Compare commits
34 Commits
cloud/1.32
...
eager-exec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c6a0f98b2 | ||
|
|
433d0f5017 | ||
|
|
3e316ac9b7 | ||
|
|
9da82f47ef | ||
|
|
a8d6f7baff | ||
|
|
a7daa5071c | ||
|
|
639975b804 | ||
|
|
ff0d385db8 | ||
|
|
6e2e591937 | ||
|
|
27fcc4554f | ||
|
|
e563c1be75 | ||
|
|
5a297e520c | ||
|
|
85bec5ee47 | ||
|
|
bdf6d4dea2 | ||
|
|
b8a796212c | ||
|
|
bc553f12be | ||
|
|
6bb35d46c1 | ||
|
|
68c38f0098 | ||
|
|
236247f05f | ||
|
|
87d6d18c57 | ||
|
|
87106ccb95 | ||
|
|
a20fb7d260 | ||
|
|
836cd7f9ba | ||
|
|
acd855601c | ||
|
|
423a2e76bc | ||
|
|
26578981d4 | ||
|
|
38fb53dca8 | ||
|
|
a832141a45 | ||
|
|
d1f0211b61 | ||
|
|
cc42c2967c | ||
|
|
bb51a5aa76 | ||
|
|
674d884e79 | ||
|
|
6f89d9a9f8 | ||
|
|
08b206f191 |
3
.github/workflows/pr-backport.yaml
vendored
@@ -78,8 +78,7 @@ jobs:
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
LABELS=$(gh pr view ${{ inputs.pr_number }} --json labels | jq -r '.labels[].name')
|
||||
else
|
||||
LABELS='${{ toJSON(github.event.pull_request.labels) }}'
|
||||
LABELS=$(echo "$LABELS" | jq -r '.[].name')
|
||||
LABELS=$(jq -r '.pull_request.labels[].name' "$GITHUB_EVENT_PATH")
|
||||
fi
|
||||
|
||||
add_target() {
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
"build-storybook": "storybook build -o dist/storybook"
|
||||
},
|
||||
"dependencies": {
|
||||
"@comfyorg/comfyui-electron-types": "0.4.73-0",
|
||||
"@comfyorg/comfyui-electron-types": "catalog:",
|
||||
"@comfyorg/shared-frontend-utils": "workspace:*",
|
||||
"@primevue/core": "catalog:",
|
||||
"@primevue/themes": "catalog:",
|
||||
|
||||
@@ -115,19 +115,18 @@ import Button from 'primevue/button'
|
||||
import Divider from 'primevue/divider'
|
||||
import InputText from 'primevue/inputtext'
|
||||
import Message from 'primevue/message'
|
||||
import { type ModelRef, computed, onMounted, ref } from 'vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import type { ModelRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import MigrationPicker from '@/components/install/MigrationPicker.vue'
|
||||
import MirrorItem from '@/components/install/mirror/MirrorItem.vue'
|
||||
import {
|
||||
PYPI_MIRROR,
|
||||
PYTHON_MIRROR,
|
||||
type UVMirror
|
||||
} from '@/constants/uvMirrors'
|
||||
import { PYPI_MIRROR, PYTHON_MIRROR } from '@/constants/uvMirrors'
|
||||
import type { UVMirror } from '@/constants/uvMirrors'
|
||||
import { electronAPI } from '@/utils/envUtil'
|
||||
import { ValidationState } from '@/utils/validationUtil'
|
||||
|
||||
import MigrationPicker from './MigrationPicker.vue'
|
||||
import MirrorItem from './mirror/MirrorItem.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const installPath = defineModel<string>('installPath', { required: true })
|
||||
@@ -229,6 +228,10 @@ const validatePath = async (path: string | undefined) => {
|
||||
}
|
||||
if (validation.parentMissing) errors.push(t('install.parentMissing'))
|
||||
if (validation.isOneDrive) errors.push(t('install.isOneDrive'))
|
||||
if (validation.isInsideAppInstallDir)
|
||||
errors.push(t('install.insideAppInstallDir'))
|
||||
if (validation.isInsideUpdaterCache)
|
||||
errors.push(t('install.insideUpdaterCache'))
|
||||
|
||||
if (validation.error)
|
||||
errors.push(`${t('install.unhandledError')}: ${validation.error}`)
|
||||
|
||||
@@ -16,7 +16,8 @@ export const DESKTOP_MAINTENANCE_TASKS: Readonly<MaintenanceTask>[] = [
|
||||
execute: async () => await electron.setBasePath(),
|
||||
name: 'Base path',
|
||||
shortDescription: 'Change the application base path.',
|
||||
errorDescription: 'Unable to open the base path. Please select a new one.',
|
||||
errorDescription:
|
||||
'The current base path is invalid or unsafe. Please select a new location.',
|
||||
description:
|
||||
'The base path is the default location where ComfyUI stores data. It is the location for the python environment, and may also contain models, custom nodes, and other extensions.',
|
||||
isInstallationFix: true,
|
||||
|
||||
@@ -85,6 +85,7 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
|
||||
const electron = electronAPI()
|
||||
|
||||
// Reactive state
|
||||
const lastUpdate = ref<InstallValidation | null>(null)
|
||||
const isRefreshing = ref(false)
|
||||
const isRunningTerminalCommand = computed(() =>
|
||||
tasks.value
|
||||
@@ -97,6 +98,13 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
|
||||
.some((task) => getRunner(task)?.executing)
|
||||
)
|
||||
|
||||
const unsafeBasePath = computed(
|
||||
() => lastUpdate.value?.unsafeBasePath === true
|
||||
)
|
||||
const unsafeBasePathReason = computed(
|
||||
() => lastUpdate.value?.unsafeBasePathReason
|
||||
)
|
||||
|
||||
// Task list
|
||||
const tasks = ref(DESKTOP_MAINTENANCE_TASKS)
|
||||
|
||||
@@ -123,6 +131,7 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
|
||||
* @param validationUpdate Update details passed in by electron
|
||||
*/
|
||||
const processUpdate = (validationUpdate: InstallValidation) => {
|
||||
lastUpdate.value = validationUpdate
|
||||
const update = validationUpdate as IndexedUpdate
|
||||
isRefreshing.value = true
|
||||
|
||||
@@ -155,7 +164,11 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
|
||||
}
|
||||
|
||||
const execute = async (task: MaintenanceTask) => {
|
||||
return getRunner(task).execute(task)
|
||||
const success = await getRunner(task).execute(task)
|
||||
if (success && task.isInstallationFix) {
|
||||
await refreshDesktopTasks()
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -163,6 +176,8 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
|
||||
isRefreshing,
|
||||
isRunningTerminalCommand,
|
||||
isRunningInstallationFix,
|
||||
unsafeBasePath,
|
||||
unsafeBasePathReason,
|
||||
execute,
|
||||
getRunner,
|
||||
processUpdate,
|
||||
|
||||
159
apps/desktop-ui/src/views/MaintenanceView.stories.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
// eslint-disable-next-line storybook/no-renderer-packages
|
||||
import type { Meta, StoryObj } from '@storybook/vue3'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
type UnsafeReason = 'appInstallDir' | 'updaterCache' | 'oneDrive' | null
|
||||
type ValidationIssueState = 'OK' | 'warning' | 'error' | 'skipped'
|
||||
|
||||
type ValidationState = {
|
||||
inProgress: boolean
|
||||
installState: string
|
||||
basePath?: ValidationIssueState
|
||||
unsafeBasePath: boolean
|
||||
unsafeBasePathReason: UnsafeReason
|
||||
venvDirectory?: ValidationIssueState
|
||||
pythonInterpreter?: ValidationIssueState
|
||||
pythonPackages?: ValidationIssueState
|
||||
uv?: ValidationIssueState
|
||||
git?: ValidationIssueState
|
||||
vcRedist?: ValidationIssueState
|
||||
upgradePackages?: ValidationIssueState
|
||||
}
|
||||
|
||||
const validationState: ValidationState = {
|
||||
inProgress: false,
|
||||
installState: 'installed',
|
||||
basePath: 'OK',
|
||||
unsafeBasePath: false,
|
||||
unsafeBasePathReason: null,
|
||||
venvDirectory: 'OK',
|
||||
pythonInterpreter: 'OK',
|
||||
pythonPackages: 'OK',
|
||||
uv: 'OK',
|
||||
git: 'OK',
|
||||
vcRedist: 'OK',
|
||||
upgradePackages: 'OK'
|
||||
}
|
||||
|
||||
const createMockElectronAPI = () => {
|
||||
const logListeners: Array<(message: string) => void> = []
|
||||
|
||||
const getValidationUpdate = () => ({
|
||||
...validationState
|
||||
})
|
||||
|
||||
return {
|
||||
getPlatform: () => 'darwin',
|
||||
changeTheme: (_theme: unknown) => {},
|
||||
onLogMessage: (listener: (message: string) => void) => {
|
||||
logListeners.push(listener)
|
||||
},
|
||||
showContextMenu: (_options: unknown) => {},
|
||||
Events: {
|
||||
trackEvent: (_eventName: string, _data?: unknown) => {}
|
||||
},
|
||||
Validation: {
|
||||
onUpdate: (_callback: (update: unknown) => void) => {},
|
||||
async getStatus() {
|
||||
return getValidationUpdate()
|
||||
},
|
||||
async validateInstallation(callback: (update: unknown) => void) {
|
||||
callback(getValidationUpdate())
|
||||
},
|
||||
async complete() {
|
||||
// Only allow completion when the base path is safe
|
||||
return !validationState.unsafeBasePath
|
||||
},
|
||||
dispose: () => {}
|
||||
},
|
||||
setBasePath: () => Promise.resolve(true),
|
||||
reinstall: () => Promise.resolve(),
|
||||
uv: {
|
||||
installRequirements: () => Promise.resolve(),
|
||||
clearCache: () => Promise.resolve(),
|
||||
resetVenv: () => Promise.resolve()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ensureElectronAPI = () => {
|
||||
const globalWindow = window as unknown as { electronAPI?: unknown }
|
||||
if (!globalWindow.electronAPI) {
|
||||
globalWindow.electronAPI = createMockElectronAPI()
|
||||
}
|
||||
|
||||
return globalWindow.electronAPI
|
||||
}
|
||||
|
||||
const MaintenanceView = defineAsyncComponent(async () => {
|
||||
ensureElectronAPI()
|
||||
const module = await import('./MaintenanceView.vue')
|
||||
return module.default
|
||||
})
|
||||
|
||||
const meta: Meta<typeof MaintenanceView> = {
|
||||
title: 'Desktop/Views/MaintenanceView',
|
||||
component: MaintenanceView,
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
backgrounds: {
|
||||
default: 'dark',
|
||||
values: [
|
||||
{ name: 'dark', value: '#0a0a0a' },
|
||||
{ name: 'neutral-900', value: '#171717' },
|
||||
{ name: 'neutral-950', value: '#0a0a0a' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
name: 'All tasks OK',
|
||||
render: () => ({
|
||||
components: { MaintenanceView },
|
||||
setup() {
|
||||
validationState.inProgress = false
|
||||
validationState.installState = 'installed'
|
||||
validationState.basePath = 'OK'
|
||||
validationState.unsafeBasePath = false
|
||||
validationState.unsafeBasePathReason = null
|
||||
validationState.venvDirectory = 'OK'
|
||||
validationState.pythonInterpreter = 'OK'
|
||||
validationState.pythonPackages = 'OK'
|
||||
validationState.uv = 'OK'
|
||||
validationState.git = 'OK'
|
||||
validationState.vcRedist = 'OK'
|
||||
validationState.upgradePackages = 'OK'
|
||||
ensureElectronAPI()
|
||||
return {}
|
||||
},
|
||||
template: '<MaintenanceView />'
|
||||
})
|
||||
}
|
||||
|
||||
export const UnsafeBasePathOneDrive: Story = {
|
||||
name: 'Unsafe base path (OneDrive)',
|
||||
render: () => ({
|
||||
components: { MaintenanceView },
|
||||
setup() {
|
||||
validationState.inProgress = false
|
||||
validationState.installState = 'installed'
|
||||
validationState.basePath = 'error'
|
||||
validationState.unsafeBasePath = true
|
||||
validationState.unsafeBasePathReason = 'oneDrive'
|
||||
validationState.venvDirectory = 'OK'
|
||||
validationState.pythonInterpreter = 'OK'
|
||||
validationState.pythonPackages = 'OK'
|
||||
validationState.uv = 'OK'
|
||||
validationState.git = 'OK'
|
||||
validationState.vcRedist = 'OK'
|
||||
validationState.upgradePackages = 'OK'
|
||||
ensureElectronAPI()
|
||||
return {}
|
||||
},
|
||||
template: '<MaintenanceView />'
|
||||
})
|
||||
}
|
||||
@@ -47,6 +47,28 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Unsafe migration warning -->
|
||||
<div v-if="taskStore.unsafeBasePath" class="my-4">
|
||||
<p class="flex items-start gap-3 text-neutral-300">
|
||||
<Tag
|
||||
icon="pi pi-exclamation-triangle"
|
||||
severity="warn"
|
||||
:value="t('icon.exclamation-triangle')"
|
||||
/>
|
||||
<span>
|
||||
<strong class="block mb-1">
|
||||
{{ t('maintenance.unsafeMigration.title') }}
|
||||
</strong>
|
||||
<span class="block mb-1">
|
||||
{{ unsafeReasonText }}
|
||||
</span>
|
||||
<span class="block text-sm text-neutral-400">
|
||||
{{ t('maintenance.unsafeMigration.action') }}
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Tasks -->
|
||||
<TaskListPanel
|
||||
class="border-neutral-700 border-solid border-x-0 border-y"
|
||||
@@ -89,10 +111,10 @@
|
||||
import { PrimeIcons } from '@primevue/core/api'
|
||||
import Button from 'primevue/button'
|
||||
import SelectButton from 'primevue/selectbutton'
|
||||
import Tag from 'primevue/tag'
|
||||
import Toast from 'primevue/toast'
|
||||
import { useToast } from 'primevue/usetoast'
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { watch } from 'vue'
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
|
||||
import RefreshButton from '@/components/common/RefreshButton.vue'
|
||||
import StatusTag from '@/components/maintenance/StatusTag.vue'
|
||||
@@ -139,6 +161,27 @@ const filterOptions = ref([
|
||||
/** Filter binding; can be set to show all tasks, or only errors. */
|
||||
const filter = ref<MaintenanceFilter>(filterOptions.value[0])
|
||||
|
||||
const unsafeReasonText = computed(() => {
|
||||
const reason = taskStore.unsafeBasePathReason
|
||||
if (!reason) {
|
||||
return t('maintenance.unsafeMigration.generic')
|
||||
}
|
||||
|
||||
if (reason === 'appInstallDir') {
|
||||
return t('maintenance.unsafeMigration.appInstallDir')
|
||||
}
|
||||
|
||||
if (reason === 'updaterCache') {
|
||||
return t('maintenance.unsafeMigration.updaterCache')
|
||||
}
|
||||
|
||||
if (reason === 'oneDrive') {
|
||||
return t('maintenance.unsafeMigration.oneDrive')
|
||||
}
|
||||
|
||||
return t('maintenance.unsafeMigration.generic')
|
||||
})
|
||||
|
||||
/** If valid, leave the validation window. */
|
||||
const completeValidation = async () => {
|
||||
const isValid = await electron.Validation.complete()
|
||||
|
||||
@@ -16,7 +16,6 @@ import { ComfyNodeSearchBox } from './components/ComfyNodeSearchBox'
|
||||
import { SettingDialog } from './components/SettingDialog'
|
||||
import {
|
||||
NodeLibrarySidebarTab,
|
||||
QueueSidebarTab,
|
||||
WorkflowsSidebarTab
|
||||
} from './components/SidebarTab'
|
||||
import { Topbar } from './components/Topbar'
|
||||
@@ -31,7 +30,6 @@ type WorkspaceStore = ReturnType<typeof useWorkspaceStore>
|
||||
class ComfyMenu {
|
||||
private _nodeLibraryTab: NodeLibrarySidebarTab | null = null
|
||||
private _workflowsTab: WorkflowsSidebarTab | null = null
|
||||
private _queueTab: QueueSidebarTab | null = null
|
||||
private _topbar: Topbar | null = null
|
||||
|
||||
public readonly sideToolbar: Locator
|
||||
@@ -60,11 +58,6 @@ class ComfyMenu {
|
||||
return this._workflowsTab
|
||||
}
|
||||
|
||||
get queueTab() {
|
||||
this._queueTab ??= new QueueSidebarTab(this.page)
|
||||
return this._queueTab
|
||||
}
|
||||
|
||||
get topbar() {
|
||||
this._topbar ??= new Topbar(this.page)
|
||||
return this._topbar
|
||||
|
||||
@@ -148,124 +148,3 @@ export class WorkflowsSidebarTab extends SidebarTab {
|
||||
.click()
|
||||
}
|
||||
}
|
||||
|
||||
export class QueueSidebarTab extends SidebarTab {
|
||||
constructor(public readonly page: Page) {
|
||||
super(page, 'queue')
|
||||
}
|
||||
|
||||
get root() {
|
||||
return this.page.locator('.sidebar-content-container', { hasText: 'Queue' })
|
||||
}
|
||||
|
||||
get tasks() {
|
||||
return this.root.locator('[data-virtual-grid-item]')
|
||||
}
|
||||
|
||||
get visibleTasks() {
|
||||
return this.tasks.locator('visible=true')
|
||||
}
|
||||
|
||||
get clearButton() {
|
||||
return this.root.locator('.clear-all-button')
|
||||
}
|
||||
|
||||
get collapseTasksButton() {
|
||||
return this.getToggleExpandButton(false)
|
||||
}
|
||||
|
||||
get expandTasksButton() {
|
||||
return this.getToggleExpandButton(true)
|
||||
}
|
||||
|
||||
get noResultsPlaceholder() {
|
||||
return this.root.locator('.no-results-placeholder')
|
||||
}
|
||||
|
||||
get galleryImage() {
|
||||
return this.page.locator('.galleria-image')
|
||||
}
|
||||
|
||||
private getToggleExpandButton(isExpanded: boolean) {
|
||||
const iconSelector = isExpanded ? '.pi-image' : '.pi-images'
|
||||
return this.root.locator(`.toggle-expanded-button ${iconSelector}`)
|
||||
}
|
||||
|
||||
async open() {
|
||||
await super.open()
|
||||
return this.root.waitFor({ state: 'visible' })
|
||||
}
|
||||
|
||||
async close() {
|
||||
await super.close()
|
||||
await this.root.waitFor({ state: 'hidden' })
|
||||
}
|
||||
|
||||
async expandTasks() {
|
||||
await this.expandTasksButton.click()
|
||||
await this.collapseTasksButton.waitFor({ state: 'visible' })
|
||||
}
|
||||
|
||||
async collapseTasks() {
|
||||
await this.collapseTasksButton.click()
|
||||
await this.expandTasksButton.waitFor({ state: 'visible' })
|
||||
}
|
||||
|
||||
async waitForTasks() {
|
||||
return Promise.all([
|
||||
this.tasks.first().waitFor({ state: 'visible' }),
|
||||
this.tasks.last().waitFor({ state: 'visible' })
|
||||
])
|
||||
}
|
||||
|
||||
async scrollTasks(direction: 'up' | 'down') {
|
||||
const scrollToEl =
|
||||
direction === 'up' ? this.tasks.last() : this.tasks.first()
|
||||
await scrollToEl.scrollIntoViewIfNeeded()
|
||||
await this.waitForTasks()
|
||||
}
|
||||
|
||||
async clearTasks() {
|
||||
await this.clearButton.click()
|
||||
const confirmButton = this.page.getByLabel('Delete')
|
||||
await confirmButton.click()
|
||||
await this.noResultsPlaceholder.waitFor({ state: 'visible' })
|
||||
}
|
||||
|
||||
/** Set the width of the tab (out of 100). Must call before opening the tab */
|
||||
async setTabWidth(width: number) {
|
||||
if (width < 0 || width > 100) {
|
||||
throw new Error('Width must be between 0 and 100')
|
||||
}
|
||||
return this.page.evaluate((width) => {
|
||||
localStorage.setItem('queue', JSON.stringify([width, 100 - width]))
|
||||
}, width)
|
||||
}
|
||||
|
||||
getTaskPreviewButton(taskIndex: number) {
|
||||
return this.tasks.nth(taskIndex).getByRole('button')
|
||||
}
|
||||
|
||||
async openTaskPreview(taskIndex: number) {
|
||||
const previewButton = this.getTaskPreviewButton(taskIndex)
|
||||
await previewButton.click()
|
||||
return this.galleryImage.waitFor({ state: 'visible' })
|
||||
}
|
||||
|
||||
getGalleryImage(imageFilename: string) {
|
||||
return this.galleryImage.and(this.page.getByAltText(imageFilename))
|
||||
}
|
||||
|
||||
getTaskImage(imageFilename: string) {
|
||||
return this.tasks.getByAltText(imageFilename)
|
||||
}
|
||||
|
||||
/** Trigger the queue store and tasks to update */
|
||||
async triggerTasksUpdate() {
|
||||
await this.page.evaluate(() => {
|
||||
window['app']['api'].dispatchCustomEvent('status', {
|
||||
exec_info: { queue_remaining: 0 }
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 106 KiB |
@@ -1,210 +0,0 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from '../../fixtures/ComfyPage'
|
||||
|
||||
test.describe.skip('Queue sidebar', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
|
||||
test('can display tasks', async ({ comfyPage }) => {
|
||||
await comfyPage.setupHistory().withTask(['example.webp']).setupRoutes()
|
||||
await comfyPage.menu.queueTab.open()
|
||||
await comfyPage.menu.queueTab.waitForTasks()
|
||||
expect(await comfyPage.menu.queueTab.visibleTasks.count()).toBe(1)
|
||||
})
|
||||
|
||||
test('can display tasks after closing then opening', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setupHistory().withTask(['example.webp']).setupRoutes()
|
||||
await comfyPage.menu.queueTab.open()
|
||||
await comfyPage.menu.queueTab.close()
|
||||
await comfyPage.menu.queueTab.open()
|
||||
await comfyPage.menu.queueTab.waitForTasks()
|
||||
expect(await comfyPage.menu.queueTab.visibleTasks.count()).toBe(1)
|
||||
})
|
||||
|
||||
test.describe('Virtual scroll', () => {
|
||||
const layouts = [
|
||||
{ description: 'Five columns layout', width: 95, rows: 3, cols: 5 },
|
||||
{ description: 'Three columns layout', width: 55, rows: 3, cols: 3 },
|
||||
{ description: 'Two columns layout', width: 40, rows: 3, cols: 2 }
|
||||
]
|
||||
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage
|
||||
.setupHistory()
|
||||
.withTask(['example.webp'])
|
||||
.repeat(50)
|
||||
.setupRoutes()
|
||||
})
|
||||
|
||||
layouts.forEach(({ description, width, rows, cols }) => {
|
||||
const preRenderedRows = 1
|
||||
const preRenderedTasks = preRenderedRows * cols * 2
|
||||
const visibleTasks = rows * cols
|
||||
const expectRenderLimit = visibleTasks + preRenderedTasks
|
||||
|
||||
test.describe(description, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.menu.queueTab.setTabWidth(width)
|
||||
await comfyPage.menu.queueTab.open()
|
||||
await comfyPage.menu.queueTab.waitForTasks()
|
||||
})
|
||||
|
||||
test('should not render items outside of view', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const renderedCount =
|
||||
await comfyPage.menu.queueTab.visibleTasks.count()
|
||||
expect(renderedCount).toBeLessThanOrEqual(expectRenderLimit)
|
||||
})
|
||||
|
||||
test('should teardown items after scrolling away', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.menu.queueTab.scrollTasks('down')
|
||||
const renderedCount =
|
||||
await comfyPage.menu.queueTab.visibleTasks.count()
|
||||
expect(renderedCount).toBeLessThanOrEqual(expectRenderLimit)
|
||||
})
|
||||
|
||||
test('should re-render items after scrolling away then back', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.menu.queueTab.scrollTasks('down')
|
||||
await comfyPage.menu.queueTab.scrollTasks('up')
|
||||
const renderedCount =
|
||||
await comfyPage.menu.queueTab.visibleTasks.count()
|
||||
expect(renderedCount).toBeLessThanOrEqual(expectRenderLimit)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Expand tasks', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
// 2-item batch and 3-item batch -> 3 additional items when expanded
|
||||
await comfyPage
|
||||
.setupHistory()
|
||||
.withTask(['example.webp', 'example.webp', 'example.webp'])
|
||||
.withTask(['example.webp', 'example.webp'])
|
||||
.setupRoutes()
|
||||
await comfyPage.menu.queueTab.open()
|
||||
await comfyPage.menu.queueTab.waitForTasks()
|
||||
})
|
||||
|
||||
test('can expand tasks with multiple outputs', async ({ comfyPage }) => {
|
||||
const initialCount = await comfyPage.menu.queueTab.visibleTasks.count()
|
||||
await comfyPage.menu.queueTab.expandTasks()
|
||||
expect(await comfyPage.menu.queueTab.visibleTasks.count()).toBe(
|
||||
initialCount + 3
|
||||
)
|
||||
})
|
||||
|
||||
test('can collapse flat tasks', async ({ comfyPage }) => {
|
||||
const initialCount = await comfyPage.menu.queueTab.visibleTasks.count()
|
||||
await comfyPage.menu.queueTab.expandTasks()
|
||||
await comfyPage.menu.queueTab.collapseTasks()
|
||||
expect(await comfyPage.menu.queueTab.visibleTasks.count()).toBe(
|
||||
initialCount
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Clear tasks', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage
|
||||
.setupHistory()
|
||||
.withTask(['example.webp'])
|
||||
.repeat(6)
|
||||
.setupRoutes()
|
||||
await comfyPage.menu.queueTab.open()
|
||||
})
|
||||
|
||||
test('can clear all tasks', async ({ comfyPage }) => {
|
||||
await comfyPage.menu.queueTab.clearTasks()
|
||||
expect(await comfyPage.menu.queueTab.visibleTasks.count()).toBe(0)
|
||||
expect(
|
||||
await comfyPage.menu.queueTab.noResultsPlaceholder.isVisible()
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
test('can load new tasks after clearing all', async ({ comfyPage }) => {
|
||||
await comfyPage.menu.queueTab.clearTasks()
|
||||
await comfyPage.menu.queueTab.close()
|
||||
await comfyPage.setupHistory().withTask(['example.webp']).setupRoutes()
|
||||
await comfyPage.menu.queueTab.open()
|
||||
await comfyPage.menu.queueTab.waitForTasks()
|
||||
expect(await comfyPage.menu.queueTab.visibleTasks.count()).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Gallery', () => {
|
||||
const firstImage = 'example.webp'
|
||||
const secondImage = 'image32x32.webp'
|
||||
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage
|
||||
.setupHistory()
|
||||
.withTask([secondImage])
|
||||
.withTask([firstImage])
|
||||
.setupRoutes()
|
||||
await comfyPage.menu.queueTab.open()
|
||||
await comfyPage.menu.queueTab.waitForTasks()
|
||||
await comfyPage.menu.queueTab.openTaskPreview(0)
|
||||
})
|
||||
|
||||
test('displays gallery image after opening task preview', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.nextFrame()
|
||||
await expect(
|
||||
comfyPage.menu.queueTab.getGalleryImage(firstImage)
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test('maintains active gallery item when new tasks are added', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Add a new task while the gallery is still open
|
||||
const newImage = 'image64x64.webp'
|
||||
comfyPage.setupHistory().withTask([newImage])
|
||||
await comfyPage.menu.queueTab.triggerTasksUpdate()
|
||||
await comfyPage.page.waitForTimeout(500)
|
||||
const newTask = comfyPage.menu.queueTab.tasks.getByAltText(newImage)
|
||||
await newTask.waitFor({ state: 'visible' })
|
||||
// The active gallery item should still be the initial image
|
||||
await expect(
|
||||
comfyPage.menu.queueTab.getGalleryImage(firstImage)
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test.describe('Gallery navigation', () => {
|
||||
const paths: {
|
||||
description: string
|
||||
path: ('Right' | 'Left')[]
|
||||
end: string
|
||||
}[] = [
|
||||
{ description: 'Right', path: ['Right'], end: secondImage },
|
||||
{ description: 'Left', path: ['Right', 'Left'], end: firstImage },
|
||||
{ description: 'Left wrap', path: ['Left'], end: secondImage },
|
||||
{ description: 'Right wrap', path: ['Right', 'Right'], end: firstImage }
|
||||
]
|
||||
|
||||
paths.forEach(({ description, path, end }) => {
|
||||
test(`can navigate gallery ${description}`, async ({ comfyPage }) => {
|
||||
for (const direction of path)
|
||||
await comfyPage.page.keyboard.press(`Arrow${direction}`, {
|
||||
delay: 256
|
||||
})
|
||||
await comfyPage.nextFrame()
|
||||
await expect(
|
||||
comfyPage.menu.queueTab.getGalleryImage(end)
|
||||
).toBeVisible()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 143 KiB After Width: | Height: | Size: 142 KiB |
49
browser_tests/tests/vueNodes/nodeStates/lod.spec.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from '../../../fixtures/ComfyPage'
|
||||
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
test.describe('Vue Nodes - LOD', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.setSetting('LiteGraph.Canvas.MinFontSizeForLOD', 8)
|
||||
await comfyPage.setup()
|
||||
await comfyPage.loadWorkflow('default')
|
||||
})
|
||||
|
||||
test('should toggle LOD based on zoom threshold', async ({ comfyPage }) => {
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
|
||||
const initialNodeCount = await comfyPage.vueNodes.getNodeCount()
|
||||
expect(initialNodeCount).toBeGreaterThan(0)
|
||||
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('vue-nodes-default.png')
|
||||
|
||||
const vueNodesContainer = comfyPage.vueNodes.nodes
|
||||
const textboxesInNodes = vueNodesContainer.getByRole('textbox')
|
||||
const comboboxesInNodes = vueNodesContainer.getByRole('combobox')
|
||||
|
||||
await expect(textboxesInNodes.first()).toBeVisible()
|
||||
await expect(comboboxesInNodes.first()).toBeVisible()
|
||||
|
||||
await comfyPage.zoom(120, 10)
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('vue-nodes-lod-active.png')
|
||||
|
||||
await expect(textboxesInNodes.first()).toBeHidden()
|
||||
await expect(comboboxesInNodes.first()).toBeHidden()
|
||||
|
||||
await comfyPage.zoom(-120, 10)
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'vue-nodes-lod-inactive.png'
|
||||
)
|
||||
await expect(textboxesInNodes.first()).toBeVisible()
|
||||
await expect(comboboxesInNodes.first()).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 82 KiB |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"private": true,
|
||||
"version": "1.32.6",
|
||||
"version": "1.33.5",
|
||||
"type": "module",
|
||||
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
|
||||
"homepage": "https://comfy.org",
|
||||
@@ -128,7 +128,7 @@
|
||||
"dependencies": {
|
||||
"@alloc/quick-lru": "catalog:",
|
||||
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
|
||||
"@comfyorg/comfyui-electron-types": "0.4.73-0",
|
||||
"@comfyorg/comfyui-electron-types": "catalog:",
|
||||
"@comfyorg/design-system": "workspace:*",
|
||||
"@comfyorg/registry-types": "workspace:*",
|
||||
"@comfyorg/shared-frontend-utils": "workspace:*",
|
||||
|
||||
@@ -1329,6 +1329,57 @@ audio.comfy-audio.empty-audio-widget {
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
/* START LOD specific styles */
|
||||
/* LOD styles - Custom CSS avoids 100+ Tailwind selectors that would slow style recalculation when .isLOD toggles */
|
||||
|
||||
.isLOD .lg-node {
|
||||
box-shadow: none;
|
||||
filter: none;
|
||||
backdrop-filter: none;
|
||||
text-shadow: none;
|
||||
mask-image: none;
|
||||
clip-path: none;
|
||||
background-image: none;
|
||||
text-rendering: optimizeSpeed;
|
||||
border-radius: 0;
|
||||
contain: layout style;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.isLOD .lg-node-header {
|
||||
border-radius: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.isLOD .lg-node-widgets {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.lod-toggle {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.isLOD .lod-toggle {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.lod-fallback {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.isLOD .lod-fallback {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.isLOD .image-preview img {
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
.isLOD .slot-dot {
|
||||
border-radius: 0;
|
||||
}
|
||||
/* END LOD specific styles */
|
||||
|
||||
/* ===================== Mask Editor Styles ===================== */
|
||||
/* To be migrated to Tailwind later */
|
||||
#maskEditor_brush {
|
||||
|
||||
55
pnpm-lock.yaml
generated
@@ -9,6 +9,9 @@ catalogs:
|
||||
'@alloc/quick-lru':
|
||||
specifier: ^5.2.0
|
||||
version: 5.2.0
|
||||
'@comfyorg/comfyui-electron-types':
|
||||
specifier: 0.5.5
|
||||
version: 0.5.5
|
||||
'@eslint/js':
|
||||
specifier: ^9.35.0
|
||||
version: 9.35.0
|
||||
@@ -318,8 +321,8 @@ importers:
|
||||
specifier: ^1.3.1
|
||||
version: 1.3.1
|
||||
'@comfyorg/comfyui-electron-types':
|
||||
specifier: 0.4.73-0
|
||||
version: 0.4.73-0
|
||||
specifier: 'catalog:'
|
||||
version: 0.5.5
|
||||
'@comfyorg/design-system':
|
||||
specifier: workspace:*
|
||||
version: link:packages/design-system
|
||||
@@ -709,8 +712,8 @@ importers:
|
||||
apps/desktop-ui:
|
||||
dependencies:
|
||||
'@comfyorg/comfyui-electron-types':
|
||||
specifier: 0.4.73-0
|
||||
version: 0.4.73-0
|
||||
specifier: 'catalog:'
|
||||
version: 0.5.5
|
||||
'@comfyorg/shared-frontend-utils':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/shared-frontend-utils
|
||||
@@ -1453,8 +1456,8 @@ packages:
|
||||
'@cacheable/utils@2.0.3':
|
||||
resolution: {integrity: sha512-m7Rce68cMHlAUjvWBy9Ru1Nmw5gU0SjGGtQDdhpe6E0xnbcvrIY0Epy//JU1VYYBUTzrG9jvgmTauULGKzOkWA==}
|
||||
|
||||
'@comfyorg/comfyui-electron-types@0.4.73-0':
|
||||
resolution: {integrity: sha512-WlItGJQx9ZWShNG9wypx3kq+19pSig/U+s5sD2SAeEcMph4u8A/TS+lnRgdKhT58VT1uD7cMcj2SJpfdBPNWvw==}
|
||||
'@comfyorg/comfyui-electron-types@0.5.5':
|
||||
resolution: {integrity: sha512-f3XOXpMsALIwHakz7FekVPm4/Fh2pvJPEi8tRe8jYGBt8edsd4Mkkq31Yjs2Weem3BP7yNwbdNuSiQdP/pxJyg==}
|
||||
|
||||
'@csstools/color-helpers@5.1.0':
|
||||
resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==}
|
||||
@@ -4413,6 +4416,9 @@ packages:
|
||||
csstype@3.1.3:
|
||||
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
||||
|
||||
csstype@3.2.3:
|
||||
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
|
||||
|
||||
data-urls@5.0.0:
|
||||
resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -7000,6 +7006,11 @@ packages:
|
||||
engines: {node: '>= 0.4'}
|
||||
hasBin: true
|
||||
|
||||
resolve@1.22.11:
|
||||
resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
hasBin: true
|
||||
|
||||
restore-cursor@3.1.0:
|
||||
resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -7095,6 +7106,11 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
semver@7.7.3:
|
||||
resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
set-function-length@1.2.2:
|
||||
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -7815,8 +7831,8 @@ packages:
|
||||
vue-component-type-helpers@3.1.1:
|
||||
resolution: {integrity: sha512-B0kHv7qX6E7+kdc5nsaqjdGZ1KwNKSUQDWGy7XkTYT7wFsOpkEyaJ1Vq79TjwrrtuLRgizrTV7PPuC4rRQo+vw==}
|
||||
|
||||
vue-component-type-helpers@3.1.3:
|
||||
resolution: {integrity: sha512-V1dOD8XYfstOKCnXbWyEJIrhTBMwSyNjv271L1Jlx9ExpNlCSuqOs3OdWrGJ0V544zXufKbcYabi/o+gK8lyfQ==}
|
||||
vue-component-type-helpers@3.1.4:
|
||||
resolution: {integrity: sha512-Uws7Ew1OzTTqHW8ZVl/qLl/HB+jf08M0NdFONbVWAx0N4gMLK8yfZDgeB77hDnBmaigWWEn5qP8T9BG59jIeyQ==}
|
||||
|
||||
vue-demi@0.14.10:
|
||||
resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
|
||||
@@ -8992,7 +9008,7 @@ snapshots:
|
||||
|
||||
'@cacheable/utils@2.0.3': {}
|
||||
|
||||
'@comfyorg/comfyui-electron-types@0.4.73-0': {}
|
||||
'@comfyorg/comfyui-electron-types@0.5.5': {}
|
||||
|
||||
'@csstools/color-helpers@5.1.0': {}
|
||||
|
||||
@@ -10617,7 +10633,7 @@ snapshots:
|
||||
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
|
||||
type-fest: 2.19.0
|
||||
vue: 3.5.13(typescript@5.9.2)
|
||||
vue-component-type-helpers: 3.1.3
|
||||
vue-component-type-helpers: 3.1.4
|
||||
|
||||
'@swc/helpers@0.5.17':
|
||||
dependencies:
|
||||
@@ -10989,7 +11005,7 @@ snapshots:
|
||||
|
||||
'@types/react@19.1.9':
|
||||
dependencies:
|
||||
csstype: 3.1.3
|
||||
csstype: 3.2.3
|
||||
|
||||
'@types/semver@7.7.0': {}
|
||||
|
||||
@@ -12168,6 +12184,8 @@ snapshots:
|
||||
|
||||
csstype@3.1.3: {}
|
||||
|
||||
csstype@3.2.3: {}
|
||||
|
||||
data-urls@5.0.0:
|
||||
dependencies:
|
||||
whatwg-mimetype: 4.0.0
|
||||
@@ -12594,7 +12612,7 @@ snapshots:
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
is-core-module: 2.16.1
|
||||
resolve: 1.22.10
|
||||
resolve: 1.22.11
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
optional: true
|
||||
@@ -13740,7 +13758,7 @@ snapshots:
|
||||
acorn: 8.15.0
|
||||
eslint-visitor-keys: 3.4.3
|
||||
espree: 9.6.1
|
||||
semver: 7.7.2
|
||||
semver: 7.7.3
|
||||
|
||||
jsonc-parser@3.2.0: {}
|
||||
|
||||
@@ -15345,6 +15363,13 @@ snapshots:
|
||||
path-parse: 1.0.7
|
||||
supports-preserve-symlinks-flag: 1.0.0
|
||||
|
||||
resolve@1.22.11:
|
||||
dependencies:
|
||||
is-core-module: 2.16.1
|
||||
path-parse: 1.0.7
|
||||
supports-preserve-symlinks-flag: 1.0.0
|
||||
optional: true
|
||||
|
||||
restore-cursor@3.1.0:
|
||||
dependencies:
|
||||
onetime: 5.1.2
|
||||
@@ -15449,6 +15474,8 @@ snapshots:
|
||||
|
||||
semver@7.7.2: {}
|
||||
|
||||
semver@7.7.3: {}
|
||||
|
||||
set-function-length@1.2.2:
|
||||
dependencies:
|
||||
define-data-property: 1.1.4
|
||||
@@ -16343,7 +16370,7 @@ snapshots:
|
||||
|
||||
vue-component-type-helpers@3.1.1: {}
|
||||
|
||||
vue-component-type-helpers@3.1.3: {}
|
||||
vue-component-type-helpers@3.1.4: {}
|
||||
|
||||
vue-demi@0.14.10(vue@3.5.13(typescript@5.9.2)):
|
||||
dependencies:
|
||||
|
||||
@@ -4,6 +4,7 @@ packages:
|
||||
|
||||
catalog:
|
||||
'@alloc/quick-lru': ^5.2.0
|
||||
'@comfyorg/comfyui-electron-types': 0.5.5
|
||||
'@eslint/js': ^9.35.0
|
||||
'@iconify-json/lucide': ^1.1.178
|
||||
'@iconify/json': ^2.2.380
|
||||
|
||||
@@ -31,46 +31,12 @@
|
||||
</template>
|
||||
</SplitButton>
|
||||
<BatchCountEdit />
|
||||
<ButtonGroup class="execution-actions flex flex-nowrap">
|
||||
<Button
|
||||
v-tooltip.bottom="{
|
||||
value: $t('menu.interrupt'),
|
||||
showDelay: 600
|
||||
}"
|
||||
icon="pi pi-times"
|
||||
:severity="executingPrompt ? 'danger' : 'secondary'"
|
||||
:disabled="!executingPrompt"
|
||||
text
|
||||
:aria-label="$t('menu.interrupt')"
|
||||
@click="() => commandStore.execute('Comfy.Interrupt')"
|
||||
/>
|
||||
<Button
|
||||
v-tooltip.bottom="{
|
||||
value: $t('sideToolbar.queueTab.clearPendingTasks'),
|
||||
showDelay: 600
|
||||
}"
|
||||
icon="pi pi-stop"
|
||||
:severity="hasPendingTasks ? 'danger' : 'secondary'"
|
||||
:disabled="!hasPendingTasks"
|
||||
text
|
||||
:aria-label="$t('sideToolbar.queueTab.clearPendingTasks')"
|
||||
@click="
|
||||
() => {
|
||||
if (queueCountStore.count.value > 1) {
|
||||
commandStore.execute('Comfy.ClearPendingTasks')
|
||||
}
|
||||
queueMode = 'disabled'
|
||||
}
|
||||
"
|
||||
/>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia'
|
||||
import Button from 'primevue/button'
|
||||
import ButtonGroup from 'primevue/buttongroup'
|
||||
import type { MenuItem } from 'primevue/menuitem'
|
||||
import SplitButton from 'primevue/splitbutton'
|
||||
import { computed } from 'vue'
|
||||
@@ -78,26 +44,17 @@ import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { useTelemetry } from '@/platform/telemetry'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useNodeDefStore } from '@/stores/nodeDefStore'
|
||||
import {
|
||||
useQueuePendingTaskCountStore,
|
||||
useQueueSettingsStore
|
||||
} from '@/stores/queueStore'
|
||||
import { useQueueSettingsStore } from '@/stores/queueStore'
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
import { graphHasMissingNodes } from '@/workbench/extensions/manager/utils/graphHasMissingNodes'
|
||||
import { useMissingNodes } from '@/workbench/extensions/manager/composables/nodePack/useMissingNodes'
|
||||
|
||||
import BatchCountEdit from '../BatchCountEdit.vue'
|
||||
|
||||
const workspaceStore = useWorkspaceStore()
|
||||
const queueCountStore = storeToRefs(useQueuePendingTaskCountStore())
|
||||
const { mode: queueMode, batchCount } = storeToRefs(useQueueSettingsStore())
|
||||
|
||||
const nodeDefStore = useNodeDefStore()
|
||||
const hasMissingNodes = computed(() =>
|
||||
graphHasMissingNodes(app.graph, nodeDefStore.nodeDefsByName)
|
||||
)
|
||||
const { hasMissingNodes } = useMissingNodes()
|
||||
|
||||
const { t } = useI18n()
|
||||
const queueModeMenuItemLookup = computed(() => {
|
||||
@@ -149,11 +106,6 @@ const queueModeMenuItems = computed(() =>
|
||||
Object.values(queueModeMenuItemLookup.value)
|
||||
)
|
||||
|
||||
const executingPrompt = computed(() => !!queueCountStore.count.value)
|
||||
const hasPendingTasks = computed(
|
||||
() => queueCountStore.count.value > 1 || queueMode.value !== 'disabled'
|
||||
)
|
||||
|
||||
const iconClass = computed(() => {
|
||||
if (hasMissingNodes.value) {
|
||||
return 'icon-[lucide--triangle-alert]'
|
||||
|
||||
@@ -64,13 +64,11 @@ import {
|
||||
ComfyWorkflow,
|
||||
useWorkflowStore
|
||||
} from '@/platform/workflow/management/stores/workflowStore'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useNodeDefStore } from '@/stores/nodeDefStore'
|
||||
import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore'
|
||||
import { appendJsonExt } from '@/utils/formatUtil'
|
||||
import { graphHasMissingNodes } from '@/workbench/extensions/manager/utils/graphHasMissingNodes'
|
||||
import { useMissingNodes } from '@/workbench/extensions/manager/composables/nodePack/useMissingNodes'
|
||||
|
||||
interface Props {
|
||||
item: MenuItem
|
||||
@@ -81,10 +79,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
isActive: false
|
||||
})
|
||||
|
||||
const nodeDefStore = useNodeDefStore()
|
||||
const hasMissingNodes = computed(() =>
|
||||
graphHasMissingNodes(app.graph, nodeDefStore.nodeDefsByName)
|
||||
)
|
||||
const { hasMissingNodes } = useMissingNodes()
|
||||
|
||||
const { t } = useI18n()
|
||||
const menu = ref<InstanceType<typeof Menu> & MenuState>()
|
||||
|
||||
@@ -68,4 +68,8 @@ const toggle = (event: Event) => {
|
||||
const hide = () => {
|
||||
popover.value?.hide()
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
hide
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
class="w-62.5"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--arrow-up-down] text-muted-foreground" />
|
||||
<i class="icon-[lucide--arrow-up-down]" />
|
||||
</template>
|
||||
</SingleSelect>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex w-[490px] flex-col border-t-1 border-b-1 border-border-default"
|
||||
class="flex w-[490px] flex-col border-t-1 border-border-default"
|
||||
:class="isCloud ? 'border-b-1' : ''"
|
||||
>
|
||||
<div class="flex h-full w-full flex-col gap-4 p-4">
|
||||
<!-- Description -->
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<InputText
|
||||
ref="inputRef"
|
||||
v-model="inputValue"
|
||||
:placeholder
|
||||
autofocus
|
||||
@keyup.enter="onConfirm"
|
||||
@focus="selectAllText"
|
||||
@@ -28,6 +29,7 @@ const props = defineProps<{
|
||||
message: string
|
||||
defaultValue: string
|
||||
onConfirm: (value: string) => void
|
||||
placeholder?: string
|
||||
}>()
|
||||
|
||||
const inputValue = ref<string>(props.defaultValue)
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
root: ({ props }: MultiSelectPassThroughMethodOptions) => ({
|
||||
class: cn(
|
||||
'h-10 relative inline-flex cursor-pointer select-none',
|
||||
'rounded-lg bg-secondary-background text-base-foreground',
|
||||
'rounded-lg bg-base-background text-base-foreground',
|
||||
'transition-all duration-200 ease-in-out',
|
||||
'border-[2.5px] border-solid',
|
||||
selectedCount > 0
|
||||
@@ -127,7 +127,7 @@
|
||||
|
||||
<!-- Trigger value (keep text scale identical) -->
|
||||
<template #value>
|
||||
<span class="text-sm">
|
||||
<span class="text-sm text-muted-foreground">
|
||||
{{ label }}
|
||||
</span>
|
||||
<span
|
||||
@@ -140,7 +140,7 @@
|
||||
|
||||
<!-- Chevron size identical to current -->
|
||||
<template #dropdownicon>
|
||||
<i class="icon-[lucide--chevron-down] text-muted-foreground" />
|
||||
<i class="icon-[lucide--chevron-down] text-lg text-neutral-400" />
|
||||
</template>
|
||||
|
||||
<!-- Custom option row: square checkbox + label (unchanged layout/colors) -->
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div :class="wrapperStyle" @click="focusInput">
|
||||
<i class="icon-[lucide--search] text-muted-foreground" />
|
||||
<i class="icon-[lucide--search] text-muted" />
|
||||
<InputText
|
||||
ref="input"
|
||||
v-model="internalSearchQuery"
|
||||
@@ -73,7 +73,7 @@ onMounted(() => autofocus && focusInput())
|
||||
|
||||
const wrapperStyle = computed(() => {
|
||||
const baseClasses =
|
||||
'relative flex w-full items-center gap-2 bg-secondary-background cursor-text'
|
||||
'relative flex w-full items-center gap-2 bg-base-background cursor-text'
|
||||
|
||||
if (showBorder) {
|
||||
return cn(
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
'h-10 relative inline-flex cursor-pointer select-none items-center',
|
||||
// trigger surface
|
||||
'rounded-lg',
|
||||
'bg-secondary-background text-base-foreground',
|
||||
'bg-base-background text-base-foreground',
|
||||
'border-[2.5px] border-solid border-transparent',
|
||||
'transition-all duration-200 ease-in-out',
|
||||
'focus-within:border-node-component-border',
|
||||
@@ -84,7 +84,7 @@
|
||||
>
|
||||
<!-- Trigger value -->
|
||||
<template #value="slotProps">
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<div class="flex items-center gap-2 text-sm text-neutral-500">
|
||||
<slot name="icon" />
|
||||
<span
|
||||
v-if="slotProps.value !== null && slotProps.value !== undefined"
|
||||
@@ -100,7 +100,7 @@
|
||||
|
||||
<!-- Trigger caret -->
|
||||
<template #dropdownicon>
|
||||
<i class="icon-[lucide--chevron-down] text-muted-foreground" />
|
||||
<i class="icon-[lucide--chevron-down] text-base text-neutral-500" />
|
||||
</template>
|
||||
|
||||
<!-- Option row -->
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
@click.stop="handleNodes2ToggleClick"
|
||||
>
|
||||
<span class="p-menubar-item-label text-nowrap">{{ item.label }}</span>
|
||||
<Tag severity="info" class="ml-2 text-xs">{{ $t('g.beta') }}</Tag>
|
||||
<ToggleSwitch
|
||||
v-model="nodes2Enabled"
|
||||
class="ml-4"
|
||||
@@ -102,7 +101,6 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { MenuItem } from 'primevue/menuitem'
|
||||
import Tag from 'primevue/tag'
|
||||
import TieredMenu from 'primevue/tieredmenu'
|
||||
import type { TieredMenuMethods, TieredMenuState } from 'primevue/tieredmenu'
|
||||
import ToggleSwitch from 'primevue/toggleswitch'
|
||||
|
||||
@@ -85,10 +85,13 @@
|
||||
:show-output-count="shouldShowOutputCount(item)"
|
||||
:output-count="getOutputCount(item)"
|
||||
:show-delete-button="shouldShowDeleteButton"
|
||||
:open-popover-id="openPopoverId"
|
||||
@click="handleAssetSelect(item)"
|
||||
@zoom="handleZoomClick(item)"
|
||||
@output-count-click="enterFolderView(item)"
|
||||
@asset-deleted="refreshAssets"
|
||||
@popover-opened="openPopoverId = item.id"
|
||||
@popover-closed="openPopoverId = null"
|
||||
/>
|
||||
</template>
|
||||
</VirtualGrid>
|
||||
@@ -199,6 +202,9 @@ const folderPromptId = ref<string | null>(null)
|
||||
const folderExecutionTime = ref<number | undefined>(undefined)
|
||||
const isInFolderView = computed(() => folderPromptId.value !== null)
|
||||
|
||||
// Track which asset's popover is open (for single-instance popover management)
|
||||
const openPopoverId = ref<string | null>(null)
|
||||
|
||||
// Determine if delete button should be shown
|
||||
// Hide delete button when in input tab and not in cloud (OSS mode - files are from local folders)
|
||||
const shouldShowDeleteButton = computed(() => {
|
||||
@@ -208,7 +214,7 @@ const shouldShowDeleteButton = computed(() => {
|
||||
|
||||
const getOutputCount = (item: AssetItem): number => {
|
||||
const count = item.user_metadata?.outputCount
|
||||
return typeof count === 'number' && count > 0 ? count : 1
|
||||
return typeof count === 'number' && count > 0 ? count : 0
|
||||
}
|
||||
|
||||
const shouldShowOutputCount = (item: AssetItem): boolean => {
|
||||
|
||||
@@ -1,289 +0,0 @@
|
||||
<template>
|
||||
<SidebarTabTemplate :title="$t('sideToolbar.queue')">
|
||||
<template #tool-buttons>
|
||||
<Button
|
||||
v-tooltip.bottom="$t(`sideToolbar.queueTab.${imageFit}ImagePreview`)"
|
||||
:icon="
|
||||
imageFit === 'cover'
|
||||
? 'pi pi-arrow-down-left-and-arrow-up-right-to-center'
|
||||
: 'pi pi-arrow-up-right-and-arrow-down-left-from-center'
|
||||
"
|
||||
text
|
||||
severity="secondary"
|
||||
class="toggle-expanded-button"
|
||||
@click="toggleImageFit"
|
||||
/>
|
||||
<Button
|
||||
v-if="isInFolderView"
|
||||
v-tooltip.bottom="$t('sideToolbar.queueTab.backToAllTasks')"
|
||||
icon="pi pi-arrow-left"
|
||||
text
|
||||
severity="secondary"
|
||||
class="back-button"
|
||||
@click="exitFolderView"
|
||||
/>
|
||||
<template v-else>
|
||||
<Button
|
||||
v-tooltip="$t('sideToolbar.queueTab.showFlatList')"
|
||||
:icon="isExpanded ? 'pi pi-images' : 'pi pi-image'"
|
||||
text
|
||||
severity="secondary"
|
||||
class="toggle-expanded-button"
|
||||
@click="toggleExpanded"
|
||||
/>
|
||||
<Button
|
||||
v-if="queueStore.hasPendingTasks"
|
||||
v-tooltip.bottom="$t('sideToolbar.queueTab.clearPendingTasks')"
|
||||
icon="pi pi-stop"
|
||||
severity="danger"
|
||||
text
|
||||
@click="() => commandStore.execute('Comfy.ClearPendingTasks')"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-trash"
|
||||
text
|
||||
severity="primary"
|
||||
class="clear-all-button"
|
||||
@click="confirmRemoveAll($event)"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
<template #body>
|
||||
<VirtualGrid
|
||||
v-if="allTasks?.length"
|
||||
:items="allTasks"
|
||||
:grid-style="{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))',
|
||||
padding: '0.5rem',
|
||||
gap: '0.5rem'
|
||||
}"
|
||||
>
|
||||
<template #item="{ item }">
|
||||
<TaskItem
|
||||
:task="item"
|
||||
:is-flat-task="isExpanded || isInFolderView"
|
||||
@contextmenu="handleContextMenu"
|
||||
@preview="handlePreview"
|
||||
@task-output-length-clicked="enterFolderView($event)"
|
||||
/>
|
||||
</template>
|
||||
</VirtualGrid>
|
||||
<div v-else-if="queueStore.isLoading">
|
||||
<ProgressSpinner
|
||||
style="width: 50px; left: 50%; transform: translateX(-50%)"
|
||||
/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<NoResultsPlaceholder
|
||||
icon="pi pi-info-circle"
|
||||
:title="$t('g.noTasksFound')"
|
||||
:message="$t('g.noTasksFoundMessage')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</SidebarTabTemplate>
|
||||
<ConfirmPopup />
|
||||
<ContextMenu ref="menu" :model="menuItems" />
|
||||
<ResultGallery
|
||||
v-model:active-index="galleryActiveIndex"
|
||||
:all-gallery-items="allGalleryItems"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Button from 'primevue/button'
|
||||
import ConfirmPopup from 'primevue/confirmpopup'
|
||||
import ContextMenu from 'primevue/contextmenu'
|
||||
import type { MenuItem } from 'primevue/menuitem'
|
||||
import ProgressSpinner from 'primevue/progressspinner'
|
||||
import { useConfirm } from 'primevue/useconfirm'
|
||||
import { useToast } from 'primevue/usetoast'
|
||||
import { computed, ref, shallowRef, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
|
||||
import VirtualGrid from '@/components/common/VirtualGrid.vue'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import type { ComfyNode } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useLitegraphService } from '@/services/litegraphService'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import type { ResultItemImpl, TaskItemImpl } from '@/stores/queueStore'
|
||||
import { useQueueStore } from '@/stores/queueStore'
|
||||
|
||||
import SidebarTabTemplate from './SidebarTabTemplate.vue'
|
||||
import ResultGallery from './queue/ResultGallery.vue'
|
||||
import TaskItem from './queue/TaskItem.vue'
|
||||
|
||||
const IMAGE_FIT = 'Comfy.Queue.ImageFit'
|
||||
const confirm = useConfirm()
|
||||
const toast = useToast()
|
||||
const queueStore = useQueueStore()
|
||||
const settingStore = useSettingStore()
|
||||
const commandStore = useCommandStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
// Expanded view: show all outputs in a flat list.
|
||||
const isExpanded = ref(false)
|
||||
const galleryActiveIndex = ref(-1)
|
||||
const allGalleryItems = shallowRef<ResultItemImpl[]>([])
|
||||
// Folder view: only show outputs from a single selected task.
|
||||
const folderTask = ref<TaskItemImpl | null>(null)
|
||||
const isInFolderView = computed(() => folderTask.value !== null)
|
||||
const imageFit = computed<string>(() => settingStore.get(IMAGE_FIT))
|
||||
|
||||
const allTasks = computed(() =>
|
||||
isInFolderView.value
|
||||
? folderTask.value
|
||||
? folderTask.value.flatten()
|
||||
: []
|
||||
: isExpanded.value
|
||||
? queueStore.flatTasks
|
||||
: queueStore.tasks
|
||||
)
|
||||
const updateGalleryItems = () => {
|
||||
allGalleryItems.value = allTasks.value.flatMap((task: TaskItemImpl) => {
|
||||
const previewOutput = task.previewOutput
|
||||
return previewOutput ? [previewOutput] : []
|
||||
})
|
||||
}
|
||||
|
||||
const toggleExpanded = () => {
|
||||
isExpanded.value = !isExpanded.value
|
||||
}
|
||||
|
||||
const removeTask = async (task: TaskItemImpl) => {
|
||||
if (task.isRunning) {
|
||||
await api.interrupt(task.promptId)
|
||||
}
|
||||
await queueStore.delete(task)
|
||||
}
|
||||
|
||||
const removeAllTasks = async () => {
|
||||
await queueStore.clear()
|
||||
}
|
||||
|
||||
const confirmRemoveAll = (event: Event) => {
|
||||
confirm.require({
|
||||
target: event.currentTarget as HTMLElement,
|
||||
message: 'Do you want to delete all tasks?',
|
||||
icon: 'pi pi-info-circle',
|
||||
rejectProps: {
|
||||
label: 'Cancel',
|
||||
severity: 'secondary',
|
||||
outlined: true
|
||||
},
|
||||
acceptProps: {
|
||||
label: 'Delete',
|
||||
severity: 'danger'
|
||||
},
|
||||
accept: async () => {
|
||||
await removeAllTasks()
|
||||
toast.add({
|
||||
severity: 'info',
|
||||
summary: 'Confirmed',
|
||||
detail: 'Tasks deleted',
|
||||
life: 3000
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const menu = ref<InstanceType<typeof ContextMenu> | null>(null)
|
||||
const menuTargetTask = ref<TaskItemImpl | null>(null)
|
||||
const menuTargetNode = ref<ComfyNode | null>(null)
|
||||
const menuItems = computed<MenuItem[]>(() => {
|
||||
const items: MenuItem[] = [
|
||||
{
|
||||
label: t('g.delete'),
|
||||
icon: 'pi pi-trash',
|
||||
command: () => menuTargetTask.value && removeTask(menuTargetTask.value),
|
||||
disabled: isExpanded.value || isInFolderView.value
|
||||
},
|
||||
{
|
||||
label: t('g.loadWorkflow'),
|
||||
icon: 'pi pi-file-export',
|
||||
command: () => menuTargetTask.value?.loadWorkflow(app),
|
||||
disabled: isCloud
|
||||
? !menuTargetTask.value?.isHistory
|
||||
: !menuTargetTask.value?.workflow
|
||||
},
|
||||
{
|
||||
label: t('g.goToNode'),
|
||||
icon: 'pi pi-arrow-circle-right',
|
||||
command: () => {
|
||||
if (!menuTargetNode.value) return
|
||||
useLitegraphService().goToNode(menuTargetNode.value.id)
|
||||
},
|
||||
visible: !!menuTargetNode.value
|
||||
}
|
||||
]
|
||||
|
||||
if (menuTargetTask.value?.previewOutput?.mediaType === 'images') {
|
||||
items.push({
|
||||
label: t('g.setAsBackground'),
|
||||
icon: 'pi pi-image',
|
||||
command: () => {
|
||||
const url = menuTargetTask.value?.previewOutput?.url
|
||||
if (url) {
|
||||
void settingStore.set('Comfy.Canvas.BackgroundImage', url)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return items
|
||||
})
|
||||
|
||||
const handleContextMenu = ({
|
||||
task,
|
||||
event,
|
||||
node
|
||||
}: {
|
||||
task: TaskItemImpl
|
||||
event: Event
|
||||
node: ComfyNode | null
|
||||
}) => {
|
||||
menuTargetTask.value = task
|
||||
menuTargetNode.value = node
|
||||
menu.value?.show(event)
|
||||
}
|
||||
|
||||
const handlePreview = (task: TaskItemImpl) => {
|
||||
updateGalleryItems()
|
||||
galleryActiveIndex.value = allGalleryItems.value.findIndex(
|
||||
(item) => item.url === task.previewOutput?.url
|
||||
)
|
||||
}
|
||||
|
||||
const enterFolderView = (task: TaskItemImpl) => {
|
||||
folderTask.value = task
|
||||
}
|
||||
|
||||
const exitFolderView = () => {
|
||||
folderTask.value = null
|
||||
}
|
||||
|
||||
const toggleImageFit = async () => {
|
||||
await settingStore.set(
|
||||
IMAGE_FIT,
|
||||
imageFit.value === 'cover' ? 'contain' : 'cover'
|
||||
)
|
||||
}
|
||||
|
||||
watch(allTasks, () => {
|
||||
const isGalleryOpen = galleryActiveIndex.value !== -1
|
||||
if (!isGalleryOpen) return
|
||||
|
||||
const prevLength = allGalleryItems.value.length
|
||||
updateGalleryItems()
|
||||
const lengthChange = allGalleryItems.value.length - prevLength
|
||||
if (!lengthChange) return
|
||||
|
||||
const newIndex = galleryActiveIndex.value + lengthChange
|
||||
galleryActiveIndex.value = Math.max(0, newIndex)
|
||||
})
|
||||
</script>
|
||||
@@ -1,79 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
ref="resultContainer"
|
||||
class="result-container"
|
||||
@click="handlePreviewClick"
|
||||
>
|
||||
<ComfyImage
|
||||
v-if="result.isImage"
|
||||
:src="result.url"
|
||||
class="task-output-image"
|
||||
:contain="imageFit === 'contain'"
|
||||
:alt="result.filename"
|
||||
/>
|
||||
<ResultVideo v-else-if="result.isVideo" :result="result" />
|
||||
<ResultAudio v-else-if="result.isAudio" :result="result" />
|
||||
<div v-else class="task-result-preview">
|
||||
<i class="pi pi-file" />
|
||||
<span>{{ result.mediaType }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
|
||||
import ComfyImage from '@/components/common/ComfyImage.vue'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import type { ResultItemImpl } from '@/stores/queueStore'
|
||||
|
||||
import ResultAudio from './ResultAudio.vue'
|
||||
import ResultVideo from './ResultVideo.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
result: ResultItemImpl
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'preview', result: ResultItemImpl): void
|
||||
}>()
|
||||
|
||||
const resultContainer = ref<HTMLElement | null>(null)
|
||||
const settingStore = useSettingStore()
|
||||
const imageFit = computed<string>(() =>
|
||||
settingStore.get('Comfy.Queue.ImageFit')
|
||||
)
|
||||
|
||||
const handlePreviewClick = () => {
|
||||
if (props.result.supportsPreview) {
|
||||
emit('preview', props.result)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (props.result.mediaType === 'images') {
|
||||
resultContainer.value?.querySelectorAll('img').forEach((img) => {
|
||||
img.draggable = true
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.result-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
aspect-ratio: 1 / 1;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.result-container:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
</style>
|
||||
@@ -1,271 +0,0 @@
|
||||
<template>
|
||||
<div class="task-item" @contextmenu="handleContextMenu">
|
||||
<div class="task-result-preview">
|
||||
<template
|
||||
v-if="
|
||||
task.displayStatus === TaskItemDisplayStatus.Completed ||
|
||||
cancelledWithResults
|
||||
"
|
||||
>
|
||||
<ResultItem
|
||||
v-if="flatOutputs.length && coverResult"
|
||||
:result="coverResult"
|
||||
@preview="handlePreview"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="task.displayStatus === TaskItemDisplayStatus.Running">
|
||||
<i v-if="!progressPreviewBlobUrl" class="pi pi-spin pi-spinner" />
|
||||
<img
|
||||
v-else
|
||||
:src="progressPreviewBlobUrl"
|
||||
class="progress-preview-img"
|
||||
/>
|
||||
</template>
|
||||
<span v-else-if="task.displayStatus === TaskItemDisplayStatus.Pending"
|
||||
>...</span
|
||||
>
|
||||
<i
|
||||
v-else-if="cancelledWithoutResults"
|
||||
class="pi pi-exclamation-triangle"
|
||||
/>
|
||||
<i
|
||||
v-else-if="task.displayStatus === TaskItemDisplayStatus.Failed"
|
||||
class="pi pi-exclamation-circle"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="task-item-details">
|
||||
<div class="tag-wrapper status-tag-group">
|
||||
<Tag v-if="isFlatTask && task.isHistory" class="node-name-tag">
|
||||
<Button
|
||||
class="task-node-link"
|
||||
:label="`${node?.type} (#${node?.id})`"
|
||||
link
|
||||
size="small"
|
||||
@click="
|
||||
() => {
|
||||
if (!node) return
|
||||
litegraphService.goToNode(node.id)
|
||||
}
|
||||
"
|
||||
/>
|
||||
</Tag>
|
||||
<Tag :severity="taskTagSeverity(task.displayStatus)">
|
||||
<span v-html="taskStatusText(task.displayStatus)" />
|
||||
<span v-if="task.isHistory" class="task-time">
|
||||
{{ formatTime(task.executionTimeInSeconds) }}
|
||||
</span>
|
||||
<span v-if="isFlatTask" class="task-prompt-id">
|
||||
{{ task.promptId.split('-')[0] }}
|
||||
</span>
|
||||
</Tag>
|
||||
</div>
|
||||
<div class="tag-wrapper">
|
||||
<Button
|
||||
v-if="task.isHistory && flatOutputs.length > 1"
|
||||
outlined
|
||||
@click="handleOutputLengthClick"
|
||||
>
|
||||
<span style="font-weight: 700">{{ flatOutputs.length }}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Button from 'primevue/button'
|
||||
import Tag from 'primevue/tag'
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
import type { ComfyNode } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import { api } from '@/scripts/api'
|
||||
import { useLitegraphService } from '@/services/litegraphService'
|
||||
import { TaskItemDisplayStatus } from '@/stores/queueStore'
|
||||
import type { TaskItemImpl } from '@/stores/queueStore'
|
||||
|
||||
import ResultItem from './ResultItem.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
task: TaskItemImpl
|
||||
isFlatTask: boolean
|
||||
}>()
|
||||
|
||||
const litegraphService = useLitegraphService()
|
||||
|
||||
const flatOutputs = props.task.flatOutputs
|
||||
const coverResult = flatOutputs.length
|
||||
? props.task.previewOutput || flatOutputs[0]
|
||||
: null
|
||||
// Using `==` instead of `===` because NodeId can be a string or a number
|
||||
const node: ComfyNode | null =
|
||||
flatOutputs.length && props.task.workflow
|
||||
? (props.task.workflow.nodes.find(
|
||||
(n: ComfyNode) => n.id == coverResult?.nodeId
|
||||
) ?? null)
|
||||
: null
|
||||
const progressPreviewBlobUrl = ref('')
|
||||
|
||||
const emit = defineEmits<{
|
||||
(
|
||||
e: 'contextmenu',
|
||||
value: { task: TaskItemImpl; event: MouseEvent; node: ComfyNode | null }
|
||||
): void
|
||||
(e: 'preview', value: TaskItemImpl): void
|
||||
(e: 'task-output-length-clicked', value: TaskItemImpl): void
|
||||
}>()
|
||||
|
||||
onMounted(() => {
|
||||
api.addEventListener('b_preview', onProgressPreviewReceived)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (progressPreviewBlobUrl.value) {
|
||||
URL.revokeObjectURL(progressPreviewBlobUrl.value)
|
||||
}
|
||||
api.removeEventListener('b_preview', onProgressPreviewReceived)
|
||||
})
|
||||
|
||||
const handleContextMenu = (e: MouseEvent) => {
|
||||
emit('contextmenu', { task: props.task, event: e, node })
|
||||
}
|
||||
|
||||
const handlePreview = () => {
|
||||
emit('preview', props.task)
|
||||
}
|
||||
|
||||
const handleOutputLengthClick = () => {
|
||||
emit('task-output-length-clicked', props.task)
|
||||
}
|
||||
|
||||
const taskTagSeverity = (status: TaskItemDisplayStatus) => {
|
||||
switch (status) {
|
||||
case TaskItemDisplayStatus.Pending:
|
||||
return 'secondary'
|
||||
case TaskItemDisplayStatus.Running:
|
||||
return 'info'
|
||||
case TaskItemDisplayStatus.Completed:
|
||||
return 'success'
|
||||
case TaskItemDisplayStatus.Failed:
|
||||
return 'danger'
|
||||
case TaskItemDisplayStatus.Cancelled:
|
||||
return 'warn'
|
||||
}
|
||||
}
|
||||
|
||||
const taskStatusText = (status: TaskItemDisplayStatus) => {
|
||||
switch (status) {
|
||||
case TaskItemDisplayStatus.Pending:
|
||||
return 'Pending'
|
||||
case TaskItemDisplayStatus.Running:
|
||||
return '<i class="pi pi-spin pi-spinner" style="font-weight: bold"></i> Running'
|
||||
case TaskItemDisplayStatus.Completed:
|
||||
return '<i class="pi pi-check" style="font-weight: bold"></i>'
|
||||
case TaskItemDisplayStatus.Failed:
|
||||
return 'Failed'
|
||||
case TaskItemDisplayStatus.Cancelled:
|
||||
return 'Cancelled'
|
||||
}
|
||||
}
|
||||
|
||||
const formatTime = (time?: number) => {
|
||||
if (time === undefined) {
|
||||
return ''
|
||||
}
|
||||
return `${time.toFixed(2)}s`
|
||||
}
|
||||
|
||||
const onProgressPreviewReceived = async ({ detail }: CustomEvent) => {
|
||||
if (props.task.displayStatus === TaskItemDisplayStatus.Running) {
|
||||
if (progressPreviewBlobUrl.value) {
|
||||
URL.revokeObjectURL(progressPreviewBlobUrl.value)
|
||||
}
|
||||
progressPreviewBlobUrl.value = URL.createObjectURL(detail)
|
||||
}
|
||||
}
|
||||
|
||||
const cancelledWithResults = computed(() => {
|
||||
return (
|
||||
props.task.displayStatus === TaskItemDisplayStatus.Cancelled &&
|
||||
flatOutputs.length
|
||||
)
|
||||
})
|
||||
|
||||
const cancelledWithoutResults = computed(() => {
|
||||
return (
|
||||
props.task.displayStatus === TaskItemDisplayStatus.Cancelled &&
|
||||
flatOutputs.length === 0
|
||||
)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.task-result-preview {
|
||||
aspect-ratio: 1 / 1;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.task-result-preview i,
|
||||
.task-result-preview span {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.task-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.task-item-details {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
padding: 0.6rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
pointer-events: none; /* Allow clicks to pass through this div */
|
||||
}
|
||||
|
||||
/* Make individual controls clickable again by restoring pointer events */
|
||||
.task-item-details .tag-wrapper,
|
||||
.task-item-details button {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.task-node-link {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
/* In dark mode, transparent background color for tags is not ideal for tags that
|
||||
are floating on top of images. */
|
||||
.tag-wrapper {
|
||||
background-color: var(--p-primary-contrast-color);
|
||||
border-radius: 6px;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.node-name-tag {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.status-tag-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.progress-preview-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
}
|
||||
</style>
|
||||
@@ -3,12 +3,9 @@
|
||||
v-if="showVueNodesBanner"
|
||||
class="pointer-events-auto relative w-full h-10 bg-gradient-to-r from-blue-600 to-blue-700 flex items-center justify-center px-4"
|
||||
>
|
||||
<div class="flex items-center text-sm">
|
||||
<div class="flex items-center">
|
||||
<i class="icon-[lucide--rocket]"></i>
|
||||
<span class="pl-2">{{ $t('vueNodesBanner.title') }}</span>
|
||||
<span class="pl-1.5 hidden md:inline">{{
|
||||
$t('vueNodesBanner.desc')
|
||||
}}</span>
|
||||
<span class="pl-2 text-sm">{{ $t('vueNodesBanner.message') }}</span>
|
||||
<Button
|
||||
class="cursor-pointer bg-transparent rounded h-7 px-3 border border-white text-white ml-4 text-xs"
|
||||
@click="handleTryItOut"
|
||||
|
||||
@@ -181,7 +181,6 @@ Composables for sidebar functionality:
|
||||
|------------|-------------|
|
||||
| `useModelLibrarySidebarTab` | Manages the model library sidebar tab |
|
||||
| `useNodeLibrarySidebarTab` | Manages the node library sidebar tab |
|
||||
| `useQueueSidebarTab` | Manages the queue sidebar tab |
|
||||
| `useWorkflowsSidebarTab` | Manages the workflows sidebar tab |
|
||||
|
||||
### Tree
|
||||
|
||||
@@ -3,6 +3,7 @@ import { shallowRef, watch } from 'vue'
|
||||
|
||||
import { useGraphNodeManager } from '@/composables/graph/useGraphNodeManager'
|
||||
import type { GraphNodeManager } from '@/composables/graph/useGraphNodeManager'
|
||||
import { useRenderModeSetting } from '@/composables/settings/useRenderModeSetting'
|
||||
import { useVueFeatureFlags } from '@/composables/useVueFeatureFlags'
|
||||
import { useVueNodesMigrationDismissed } from '@/composables/useVueNodesMigrationDismissed'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
@@ -10,7 +11,6 @@ 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'
|
||||
@@ -26,6 +26,11 @@ function useVueNodeLifecycleIndividual() {
|
||||
|
||||
let hasShownMigrationToast = false
|
||||
|
||||
useRenderModeSetting(
|
||||
{ setting: 'LiteGraph.Canvas.MinFontSizeForLOD', vue: 0, litegraph: 8 },
|
||||
shouldRenderVueNodes
|
||||
)
|
||||
|
||||
const initializeNodeManager = () => {
|
||||
// Use canvas graph if available (handles subgraph contexts), fallback to app graph
|
||||
const activeGraph = comfyApp.canvas?.graph
|
||||
@@ -39,10 +44,7 @@ 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], removeNodeTitleHeight(node.size[1])] as [
|
||||
number,
|
||||
number
|
||||
]
|
||||
size: [node.size[0], node.size[1]] as [number, number]
|
||||
}))
|
||||
layoutStore.initializeFromLiteGraph(nodes)
|
||||
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
import TopBarHeader from '@/components/maskeditor/dialog/TopBarHeader.vue'
|
||||
import MaskEditorContent from '@/components/maskeditor/MaskEditorContent.vue'
|
||||
|
||||
export function useMaskEditor() {
|
||||
const openMaskEditor = (node: LGraphNode) => {
|
||||
if (!node) {
|
||||
console.error('[MaskEditor] No node provided')
|
||||
return
|
||||
}
|
||||
|
||||
if (!node.imgs?.length && node.previewMediaType !== 'image') {
|
||||
console.error('[MaskEditor] Node has no images')
|
||||
return
|
||||
}
|
||||
|
||||
useDialogStore().showDialog({
|
||||
key: 'global-mask-editor',
|
||||
headerComponent: TopBarHeader,
|
||||
component: MaskEditorContent,
|
||||
props: {
|
||||
node
|
||||
},
|
||||
dialogComponentProps: {
|
||||
style: 'width: 90vw; height: 90vh;',
|
||||
modal: true,
|
||||
maximizable: true,
|
||||
closable: true,
|
||||
pt: {
|
||||
root: {
|
||||
class: 'mask-editor-dialog flex flex-col'
|
||||
},
|
||||
content: {
|
||||
class: 'flex flex-col min-h-0 flex-1 !p-0'
|
||||
},
|
||||
header: {
|
||||
class: '!p-2'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
openMaskEditor
|
||||
}
|
||||
}
|
||||
42
src/composables/settings/useRenderModeSetting.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import type { ComputedRef } from 'vue'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import type { Settings } from '@/schemas/apiSchema'
|
||||
|
||||
interface RenderModeSettingConfig<TSettingKey extends keyof Settings> {
|
||||
setting: TSettingKey
|
||||
vue: Settings[TSettingKey]
|
||||
litegraph: Settings[TSettingKey]
|
||||
}
|
||||
|
||||
export function useRenderModeSetting<TSettingKey extends keyof Settings>(
|
||||
config: RenderModeSettingConfig<TSettingKey>,
|
||||
isVueMode: ComputedRef<boolean>
|
||||
) {
|
||||
const settingStore = useSettingStore()
|
||||
const vueValue = ref(config.vue)
|
||||
const litegraphValue = ref(config.litegraph)
|
||||
const lastWasVue = ref<boolean | null>(null)
|
||||
|
||||
const load = async (vue: boolean) => {
|
||||
if (lastWasVue.value === vue) return
|
||||
|
||||
if (lastWasVue.value !== null) {
|
||||
const currentValue = settingStore.get(config.setting)
|
||||
if (lastWasVue.value) {
|
||||
vueValue.value = currentValue
|
||||
} else {
|
||||
litegraphValue.value = currentValue
|
||||
}
|
||||
}
|
||||
|
||||
await settingStore.set(
|
||||
config.setting,
|
||||
vue ? vueValue.value : litegraphValue.value
|
||||
)
|
||||
lastWasVue.value = vue
|
||||
}
|
||||
|
||||
watch(isVueMode, load, { immediate: true })
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { markRaw } from 'vue'
|
||||
|
||||
import QueueSidebarTab from '@/components/sidebar/tabs/QueueSidebarTab.vue'
|
||||
import { useQueuePendingTaskCountStore } from '@/stores/queueStore'
|
||||
import type { SidebarTabExtension } from '@/types/extensionTypes'
|
||||
|
||||
export const useQueueSidebarTab = (): SidebarTabExtension => {
|
||||
const queuePendingTaskCountStore = useQueuePendingTaskCountStore()
|
||||
return {
|
||||
id: 'queue',
|
||||
icon: 'pi pi-history',
|
||||
iconBadge: () => {
|
||||
const value = queuePendingTaskCountStore.count.toString()
|
||||
return value === '0' ? null : value
|
||||
},
|
||||
title: 'sideToolbar.queue',
|
||||
tooltip: 'sideToolbar.queue',
|
||||
label: 'sideToolbar.labels.queue',
|
||||
component: markRaw(QueueSidebarTab),
|
||||
type: 'vue'
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,7 @@ import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
|
||||
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
|
||||
import { useSearchBoxStore } from '@/stores/workspace/searchBoxStore'
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
import { useEagerExecutionStore } from '@/stores/eagerExecutionStore'
|
||||
import {
|
||||
getAllNonIoNodesInSubgraph,
|
||||
getExecutionIdsForSelectedNodes
|
||||
@@ -1225,6 +1226,25 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
icon: 'pi pi-database',
|
||||
label: 'toggle linear mode',
|
||||
function: () => (canvasStore.linearMode = !canvasStore.linearMode)
|
||||
},
|
||||
{
|
||||
id: 'Comfy.ToggleEagerExecution',
|
||||
icon: 'pi pi-bolt',
|
||||
label: 'Toggle Eager Execution',
|
||||
function: async () => {
|
||||
const eagerExecutionStore = useEagerExecutionStore()
|
||||
await eagerExecutionStore.toggle()
|
||||
toastStore.add({
|
||||
severity: 'info',
|
||||
summary: eagerExecutionStore.enabled
|
||||
? t('Eager execution enabled')
|
||||
: t('Eager execution disabled'),
|
||||
detail: eagerExecutionStore.enabled
|
||||
? t('Nodes will auto-execute when ancestors change')
|
||||
: t('Auto-execution disabled'),
|
||||
life: 3000
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@@ -30,12 +30,6 @@ export const CORE_KEYBINDINGS: Keybinding[] = [
|
||||
},
|
||||
commandId: 'Comfy.RefreshNodeDefinitions'
|
||||
},
|
||||
{
|
||||
combo: {
|
||||
key: 'q'
|
||||
},
|
||||
commandId: 'Workspace.ToggleSidebarTab.queue'
|
||||
},
|
||||
{
|
||||
combo: {
|
||||
key: 'w'
|
||||
|
||||
@@ -14,6 +14,7 @@ export const CORE_MENU_COMMANDS = [
|
||||
[['Edit'], ['Comfy.Undo', 'Comfy.Redo']],
|
||||
[['Edit'], ['Comfy.ClearWorkflow']],
|
||||
[['Edit'], ['Comfy.OpenClipspace']],
|
||||
[['Edit'], ['Comfy.RefreshNodeDefinitions']],
|
||||
[
|
||||
['Edit'],
|
||||
[
|
||||
|
||||
114
src/core/graph/widgets/dynamicWidgets.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import { transformInputSpecV1ToV2 } from '@/schemas/nodeDef/migration'
|
||||
import type { ComboInputSpec, InputSpec } from '@/schemas/nodeDefSchema'
|
||||
import { zDynamicComboInputSpec } from '@/schemas/nodeDefSchema'
|
||||
import { useLitegraphService } from '@/services/litegraphService'
|
||||
import { app } from '@/scripts/app'
|
||||
import type { ComfyApp } from '@/scripts/app'
|
||||
|
||||
function dynamicComboWidget(
|
||||
node: LGraphNode,
|
||||
inputName: string,
|
||||
untypedInputData: InputSpec,
|
||||
appArg: ComfyApp,
|
||||
widgetName?: string
|
||||
) {
|
||||
const { addNodeInput } = useLitegraphService()
|
||||
const parseResult = zDynamicComboInputSpec.safeParse(untypedInputData)
|
||||
if (!parseResult.success) throw new Error('invalid DynamicCombo spec')
|
||||
const inputData = parseResult.data
|
||||
const options = Object.fromEntries(
|
||||
inputData[1].options.map(({ key, inputs }) => [key, inputs])
|
||||
)
|
||||
const subSpec: ComboInputSpec = [Object.keys(options), {}]
|
||||
const { widget, minWidth, minHeight } = app.widgets['COMBO'](
|
||||
node,
|
||||
inputName,
|
||||
subSpec,
|
||||
appArg,
|
||||
widgetName
|
||||
)
|
||||
let currentDynamicNames: string[] = []
|
||||
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 = []
|
||||
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<string, InputSpec> | undefined, boolean][] = [
|
||||
[newSpec.required, false],
|
||||
[newSpec.optional, true]
|
||||
]
|
||||
for (const [inputType, isOptional] of inputTypes)
|
||||
for (const name in inputType ?? {}) {
|
||||
addNodeInput(
|
||||
node,
|
||||
transformInputSpecV1ToV2(inputType![name], {
|
||||
name,
|
||||
isOptional
|
||||
})
|
||||
)
|
||||
currentDynamicNames.push(name)
|
||||
}
|
||||
|
||||
const addedWidgets = node.widgets.splice(startingLength)
|
||||
node.widgets.splice(insertionPoint, 0, ...addedWidgets)
|
||||
if (inputInsertionPoint === 0) {
|
||||
if (
|
||||
addedWidgets.length === 0 &&
|
||||
node.inputs.length !== startingInputLength
|
||||
)
|
||||
//input is inputOnly, but lacks an insertion point
|
||||
throw new Error('Failed to find input socket for ' + widget.name)
|
||||
return
|
||||
}
|
||||
const addedInputs = node
|
||||
.spliceInputs(startingInputLength)
|
||||
.map((addedInput) => {
|
||||
const existingInput = node.inputs.findIndex(
|
||||
(existingInput) => addedInput.name === existingInput.name
|
||||
)
|
||||
return existingInput === -1
|
||||
? addedInput
|
||||
: node.spliceInputs(existingInput, 1)[0]
|
||||
})
|
||||
//assume existing inputs are in correct order
|
||||
node.spliceInputs(inputInsertionPoint, 0, ...addedInputs)
|
||||
node.size[1] = node.computeSize([...node.size])[1]
|
||||
}
|
||||
//A little hacky, but onConfigure won't work.
|
||||
//It fires too late and is overly disruptive
|
||||
let widgetValue = widget.value
|
||||
Object.defineProperty(widget, 'value', {
|
||||
get() {
|
||||
return widgetValue
|
||||
},
|
||||
set(value) {
|
||||
widgetValue = value
|
||||
updateWidgets(value)
|
||||
}
|
||||
})
|
||||
widget.value = widgetValue
|
||||
return { widget, minWidth, minHeight }
|
||||
}
|
||||
|
||||
export const dynamicWidgets = { COMFY_DYNAMICCOMBO_V3: dynamicComboWidget }
|
||||
@@ -5,9 +5,10 @@ import { app } from '@/scripts/app'
|
||||
import { ComfyApp } from '@/scripts/app'
|
||||
import { useMaskEditorStore } from '@/stores/maskEditorStore'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
import MaskEditorContent from '@/components/maskeditor/MaskEditorContent.vue'
|
||||
import TopBarHeader from '@/components/maskeditor/dialog/TopBarHeader.vue'
|
||||
import { MaskEditorDialogOld } from './maskEditorOld'
|
||||
import { ClipspaceDialog } from './clipspace'
|
||||
import { useMaskEditor } from '@/composables/maskeditor/useMaskEditor'
|
||||
|
||||
function openMaskEditor(node: LGraphNode): void {
|
||||
if (!node) {
|
||||
@@ -25,7 +26,32 @@ function openMaskEditor(node: LGraphNode): void {
|
||||
)
|
||||
|
||||
if (useNewEditor) {
|
||||
useMaskEditor().openMaskEditor(node)
|
||||
// Use new refactored editor
|
||||
useDialogStore().showDialog({
|
||||
key: 'global-mask-editor',
|
||||
headerComponent: TopBarHeader,
|
||||
component: MaskEditorContent,
|
||||
props: {
|
||||
node
|
||||
},
|
||||
dialogComponentProps: {
|
||||
style: 'width: 90vw; height: 90vh;',
|
||||
modal: true,
|
||||
maximizable: true,
|
||||
closable: true,
|
||||
pt: {
|
||||
root: {
|
||||
class: 'mask-editor-dialog flex flex-col'
|
||||
},
|
||||
content: {
|
||||
class: 'flex flex-col min-h-0 flex-1 !p-0'
|
||||
},
|
||||
header: {
|
||||
class: '!p-2'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// Use old editor
|
||||
ComfyApp.copyToClipspace(node)
|
||||
|
||||
@@ -79,7 +79,7 @@ export type {
|
||||
LGraphTriggerParam
|
||||
} from './types/graphTriggers'
|
||||
|
||||
export type RendererType = 'LG' | 'Vue'
|
||||
export type rendererType = 'LG' | 'Vue'
|
||||
|
||||
export interface LGraphState {
|
||||
lastGroupId: number
|
||||
@@ -106,7 +106,7 @@ export interface LGraphExtra extends Dictionary<unknown> {
|
||||
reroutes?: SerialisableReroute[]
|
||||
linkExtensions?: { id: number; parentId: number | undefined }[]
|
||||
ds?: DragAndScaleState
|
||||
workflowRendererVersion?: RendererType
|
||||
workflowRendererVersion?: rendererType
|
||||
}
|
||||
|
||||
export interface BaseLGraph {
|
||||
|
||||
@@ -7,7 +7,6 @@ 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 { CanvasPointer } from './CanvasPointer'
|
||||
import type { ContextMenu } from './ContextMenu'
|
||||
@@ -4044,25 +4043,16 @@ export class LGraphCanvas
|
||||
|
||||
// TODO: Report failures, i.e. `failedNodes`
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
})
|
||||
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
|
||||
}
|
||||
}))
|
||||
|
||||
if (newPositions.length) layoutStore.setSource(LayoutSource.Canvas)
|
||||
layoutStore.batchUpdateNodeBounds(newPositions)
|
||||
|
||||
this.selectItems(created)
|
||||
|
||||
@@ -835,6 +835,9 @@ export class LGraphNode
|
||||
for (const w of this.widgets) {
|
||||
if (!w) continue
|
||||
|
||||
const input = this.inputs.find((i) => i.widget?.name === w.name)
|
||||
if (input?.label) w.label = input.label
|
||||
|
||||
if (
|
||||
w.options?.property &&
|
||||
this.properties[w.options.property] != undefined
|
||||
@@ -845,15 +848,13 @@ export class LGraphNode
|
||||
}
|
||||
|
||||
if (info.widgets_values) {
|
||||
const widgetsWithValue = this.widgets.filter(
|
||||
(w) => w.serialize !== false
|
||||
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])
|
||||
)
|
||||
for (let i = 0; i < info.widgets_values.length; ++i) {
|
||||
const widget = widgetsWithValue[i]
|
||||
if (widget) {
|
||||
widget.value = info.widgets_values[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -881,7 +882,7 @@ export class LGraphNode
|
||||
|
||||
// special case for when there were errors
|
||||
if (this.constructor === LGraphNode && this.last_serialization)
|
||||
return this.last_serialization
|
||||
return { ...this.last_serialization, mode: o.mode, pos: o.pos }
|
||||
|
||||
if (this.inputs)
|
||||
o.inputs = this.inputs.map((input) => inputAsSerialisable(input))
|
||||
@@ -1649,6 +1650,19 @@ 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
|
||||
|
||||
@@ -1,43 +1,10 @@
|
||||
{
|
||||
"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": "فتح عارض ثلاثي الأبعاد (بيتا) للعقدة المحددة"
|
||||
},
|
||||
"Comfy_BrowseModelAssets": {
|
||||
"label": "تجريبي: تصفح أصول النماذج"
|
||||
},
|
||||
"Comfy_BrowseTemplates": {
|
||||
"label": "تصفح القوالب"
|
||||
},
|
||||
@@ -125,6 +92,9 @@
|
||||
"Comfy_Graph_ConvertToSubgraph": {
|
||||
"label": "تحويل التحديد إلى رسم فرعي"
|
||||
},
|
||||
"Comfy_Graph_EditSubgraphWidgets": {
|
||||
"label": "تحرير عناصر واجهة الرسم البياني الفرعي"
|
||||
},
|
||||
"Comfy_Graph_ExitSubgraph": {
|
||||
"label": "الخروج من الرسم البياني الفرعي"
|
||||
},
|
||||
@@ -134,6 +104,9 @@
|
||||
"Comfy_Graph_GroupSelectedNodes": {
|
||||
"label": "تجميع العقد المحددة"
|
||||
},
|
||||
"Comfy_Graph_ToggleWidgetPromotion": {
|
||||
"label": "تبديل ترقية عنصر الواجهة المحوم فوقه"
|
||||
},
|
||||
"Comfy_Graph_UnpackSubgraph": {
|
||||
"label": "فك التفرع الفرعي المحدد"
|
||||
},
|
||||
@@ -239,6 +212,9 @@
|
||||
"Comfy_ShowSettingsDialog": {
|
||||
"label": "عرض نافذة الإعدادات"
|
||||
},
|
||||
"Comfy_ToggleAssetAPI": {
|
||||
"label": "تجريبي: تمكين AssetAPI"
|
||||
},
|
||||
"Comfy_ToggleCanvasInfo": {
|
||||
"label": "أداء اللوحة"
|
||||
},
|
||||
@@ -257,6 +233,9 @@
|
||||
"Comfy_User_SignOut": {
|
||||
"label": "تسجيل الخروج"
|
||||
},
|
||||
"Experimental_ToggleVueNodes": {
|
||||
"label": "تجريبي: تمكين عقد Vue"
|
||||
},
|
||||
"Workspace_CloseWorkflow": {
|
||||
"label": "إغلاق سير العمل الحالي"
|
||||
},
|
||||
@@ -290,6 +269,10 @@
|
||||
"Workspace_ToggleFocusMode": {
|
||||
"label": "تبديل وضع التركيز"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_assets": {
|
||||
"label": "تبديل الشريط الجانبي للأصول",
|
||||
"tooltip": "الأصول"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_model-library": {
|
||||
"label": "تبديل الشريط الجانبي لمكتبة النماذج",
|
||||
"tooltip": "مكتبة النماذج"
|
||||
@@ -298,31 +281,8 @@
|
||||
"label": "تبديل الشريط الجانبي لمكتبة العقد",
|
||||
"tooltip": "مكتبة العقد"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_queue": {
|
||||
"label": "تبديل الشريط الجانبي لقائمة الانتظار",
|
||||
"tooltip": "قائمة الانتظار"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_workflows": {
|
||||
"label": "تبديل الشريط الجانبي لسير العمل",
|
||||
"tooltip": "سير العمل"
|
||||
},
|
||||
"Comfy_BrowseModelAssets": {
|
||||
"label": "تجريبي: تصفح أصول النماذج"
|
||||
},
|
||||
"Comfy_Graph_EditSubgraphWidgets": {
|
||||
"label": "تحرير عناصر واجهة الرسم البياني الفرعي"
|
||||
},
|
||||
"Comfy_Graph_ToggleWidgetPromotion": {
|
||||
"label": "تبديل ترقية عنصر الواجهة المحوم فوقه"
|
||||
},
|
||||
"Comfy_ToggleAssetAPI": {
|
||||
"label": "تجريبي: تمكين AssetAPI"
|
||||
},
|
||||
"Experimental_ToggleVueNodes": {
|
||||
"label": "تجريبي: تمكين عقد Vue"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_assets": {
|
||||
"label": "تبديل الشريط الجانبي للأصول",
|
||||
"tooltip": "الأصول"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1210,7 +1210,6 @@
|
||||
"Pin/Unpin Selected Nodes": "تثبيت/إلغاء تثبيت العقد المحددة",
|
||||
"Previous Opened Workflow": "سير العمل السابق المفتوح",
|
||||
"Publish": "نشر",
|
||||
"Queue Panel": "لوحة الانتظار",
|
||||
"Queue Prompt": "قائمة انتظار التعليمات",
|
||||
"Queue Prompt (Front)": "قائمة انتظار التعليمات (أمامي)",
|
||||
"Queue Selected Output Nodes": "قائمة انتظار عقد المخرجات المحددة",
|
||||
@@ -1670,18 +1669,6 @@
|
||||
},
|
||||
"openWorkflow": "فتح سير العمل من نظام الملفات المحلي",
|
||||
"queue": "قائمة الانتظار",
|
||||
"queueTab": {
|
||||
"backToAllTasks": "العودة إلى جميع المهام",
|
||||
"clearPendingTasks": "مسح المهام المعلقة",
|
||||
"containImagePreview": "ملء معاينة الصورة",
|
||||
"coverImagePreview": "تكييف معاينة الصورة",
|
||||
"filter": "تصفية النتائج",
|
||||
"filters": {
|
||||
"hideCached": "إخفاء المخزنة مؤقتًا",
|
||||
"hideCanceled": "إخفاء الملغاة"
|
||||
},
|
||||
"showFlatList": "عرض القائمة المسطحة"
|
||||
},
|
||||
"templates": "القوالب",
|
||||
"themeToggle": "تبديل المظهر",
|
||||
"workflowTab": {
|
||||
|
||||
@@ -221,6 +221,9 @@
|
||||
"Comfy_ToggleHelpCenter": {
|
||||
"label": "Help Center"
|
||||
},
|
||||
"Comfy_ToggleLinear": {
|
||||
"label": "toggle linear mode"
|
||||
},
|
||||
"Comfy_ToggleTheme": {
|
||||
"label": "Toggle Theme (Dark/Light)"
|
||||
},
|
||||
@@ -281,10 +284,6 @@
|
||||
"label": "Toggle Node Library Sidebar",
|
||||
"tooltip": "Node Library"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_queue": {
|
||||
"label": "Toggle Queue Sidebar",
|
||||
"tooltip": "Queue"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_workflows": {
|
||||
"label": "Toggle Workflows Sidebar",
|
||||
"tooltip": "Workflows"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"g": {
|
||||
"beta": "Beta",
|
||||
"user": "User",
|
||||
"currentUser": "Current user",
|
||||
"empty": "Empty",
|
||||
@@ -403,6 +402,7 @@
|
||||
"Copy Image": "Copy Image",
|
||||
"Save Image": "Save Image",
|
||||
"Rename": "Rename",
|
||||
"RenameWidget": "Rename Widget",
|
||||
"Copy": "Copy",
|
||||
"Duplicate": "Duplicate",
|
||||
"Paste": "Paste",
|
||||
@@ -505,6 +505,8 @@
|
||||
"cannotWrite": "Unable to write to the selected path",
|
||||
"insufficientFreeSpace": "Insufficient space - minimum free space",
|
||||
"isOneDrive": "OneDrive is not supported. Please install ComfyUI in another location.",
|
||||
"insideAppInstallDir": "This folder is inside the ComfyUI Desktop application bundle and will be deleted during updates. Choose a directory outside the install folder, such as Documents/ComfyUI.",
|
||||
"insideUpdaterCache": "This folder is inside the ComfyUI updater cache, which is cleared on every update. Select a different location for your data.",
|
||||
"nonDefaultDrive": "Please install ComfyUI on your system drive (eg. C:\\). Drives with different file systems may cause unpredicable issues. Models and other files can be stored on other drives after installation.",
|
||||
"parentMissing": "Path does not exist - create the containing directory first",
|
||||
"unhandledError": "Unknown error",
|
||||
@@ -680,18 +682,6 @@
|
||||
},
|
||||
"modelLibrary": "Model Library",
|
||||
"downloads": "Downloads",
|
||||
"queueTab": {
|
||||
"showFlatList": "Show Flat List",
|
||||
"backToAllTasks": "Back to All Tasks",
|
||||
"containImagePreview": "Fill Image Preview",
|
||||
"coverImagePreview": "Fit Image Preview",
|
||||
"clearPendingTasks": "Clear Pending Tasks",
|
||||
"filter": "Filter Outputs",
|
||||
"filters": {
|
||||
"hideCached": "Hide Cached",
|
||||
"hideCanceled": "Hide Canceled"
|
||||
}
|
||||
},
|
||||
"queueProgressOverlay": {
|
||||
"title": "Queue Progress",
|
||||
"total": "Total: {percent}",
|
||||
@@ -1112,6 +1102,7 @@
|
||||
"Experimental: Enable AssetAPI": "Experimental: Enable AssetAPI",
|
||||
"Canvas Performance": "Canvas Performance",
|
||||
"Help Center": "Help Center",
|
||||
"toggle linear mode": "toggle linear mode",
|
||||
"Toggle Theme (Dark/Light)": "Toggle Theme (Dark/Light)",
|
||||
"Undo": "Undo",
|
||||
"Open Sign In Dialog": "Open Sign In Dialog",
|
||||
@@ -1131,7 +1122,6 @@
|
||||
"Assets": "Assets",
|
||||
"Model Library": "Model Library",
|
||||
"Node Library": "Node Library",
|
||||
"Queue Panel": "Queue Panel",
|
||||
"Workflows": "Workflows"
|
||||
},
|
||||
"desktopMenu": {
|
||||
@@ -1413,6 +1403,7 @@
|
||||
"stable_cascade": "stable_cascade",
|
||||
"3d_models": "3d_models",
|
||||
"style_model": "style_model",
|
||||
"Topaz": "Topaz",
|
||||
"Tripo": "Tripo",
|
||||
"Veo": "Veo",
|
||||
"Vidu": "Vidu",
|
||||
@@ -1445,6 +1436,7 @@
|
||||
"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",
|
||||
@@ -1498,6 +1490,14 @@
|
||||
"taskFailed": "Task failed to run.",
|
||||
"cannotContinue": "Unable to continue - errors remain",
|
||||
"defaultDescription": "An error occurred while running a maintenance task."
|
||||
},
|
||||
"unsafeMigration": {
|
||||
"title": "Unsafe install location detected",
|
||||
"generic": "Your current ComfyUI base path is in a location that may be deleted or modified during updates. To avoid data loss, move it to a safe folder.",
|
||||
"appInstallDir": "Your base path is inside the ComfyUI Desktop application bundle. This folder may be deleted or overwritten during updates. Choose a directory outside the install folder, such as Documents/ComfyUI.",
|
||||
"updaterCache": "Your base path is inside the ComfyUI updater cache, which is cleared on each update. Choose a different location for your data.",
|
||||
"oneDrive": "Your base path is on OneDrive, which can cause sync issues and accidental data loss. Choose a local folder that is not managed by OneDrive.",
|
||||
"action": "Use the \"Base path\" maintenance task below to move ComfyUI to a safe location."
|
||||
}
|
||||
},
|
||||
"missingModelsDialog": {
|
||||
@@ -2188,8 +2188,7 @@
|
||||
}
|
||||
},
|
||||
"vueNodesBanner": {
|
||||
"title": "Introducing Nodes 2.0",
|
||||
"desc": "– More flexible workflows, powerful new widgets, built for extensibility",
|
||||
"message": "Introducing Nodes 2.0 – More flexible workflows, powerful new widgets, built for extensibility",
|
||||
"tryItOut": "Try it out"
|
||||
},
|
||||
"vueNodesMigration": {
|
||||
@@ -2199,10 +2198,6 @@
|
||||
"vueNodesMigrationMainMenu": {
|
||||
"message": "Switch back to Nodes 2.0 anytime from the main menu."
|
||||
},
|
||||
"linearMode": {
|
||||
"share": "Share",
|
||||
"openWorkflow": "Open Workflow"
|
||||
},
|
||||
"missingNodes": {
|
||||
"cloud": {
|
||||
"title": "These nodes aren't available on Comfy Cloud yet",
|
||||
@@ -2218,4 +2213,4 @@
|
||||
"replacementInstruction": "Install these nodes to run this workflow, or replace them with installed alternatives. Missing nodes are highlighted in red on the canvas."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2022,6 +2022,28 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"EmptyHunyuanVideo15Latent": {
|
||||
"display_name": "EmptyHunyuanVideo15Latent",
|
||||
"inputs": {
|
||||
"width": {
|
||||
"name": "width"
|
||||
},
|
||||
"height": {
|
||||
"name": "height"
|
||||
},
|
||||
"length": {
|
||||
"name": "length"
|
||||
},
|
||||
"batch_size": {
|
||||
"name": "batch_size"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"EmptyImage": {
|
||||
"display_name": "EmptyImage",
|
||||
"inputs": {
|
||||
@@ -2061,6 +2083,11 @@
|
||||
"name": "batch_size",
|
||||
"tooltip": "The number of latent images in the batch."
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"EmptyLatentImage": {
|
||||
@@ -2569,6 +2596,54 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"GeminiImage2Node": {
|
||||
"display_name": "Nano Banana Pro (Google Gemini Image)",
|
||||
"description": "Generate or edit images synchronously via Google Vertex API.",
|
||||
"inputs": {
|
||||
"prompt": {
|
||||
"name": "prompt",
|
||||
"tooltip": "Text prompt describing the image to generate or the edits to apply. Include any constraints, styles, or details the model should follow."
|
||||
},
|
||||
"model": {
|
||||
"name": "model"
|
||||
},
|
||||
"seed": {
|
||||
"name": "seed",
|
||||
"tooltip": "When the seed is fixed to a specific value, the model makes a best effort to provide the same response for repeated requests. Deterministic output isn't guaranteed. Also, changing the model or parameter settings, such as the temperature, can cause variations in the response even when you use the same seed value. By default, a random seed value is used."
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"resolution": {
|
||||
"name": "resolution",
|
||||
"tooltip": "Target output resolution. For 2K/4K the native Gemini upscaler is used."
|
||||
},
|
||||
"response_modalities": {
|
||||
"name": "response_modalities",
|
||||
"tooltip": "Choose 'IMAGE' for image-only output, or 'IMAGE+TEXT' to return both the generated image and a text response."
|
||||
},
|
||||
"images": {
|
||||
"name": "images",
|
||||
"tooltip": "Optional reference image(s). To include multiple images, use the Batch Images node (up to 14)."
|
||||
},
|
||||
"files": {
|
||||
"name": "files",
|
||||
"tooltip": "Optional file(s) to use as context for the model. Accepts inputs from the Gemini Generate Content Input Files node."
|
||||
},
|
||||
"control_after_generate": {
|
||||
"name": "control after generate"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
},
|
||||
"1": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"GeminiImageNode": {
|
||||
"display_name": "Nano Banana (Google Gemini Image)",
|
||||
"description": "Edit images synchronously via Google API.",
|
||||
@@ -2597,6 +2672,10 @@
|
||||
"name": "aspect_ratio",
|
||||
"tooltip": "Defaults to matching the output image size to that of your input image, or otherwise generates 1:1 squares."
|
||||
},
|
||||
"response_modalities": {
|
||||
"name": "response_modalities",
|
||||
"tooltip": "Choose 'IMAGE' for image-only output, or 'IMAGE+TEXT' to return both the generated image and a text response."
|
||||
},
|
||||
"control_after_generate": {
|
||||
"name": "control after generate"
|
||||
}
|
||||
@@ -2794,10 +2873,12 @@
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"name": "positive"
|
||||
"name": "positive",
|
||||
"tooltip": null
|
||||
},
|
||||
"1": {
|
||||
"name": "negative"
|
||||
"name": "negative",
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2819,10 +2900,12 @@
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"name": "positive"
|
||||
"name": "positive",
|
||||
"tooltip": null
|
||||
},
|
||||
"1": {
|
||||
"name": "negative"
|
||||
"name": "negative",
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2896,6 +2979,120 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"HunyuanVideo15ImageToVideo": {
|
||||
"display_name": "HunyuanVideo15ImageToVideo",
|
||||
"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"
|
||||
},
|
||||
"clip_vision_output": {
|
||||
"name": "clip_vision_output"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"model": {
|
||||
"name": "model"
|
||||
},
|
||||
"samples": {
|
||||
"name": "samples"
|
||||
},
|
||||
"upscale_method": {
|
||||
"name": "upscale_method"
|
||||
},
|
||||
"width": {
|
||||
"name": "width"
|
||||
},
|
||||
"height": {
|
||||
"name": "height"
|
||||
},
|
||||
"crop": {
|
||||
"name": "crop"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"HunyuanVideo15SuperResolution": {
|
||||
"display_name": "HunyuanVideo15SuperResolution",
|
||||
"inputs": {
|
||||
"positive": {
|
||||
"name": "positive"
|
||||
},
|
||||
"negative": {
|
||||
"name": "negative"
|
||||
},
|
||||
"latent": {
|
||||
"name": "latent"
|
||||
},
|
||||
"noise_augmentation": {
|
||||
"name": "noise_augmentation"
|
||||
},
|
||||
"vae": {
|
||||
"name": "vae"
|
||||
},
|
||||
"start_image": {
|
||||
"name": "start_image"
|
||||
},
|
||||
"clip_vision_output": {
|
||||
"name": "clip_vision_output"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"name": "positive",
|
||||
"tooltip": null
|
||||
},
|
||||
"1": {
|
||||
"name": "negative",
|
||||
"tooltip": null
|
||||
},
|
||||
"2": {
|
||||
"name": "latent",
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"HypernetworkLoader": {
|
||||
"display_name": "HypernetworkLoader",
|
||||
"inputs": {
|
||||
@@ -4557,6 +4754,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"LatentUpscaleModelLoader": {
|
||||
"display_name": "Load Latent Upscale Model",
|
||||
"inputs": {
|
||||
"model_name": {
|
||||
"name": "model_name"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"LazyCache": {
|
||||
"display_name": "LazyCache",
|
||||
"description": "A homebrew version of EasyCache - even 'easier' version of EasyCache to implement. Overall works worse than EasyCache, but better in some rare cases AND universal compatibility with everything in ComfyUI.",
|
||||
@@ -8841,7 +9051,7 @@
|
||||
}
|
||||
},
|
||||
"PreviewAny": {
|
||||
"display_name": "Preview Any",
|
||||
"display_name": "Preview as Text",
|
||||
"inputs": {
|
||||
"source": {
|
||||
"name": "source"
|
||||
@@ -11548,6 +11758,118 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"TopazImageEnhance": {
|
||||
"display_name": "Topaz Image Enhance",
|
||||
"description": "Industry-standard upscaling and image enhancement.",
|
||||
"inputs": {
|
||||
"model": {
|
||||
"name": "model"
|
||||
},
|
||||
"image": {
|
||||
"name": "image"
|
||||
},
|
||||
"prompt": {
|
||||
"name": "prompt",
|
||||
"tooltip": "Optional text prompt for creative upscaling guidance."
|
||||
},
|
||||
"subject_detection": {
|
||||
"name": "subject_detection"
|
||||
},
|
||||
"face_enhancement": {
|
||||
"name": "face_enhancement",
|
||||
"tooltip": "Enhance faces (if present) during processing."
|
||||
},
|
||||
"face_enhancement_creativity": {
|
||||
"name": "face_enhancement_creativity",
|
||||
"tooltip": "Set the creativity level for face enhancement."
|
||||
},
|
||||
"face_enhancement_strength": {
|
||||
"name": "face_enhancement_strength",
|
||||
"tooltip": "Controls how sharp enhanced faces are relative to the background."
|
||||
},
|
||||
"crop_to_fill": {
|
||||
"name": "crop_to_fill",
|
||||
"tooltip": "By default, the image is letterboxed when the output aspect ratio differs. Enable to crop the image to fill the output dimensions."
|
||||
},
|
||||
"output_width": {
|
||||
"name": "output_width",
|
||||
"tooltip": "Zero value means to calculate automatically (usually it will be original size or output_height if specified)."
|
||||
},
|
||||
"output_height": {
|
||||
"name": "output_height",
|
||||
"tooltip": "Zero value means to output in the same height as original or output width."
|
||||
},
|
||||
"creativity": {
|
||||
"name": "creativity"
|
||||
},
|
||||
"face_preservation": {
|
||||
"name": "face_preservation",
|
||||
"tooltip": "Preserve subjects' facial identity."
|
||||
},
|
||||
"color_preservation": {
|
||||
"name": "color_preservation",
|
||||
"tooltip": "Preserve the original colors."
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"TopazVideoEnhance": {
|
||||
"display_name": "Topaz Video Enhance",
|
||||
"description": "Breathe new life into video with powerful upscaling and recovery technology.",
|
||||
"inputs": {
|
||||
"video": {
|
||||
"name": "video"
|
||||
},
|
||||
"upscaler_enabled": {
|
||||
"name": "upscaler_enabled"
|
||||
},
|
||||
"upscaler_model": {
|
||||
"name": "upscaler_model"
|
||||
},
|
||||
"upscaler_resolution": {
|
||||
"name": "upscaler_resolution"
|
||||
},
|
||||
"upscaler_creativity": {
|
||||
"name": "upscaler_creativity",
|
||||
"tooltip": "Creativity level (applies only to Starlight (Astra) Creative)."
|
||||
},
|
||||
"interpolation_enabled": {
|
||||
"name": "interpolation_enabled"
|
||||
},
|
||||
"interpolation_model": {
|
||||
"name": "interpolation_model"
|
||||
},
|
||||
"interpolation_slowmo": {
|
||||
"name": "interpolation_slowmo",
|
||||
"tooltip": "Slow-motion factor applied to the input video. For example, 2 makes the output twice as slow and doubles the duration."
|
||||
},
|
||||
"interpolation_frame_rate": {
|
||||
"name": "interpolation_frame_rate",
|
||||
"tooltip": "Output frame rate."
|
||||
},
|
||||
"interpolation_duplicate": {
|
||||
"name": "interpolation_duplicate",
|
||||
"tooltip": "Analyze the input for duplicate frames and remove them."
|
||||
},
|
||||
"interpolation_duplicate_threshold": {
|
||||
"name": "interpolation_duplicate_threshold",
|
||||
"tooltip": "Detection sensitivity for duplicate frames."
|
||||
},
|
||||
"dynamic_compression_level": {
|
||||
"name": "dynamic_compression_level",
|
||||
"tooltip": "CQP level."
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"TorchCompileModel": {
|
||||
"display_name": "TorchCompileModel",
|
||||
"inputs": {
|
||||
@@ -12162,6 +12484,11 @@
|
||||
"octree_resolution": {
|
||||
"name": "octree_resolution"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"VAEDecodeTiled": {
|
||||
@@ -12586,6 +12913,11 @@
|
||||
"threshold": {
|
||||
"name": "threshold"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"VoxelToMeshBasic": {
|
||||
@@ -12597,6 +12929,11 @@
|
||||
"threshold": {
|
||||
"name": "threshold"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"VPScheduler": {
|
||||
|
||||
@@ -1,43 +1,10 @@
|
||||
{
|
||||
"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"
|
||||
},
|
||||
"Comfy_BrowseModelAssets": {
|
||||
"label": "Experimental: Explorar recursos de modelos"
|
||||
},
|
||||
"Comfy_BrowseTemplates": {
|
||||
"label": "Explorar plantillas"
|
||||
},
|
||||
@@ -125,6 +92,9 @@
|
||||
"Comfy_Graph_ConvertToSubgraph": {
|
||||
"label": "Convertir selección en subgrafo"
|
||||
},
|
||||
"Comfy_Graph_EditSubgraphWidgets": {
|
||||
"label": "Editar widgets de subgráficos"
|
||||
},
|
||||
"Comfy_Graph_ExitSubgraph": {
|
||||
"label": "Salir de subgrafo"
|
||||
},
|
||||
@@ -134,6 +104,9 @@
|
||||
"Comfy_Graph_GroupSelectedNodes": {
|
||||
"label": "Agrupar nodos seleccionados"
|
||||
},
|
||||
"Comfy_Graph_ToggleWidgetPromotion": {
|
||||
"label": "Alternar promoción del widget sobre el que se pasa el cursor"
|
||||
},
|
||||
"Comfy_Graph_UnpackSubgraph": {
|
||||
"label": "Desempaquetar el subgrafo seleccionado"
|
||||
},
|
||||
@@ -239,6 +212,9 @@
|
||||
"Comfy_ShowSettingsDialog": {
|
||||
"label": "Mostrar Diálogo de Configuraciones"
|
||||
},
|
||||
"Comfy_ToggleAssetAPI": {
|
||||
"label": "Experimental: Habilitar AssetAPI"
|
||||
},
|
||||
"Comfy_ToggleCanvasInfo": {
|
||||
"label": "Rendimiento del lienzo"
|
||||
},
|
||||
@@ -257,6 +233,9 @@
|
||||
"Comfy_User_SignOut": {
|
||||
"label": "Cerrar sesión"
|
||||
},
|
||||
"Experimental_ToggleVueNodes": {
|
||||
"label": "Experimental: Habilitar nodos Vue"
|
||||
},
|
||||
"Workspace_CloseWorkflow": {
|
||||
"label": "Cerrar Flujo de Trabajo Actual"
|
||||
},
|
||||
@@ -290,6 +269,10 @@
|
||||
"Workspace_ToggleFocusMode": {
|
||||
"label": "Alternar Modo de Enfoque"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_assets": {
|
||||
"label": "Alternar barra lateral de recursos",
|
||||
"tooltip": "Recursos"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_model-library": {
|
||||
"label": "Alternar Barra Lateral de Biblioteca de Modelos",
|
||||
"tooltip": "Biblioteca de Modelos"
|
||||
@@ -298,31 +281,8 @@
|
||||
"label": "Alternar Barra Lateral de Biblioteca de Nodos",
|
||||
"tooltip": "Biblioteca de Nodos"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_queue": {
|
||||
"label": "Alternar Barra Lateral de Cola",
|
||||
"tooltip": "Cola"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_workflows": {
|
||||
"label": "Alternar Barra Lateral de Flujos de Trabajo",
|
||||
"tooltip": "Flujos de Trabajo"
|
||||
},
|
||||
"Comfy_BrowseModelAssets": {
|
||||
"label": "Experimental: Explorar recursos de modelos"
|
||||
},
|
||||
"Comfy_Graph_EditSubgraphWidgets": {
|
||||
"label": "Editar widgets de subgráficos"
|
||||
},
|
||||
"Comfy_Graph_ToggleWidgetPromotion": {
|
||||
"label": "Alternar promoción del widget sobre el que se pasa el cursor"
|
||||
},
|
||||
"Comfy_ToggleAssetAPI": {
|
||||
"label": "Experimental: Habilitar AssetAPI"
|
||||
},
|
||||
"Experimental_ToggleVueNodes": {
|
||||
"label": "Experimental: Habilitar nodos Vue"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_assets": {
|
||||
"label": "Alternar barra lateral de recursos",
|
||||
"tooltip": "Recursos"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1210,7 +1210,6 @@
|
||||
"Pin/Unpin Selected Nodes": "Anclar/Desanclar nodos seleccionados",
|
||||
"Previous Opened Workflow": "Flujo de trabajo abierto anterior",
|
||||
"Publish": "Publicar",
|
||||
"Queue Panel": "Panel de Cola",
|
||||
"Queue Prompt": "Indicador de cola",
|
||||
"Queue Prompt (Front)": "Indicador de cola (Frente)",
|
||||
"Queue Selected Output Nodes": "Encolar nodos de salida seleccionados",
|
||||
@@ -1670,18 +1669,6 @@
|
||||
},
|
||||
"openWorkflow": "Abrir flujo de trabajo en el sistema de archivos local",
|
||||
"queue": "Cola",
|
||||
"queueTab": {
|
||||
"backToAllTasks": "Volver a todas las tareas",
|
||||
"clearPendingTasks": "Borrar tareas pendientes",
|
||||
"containImagePreview": "Llenar vista previa de la imagen",
|
||||
"coverImagePreview": "Ajustar vista previa de la imagen",
|
||||
"filter": "Filtrar salidas",
|
||||
"filters": {
|
||||
"hideCached": "Ocultar en caché",
|
||||
"hideCanceled": "Ocultar cancelados"
|
||||
},
|
||||
"showFlatList": "Mostrar lista plana"
|
||||
},
|
||||
"templates": "Plantillas",
|
||||
"themeToggle": "Cambiar tema",
|
||||
"workflowTab": {
|
||||
|
||||
@@ -1,43 +1,10 @@
|
||||
{
|
||||
"Comfy-Desktop_CheckForUpdates": {
|
||||
"label": "Vérifier les mises à jour"
|
||||
},
|
||||
"Comfy-Desktop_Folders_OpenCustomNodesFolder": {
|
||||
"label": "Ouvrir le dossier des nœuds personnalisés"
|
||||
},
|
||||
"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 de l'utilisateur 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é"
|
||||
},
|
||||
"Comfy_BrowseModelAssets": {
|
||||
"label": "Expérimental : Parcourir les ressources de modèles"
|
||||
},
|
||||
"Comfy_BrowseTemplates": {
|
||||
"label": "Parcourir les modèles"
|
||||
},
|
||||
@@ -125,6 +92,9 @@
|
||||
"Comfy_Graph_ConvertToSubgraph": {
|
||||
"label": "Convertir la sélection en sous-graphe"
|
||||
},
|
||||
"Comfy_Graph_EditSubgraphWidgets": {
|
||||
"label": "Modifier les widgets de sous-graphe"
|
||||
},
|
||||
"Comfy_Graph_ExitSubgraph": {
|
||||
"label": "Quitter le sous-graphe"
|
||||
},
|
||||
@@ -134,6 +104,9 @@
|
||||
"Comfy_Graph_GroupSelectedNodes": {
|
||||
"label": "Grouper les nœuds sélectionnés"
|
||||
},
|
||||
"Comfy_Graph_ToggleWidgetPromotion": {
|
||||
"label": "Activer/désactiver la promotion du widget survolé"
|
||||
},
|
||||
"Comfy_Graph_UnpackSubgraph": {
|
||||
"label": "Décompresser le sous-graphe sélectionné"
|
||||
},
|
||||
@@ -239,6 +212,9 @@
|
||||
"Comfy_ShowSettingsDialog": {
|
||||
"label": "Afficher la boîte de dialogue des paramètres"
|
||||
},
|
||||
"Comfy_ToggleAssetAPI": {
|
||||
"label": "Expérimental : Activer AssetAPI"
|
||||
},
|
||||
"Comfy_ToggleCanvasInfo": {
|
||||
"label": "Performance du canvas"
|
||||
},
|
||||
@@ -257,6 +233,9 @@
|
||||
"Comfy_User_SignOut": {
|
||||
"label": "Se déconnecter"
|
||||
},
|
||||
"Experimental_ToggleVueNodes": {
|
||||
"label": "Expérimental : Activer les nœuds Vue"
|
||||
},
|
||||
"Workspace_CloseWorkflow": {
|
||||
"label": "Fermer le flux de travail actuel"
|
||||
},
|
||||
@@ -290,6 +269,10 @@
|
||||
"Workspace_ToggleFocusMode": {
|
||||
"label": "Basculer le mode focus"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_assets": {
|
||||
"label": "Afficher/Masquer la barre latérale des ressources",
|
||||
"tooltip": "Ressources"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_model-library": {
|
||||
"label": "Basculer la barre latérale de la bibliothèque de modèles",
|
||||
"tooltip": "Bibliothèque de modèles"
|
||||
@@ -298,31 +281,8 @@
|
||||
"label": "Basculer la barre latérale de la bibliothèque de nœuds",
|
||||
"tooltip": "Bibliothèque de nœuds"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_queue": {
|
||||
"label": "Basculer la barre latérale de la file d'attente",
|
||||
"tooltip": "File d'attente"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_workflows": {
|
||||
"label": "Basculer la barre latérale des flux de travail",
|
||||
"tooltip": "Flux de travail"
|
||||
},
|
||||
"Comfy_BrowseModelAssets": {
|
||||
"label": "Expérimental : Parcourir les ressources de modèles"
|
||||
},
|
||||
"Comfy_Graph_EditSubgraphWidgets": {
|
||||
"label": "Modifier les widgets de sous-graphe"
|
||||
},
|
||||
"Comfy_Graph_ToggleWidgetPromotion": {
|
||||
"label": "Activer/désactiver la promotion du widget survolé"
|
||||
},
|
||||
"Comfy_ToggleAssetAPI": {
|
||||
"label": "Expérimental : Activer AssetAPI"
|
||||
},
|
||||
"Experimental_ToggleVueNodes": {
|
||||
"label": "Expérimental : Activer les nœuds Vue"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_assets": {
|
||||
"label": "Afficher/Masquer la barre latérale des ressources",
|
||||
"tooltip": "Ressources"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1210,7 +1210,6 @@
|
||||
"Pin/Unpin Selected Nodes": "Épingler/Désépingler les nœuds sélectionnés",
|
||||
"Previous Opened Workflow": "Flux de travail ouvert précédent",
|
||||
"Publish": "Publier",
|
||||
"Queue Panel": "Panneau de file d'attente",
|
||||
"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",
|
||||
@@ -1670,18 +1669,6 @@
|
||||
},
|
||||
"openWorkflow": "Ouvrir le flux de travail dans le système de fichiers local",
|
||||
"queue": "File d'attente",
|
||||
"queueTab": {
|
||||
"backToAllTasks": "Retour à toutes les tâches",
|
||||
"clearPendingTasks": "Effacer les tâches en attente",
|
||||
"containImagePreview": "Remplir l'aperçu de l'image",
|
||||
"coverImagePreview": "Adapter l'aperçu de l'image",
|
||||
"filter": "Filtrer les sorties",
|
||||
"filters": {
|
||||
"hideCached": "Masquer le cache",
|
||||
"hideCanceled": "Masquer les annulations"
|
||||
},
|
||||
"showFlatList": "Afficher la liste plate"
|
||||
},
|
||||
"templates": "Modèles",
|
||||
"themeToggle": "Basculer le thème",
|
||||
"workflowTab": {
|
||||
|
||||
@@ -1,43 +1,10 @@
|
||||
{
|
||||
"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": "DevToolsを開く"
|
||||
},
|
||||
"Comfy-Desktop_OpenUserGuide": {
|
||||
"label": "デスクトップユーザーガイド"
|
||||
},
|
||||
"Comfy-Desktop_Quit": {
|
||||
"label": "終了"
|
||||
},
|
||||
"Comfy-Desktop_Reinstall": {
|
||||
"label": "再インストール"
|
||||
},
|
||||
"Comfy-Desktop_Restart": {
|
||||
"label": "再起動"
|
||||
},
|
||||
"Comfy_3DViewer_Open3DViewer": {
|
||||
"label": "選択したノードの3Dビューアー(ベータ)を開く"
|
||||
},
|
||||
"Comfy_BrowseModelAssets": {
|
||||
"label": "実験的: モデルアセットを参照"
|
||||
},
|
||||
"Comfy_BrowseTemplates": {
|
||||
"label": "テンプレートを参照"
|
||||
},
|
||||
@@ -125,6 +92,9 @@
|
||||
"Comfy_Graph_ConvertToSubgraph": {
|
||||
"label": "選択範囲をサブグラフに変換"
|
||||
},
|
||||
"Comfy_Graph_EditSubgraphWidgets": {
|
||||
"label": "サブグラフウィジェットを編集"
|
||||
},
|
||||
"Comfy_Graph_ExitSubgraph": {
|
||||
"label": "サブグラフを終了"
|
||||
},
|
||||
@@ -134,6 +104,9 @@
|
||||
"Comfy_Graph_GroupSelectedNodes": {
|
||||
"label": "選択したノードをグループ化"
|
||||
},
|
||||
"Comfy_Graph_ToggleWidgetPromotion": {
|
||||
"label": "ホバー中のウィジェットの優先表示を切り替え"
|
||||
},
|
||||
"Comfy_Graph_UnpackSubgraph": {
|
||||
"label": "選択したサブグラフを展開"
|
||||
},
|
||||
@@ -239,6 +212,9 @@
|
||||
"Comfy_ShowSettingsDialog": {
|
||||
"label": "設定ダイアログを表示"
|
||||
},
|
||||
"Comfy_ToggleAssetAPI": {
|
||||
"label": "実験的: AssetAPIを有効化"
|
||||
},
|
||||
"Comfy_ToggleCanvasInfo": {
|
||||
"label": "キャンバスパフォーマンス"
|
||||
},
|
||||
@@ -257,6 +233,9 @@
|
||||
"Comfy_User_SignOut": {
|
||||
"label": "サインアウト"
|
||||
},
|
||||
"Experimental_ToggleVueNodes": {
|
||||
"label": "実験的: Vueノードを有効化"
|
||||
},
|
||||
"Workspace_CloseWorkflow": {
|
||||
"label": "現在のワークフローを閉じる"
|
||||
},
|
||||
@@ -290,6 +269,10 @@
|
||||
"Workspace_ToggleFocusMode": {
|
||||
"label": "フォーカスモードの切り替え"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_assets": {
|
||||
"label": "アセットサイドバーの表示切り替え",
|
||||
"tooltip": "アセット"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_model-library": {
|
||||
"label": "モデルライブラリサイドバーの切り替え",
|
||||
"tooltip": "モデルライブラリ"
|
||||
@@ -298,31 +281,8 @@
|
||||
"label": "ノードライブラリサイドバーの切り替え",
|
||||
"tooltip": "ノードライブラリ"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_queue": {
|
||||
"label": "キューサイドバーの切り替え",
|
||||
"tooltip": "キュー"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_workflows": {
|
||||
"label": "ワークフローサイドバーの切り替え",
|
||||
"tooltip": "ワークフロー"
|
||||
},
|
||||
"Comfy_BrowseModelAssets": {
|
||||
"label": "実験的: モデルアセットを参照"
|
||||
},
|
||||
"Comfy_Graph_EditSubgraphWidgets": {
|
||||
"label": "サブグラフウィジェットを編集"
|
||||
},
|
||||
"Comfy_Graph_ToggleWidgetPromotion": {
|
||||
"label": "ホバー中のウィジェットの優先表示を切り替え"
|
||||
},
|
||||
"Comfy_ToggleAssetAPI": {
|
||||
"label": "実験的: AssetAPIを有効化"
|
||||
},
|
||||
"Experimental_ToggleVueNodes": {
|
||||
"label": "実験的: Vueノードを有効化"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_assets": {
|
||||
"label": "アセットサイドバーの表示切り替え",
|
||||
"tooltip": "アセット"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1210,7 +1210,6 @@
|
||||
"Pin/Unpin Selected Nodes": "選択したノードのピン留め/ピン留め解除",
|
||||
"Previous Opened Workflow": "前に開いたワークフロー",
|
||||
"Publish": "公開",
|
||||
"Queue Panel": "キューパネル",
|
||||
"Queue Prompt": "キューのプロンプト",
|
||||
"Queue Prompt (Front)": "キューのプロンプト (前面)",
|
||||
"Queue Selected Output Nodes": "選択した出力ノードをキューに追加",
|
||||
@@ -1670,18 +1669,6 @@
|
||||
},
|
||||
"openWorkflow": "ローカルでワークフローを開く",
|
||||
"queue": "キュー",
|
||||
"queueTab": {
|
||||
"backToAllTasks": "すべてのタスクに戻る",
|
||||
"clearPendingTasks": "保留中のタスクをクリア",
|
||||
"containImagePreview": "画像プレビューを含める",
|
||||
"coverImagePreview": "画像プレビューに合わせる",
|
||||
"filter": "出力をフィルタ",
|
||||
"filters": {
|
||||
"hideCached": "キャッシュを非表示",
|
||||
"hideCanceled": "キャンセル済みを非表示"
|
||||
},
|
||||
"showFlatList": "フラットリストを表示"
|
||||
},
|
||||
"templates": "テンプレート",
|
||||
"themeToggle": "テーマを切り替え",
|
||||
"workflowTab": {
|
||||
|
||||
@@ -1,43 +1,10 @@
|
||||
{
|
||||
"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": "DevTools 열기"
|
||||
},
|
||||
"Comfy-Desktop_OpenUserGuide": {
|
||||
"label": "데스크톱 사용자 가이드"
|
||||
},
|
||||
"Comfy-Desktop_Quit": {
|
||||
"label": "종료"
|
||||
},
|
||||
"Comfy-Desktop_Reinstall": {
|
||||
"label": "재설치"
|
||||
},
|
||||
"Comfy-Desktop_Restart": {
|
||||
"label": "재시작"
|
||||
},
|
||||
"Comfy_3DViewer_Open3DViewer": {
|
||||
"label": "선택한 노드에 대해 3D 뷰어(베타) 열기"
|
||||
},
|
||||
"Comfy_BrowseModelAssets": {
|
||||
"label": "실험적: 모델 에셋 탐색"
|
||||
},
|
||||
"Comfy_BrowseTemplates": {
|
||||
"label": "템플릿 탐색"
|
||||
},
|
||||
@@ -125,6 +92,9 @@
|
||||
"Comfy_Graph_ConvertToSubgraph": {
|
||||
"label": "선택 영역을 서브그래프로 변환"
|
||||
},
|
||||
"Comfy_Graph_EditSubgraphWidgets": {
|
||||
"label": "서브그래프 위젯 편집"
|
||||
},
|
||||
"Comfy_Graph_ExitSubgraph": {
|
||||
"label": "서브그래프 나가기"
|
||||
},
|
||||
@@ -134,6 +104,9 @@
|
||||
"Comfy_Graph_GroupSelectedNodes": {
|
||||
"label": "선택한 노드 그룹화"
|
||||
},
|
||||
"Comfy_Graph_ToggleWidgetPromotion": {
|
||||
"label": "호버링된 위젯 프로모션 전환"
|
||||
},
|
||||
"Comfy_Graph_UnpackSubgraph": {
|
||||
"label": "선택한 서브그래프 묶음 풀기"
|
||||
},
|
||||
@@ -239,6 +212,9 @@
|
||||
"Comfy_ShowSettingsDialog": {
|
||||
"label": "설정 대화상자 보기"
|
||||
},
|
||||
"Comfy_ToggleAssetAPI": {
|
||||
"label": "실험적: AssetAPI 활성화"
|
||||
},
|
||||
"Comfy_ToggleCanvasInfo": {
|
||||
"label": "캔버스 성능"
|
||||
},
|
||||
@@ -257,6 +233,9 @@
|
||||
"Comfy_User_SignOut": {
|
||||
"label": "로그아웃"
|
||||
},
|
||||
"Experimental_ToggleVueNodes": {
|
||||
"label": "실험적: Vue 노드 활성화"
|
||||
},
|
||||
"Workspace_CloseWorkflow": {
|
||||
"label": "현재 워크플로 닫기"
|
||||
},
|
||||
@@ -290,6 +269,10 @@
|
||||
"Workspace_ToggleFocusMode": {
|
||||
"label": "포커스 모드 토글"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_assets": {
|
||||
"label": "에셋 사이드바 전환",
|
||||
"tooltip": "에셋"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_model-library": {
|
||||
"label": "모델 라이브러리 사이드바 토글",
|
||||
"tooltip": "모델 라이브러리"
|
||||
@@ -298,31 +281,8 @@
|
||||
"label": "노드 라이브러리 사이드바 토글",
|
||||
"tooltip": "노드 라이브러리"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_queue": {
|
||||
"label": "실행 큐 사이드바 토글",
|
||||
"tooltip": "실행 큐"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_workflows": {
|
||||
"label": "워크플로 사이드바 토글",
|
||||
"tooltip": "워크플로"
|
||||
},
|
||||
"Comfy_BrowseModelAssets": {
|
||||
"label": "실험적: 모델 에셋 탐색"
|
||||
},
|
||||
"Comfy_Graph_EditSubgraphWidgets": {
|
||||
"label": "서브그래프 위젯 편집"
|
||||
},
|
||||
"Comfy_Graph_ToggleWidgetPromotion": {
|
||||
"label": "호버링된 위젯 프로모션 전환"
|
||||
},
|
||||
"Comfy_ToggleAssetAPI": {
|
||||
"label": "실험적: AssetAPI 활성화"
|
||||
},
|
||||
"Experimental_ToggleVueNodes": {
|
||||
"label": "실험적: Vue 노드 활성화"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_assets": {
|
||||
"label": "에셋 사이드바 전환",
|
||||
"tooltip": "에셋"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1210,7 +1210,6 @@
|
||||
"Pin/Unpin Selected Nodes": "선택한 노드 고정/고정 해제",
|
||||
"Previous Opened Workflow": "이전 열린 워크플로",
|
||||
"Publish": "게시",
|
||||
"Queue Panel": "큐 패널",
|
||||
"Queue Prompt": "실행 대기열에 프롬프트 추가",
|
||||
"Queue Prompt (Front)": "실행 대기열 맨 앞에 프롬프트 추가",
|
||||
"Queue Selected Output Nodes": "선택한 출력 노드 대기열에 추가",
|
||||
@@ -1670,18 +1669,6 @@
|
||||
},
|
||||
"openWorkflow": "로컬 파일 시스템에서 워크플로 열기",
|
||||
"queue": "실행 대기열",
|
||||
"queueTab": {
|
||||
"backToAllTasks": "모든 작업으로 돌아가기",
|
||||
"clearPendingTasks": "보류 중인 작업 지우기",
|
||||
"containImagePreview": "이미지 미리보기 채우기",
|
||||
"coverImagePreview": "이미지 미리보기 맞추기",
|
||||
"filter": "출력 필터",
|
||||
"filters": {
|
||||
"hideCached": "캐시 숨기기",
|
||||
"hideCanceled": "취소된 작업 숨기기"
|
||||
},
|
||||
"showFlatList": "평면 목록 표시"
|
||||
},
|
||||
"templates": "템플릿",
|
||||
"themeToggle": "테마 전환",
|
||||
"workflowTab": {
|
||||
|
||||
@@ -1,43 +1,10 @@
|
||||
{
|
||||
"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-просмотрщик (бета) для выбранного узла"
|
||||
},
|
||||
"Comfy_BrowseModelAssets": {
|
||||
"label": "Экспериментально: Просмотр ресурсов моделей"
|
||||
},
|
||||
"Comfy_BrowseTemplates": {
|
||||
"label": "Просмотр шаблонов"
|
||||
},
|
||||
@@ -125,6 +92,9 @@
|
||||
"Comfy_Graph_ConvertToSubgraph": {
|
||||
"label": "Преобразовать выделенное в подграф"
|
||||
},
|
||||
"Comfy_Graph_EditSubgraphWidgets": {
|
||||
"label": "Редактировать виджеты подграфов"
|
||||
},
|
||||
"Comfy_Graph_ExitSubgraph": {
|
||||
"label": "Выйти из подграфа"
|
||||
},
|
||||
@@ -134,6 +104,9 @@
|
||||
"Comfy_Graph_GroupSelectedNodes": {
|
||||
"label": "Группировать выбранные ноды"
|
||||
},
|
||||
"Comfy_Graph_ToggleWidgetPromotion": {
|
||||
"label": "Переключить продвижение наведенного виджета"
|
||||
},
|
||||
"Comfy_Graph_UnpackSubgraph": {
|
||||
"label": "Распаковать выбранный подграф"
|
||||
},
|
||||
@@ -239,6 +212,9 @@
|
||||
"Comfy_ShowSettingsDialog": {
|
||||
"label": "Показать диалог настроек"
|
||||
},
|
||||
"Comfy_ToggleAssetAPI": {
|
||||
"label": "Экспериментально: Включить AssetAPI"
|
||||
},
|
||||
"Comfy_ToggleCanvasInfo": {
|
||||
"label": "Производительность холста"
|
||||
},
|
||||
@@ -257,6 +233,9 @@
|
||||
"Comfy_User_SignOut": {
|
||||
"label": "Выйти"
|
||||
},
|
||||
"Experimental_ToggleVueNodes": {
|
||||
"label": "Экспериментально: Включить Vue узлы"
|
||||
},
|
||||
"Workspace_CloseWorkflow": {
|
||||
"label": "Закрыть текущий рабочий процесс"
|
||||
},
|
||||
@@ -290,6 +269,10 @@
|
||||
"Workspace_ToggleFocusMode": {
|
||||
"label": "Переключить режим фокуса"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_assets": {
|
||||
"label": "Переключить боковую панель ресурсов",
|
||||
"tooltip": "Ресурсы"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_model-library": {
|
||||
"label": "Переключить боковую панель библиотеки моделей",
|
||||
"tooltip": "Библиотека моделей"
|
||||
@@ -298,31 +281,8 @@
|
||||
"label": "Переключить боковую панель библиотеки нод",
|
||||
"tooltip": "Библиотека нод"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_queue": {
|
||||
"label": "Переключить боковую панель очереди",
|
||||
"tooltip": "Очередь"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_workflows": {
|
||||
"label": "Переключить боковую панель рабочих процессов",
|
||||
"tooltip": "Рабочие процессы"
|
||||
},
|
||||
"Comfy_BrowseModelAssets": {
|
||||
"label": "Экспериментально: Просмотр ресурсов моделей"
|
||||
},
|
||||
"Comfy_Graph_EditSubgraphWidgets": {
|
||||
"label": "Редактировать виджеты подграфов"
|
||||
},
|
||||
"Comfy_Graph_ToggleWidgetPromotion": {
|
||||
"label": "Переключить продвижение наведенного виджета"
|
||||
},
|
||||
"Comfy_ToggleAssetAPI": {
|
||||
"label": "Экспериментально: Включить AssetAPI"
|
||||
},
|
||||
"Experimental_ToggleVueNodes": {
|
||||
"label": "Экспериментально: Включить Vue узлы"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_assets": {
|
||||
"label": "Переключить боковую панель ресурсов",
|
||||
"tooltip": "Ресурсы"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1210,7 +1210,6 @@
|
||||
"Pin/Unpin Selected Nodes": "Закрепить/открепить выбранные ноды",
|
||||
"Previous Opened Workflow": "Предыдущий открытый рабочий процесс",
|
||||
"Publish": "Опубликовать",
|
||||
"Queue Panel": "Панель очереди",
|
||||
"Queue Prompt": "Запрос в очереди",
|
||||
"Queue Prompt (Front)": "Запрос в очереди (спереди)",
|
||||
"Queue Selected Output Nodes": "Добавить выбранные выходные узлы в очередь",
|
||||
@@ -1670,18 +1669,6 @@
|
||||
},
|
||||
"openWorkflow": "Открыть рабочий процесс в локальной файловой системе",
|
||||
"queue": "Очередь",
|
||||
"queueTab": {
|
||||
"backToAllTasks": "Вернуться ко всем задачам",
|
||||
"clearPendingTasks": "Очистить отложенные задачи",
|
||||
"containImagePreview": "Предпросмотр заливающего изображения",
|
||||
"coverImagePreview": "Предпросмотр подходящего изображения",
|
||||
"filter": "Фильтровать выводы",
|
||||
"filters": {
|
||||
"hideCached": "Скрыть кэшированные",
|
||||
"hideCanceled": "Скрыть отмененные"
|
||||
},
|
||||
"showFlatList": "Показать плоский список"
|
||||
},
|
||||
"templates": "Шаблоны",
|
||||
"themeToggle": "Переключить тему",
|
||||
"workflowTab": {
|
||||
|
||||
@@ -1,43 +1,10 @@
|
||||
{
|
||||
"Comfy-Desktop_CheckForUpdates": {
|
||||
"label": "Güncellemeleri Kontrol Et"
|
||||
},
|
||||
"Comfy-Desktop_Folders_OpenCustomNodesFolder": {
|
||||
"label": "Özel Düğümler Klasörünü Aç"
|
||||
},
|
||||
"Comfy-Desktop_Folders_OpenInputsFolder": {
|
||||
"label": "Girişler Klasörünü Aç"
|
||||
},
|
||||
"Comfy-Desktop_Folders_OpenLogsFolder": {
|
||||
"label": "Kayıtlar Klasörünü Aç"
|
||||
},
|
||||
"Comfy-Desktop_Folders_OpenModelConfig": {
|
||||
"label": "extra_model_paths.yaml dosyasını aç"
|
||||
},
|
||||
"Comfy-Desktop_Folders_OpenModelsFolder": {
|
||||
"label": "Modeller Klasörünü Aç"
|
||||
},
|
||||
"Comfy-Desktop_Folders_OpenOutputsFolder": {
|
||||
"label": "Çıktılar 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ç"
|
||||
},
|
||||
"Comfy_BrowseModelAssets": {
|
||||
"label": "Deneysel: Model Varlıklarını Gözat"
|
||||
},
|
||||
"Comfy_BrowseTemplates": {
|
||||
"label": "Şablonlara Gözat"
|
||||
},
|
||||
@@ -125,6 +92,9 @@
|
||||
"Comfy_Graph_ConvertToSubgraph": {
|
||||
"label": "Seçimi Alt Grafiğe Dönüştür"
|
||||
},
|
||||
"Comfy_Graph_EditSubgraphWidgets": {
|
||||
"label": "Alt Grafik Bileşenlerini Düzenle"
|
||||
},
|
||||
"Comfy_Graph_ExitSubgraph": {
|
||||
"label": "Alt Grafikten Çık"
|
||||
},
|
||||
@@ -134,6 +104,9 @@
|
||||
"Comfy_Graph_GroupSelectedNodes": {
|
||||
"label": "Seçili Düğümleri Gruplandır"
|
||||
},
|
||||
"Comfy_Graph_ToggleWidgetPromotion": {
|
||||
"label": "Vurgulanan bileşenin önceliğini değiştir"
|
||||
},
|
||||
"Comfy_Graph_UnpackSubgraph": {
|
||||
"label": "Seçili Alt Grafiği Aç"
|
||||
},
|
||||
@@ -239,6 +212,9 @@
|
||||
"Comfy_ShowSettingsDialog": {
|
||||
"label": "Ayarlar İletişim Kutusunu Göster"
|
||||
},
|
||||
"Comfy_ToggleAssetAPI": {
|
||||
"label": "Deneysel: AssetAPI'yi Etkinleştir"
|
||||
},
|
||||
"Comfy_ToggleCanvasInfo": {
|
||||
"label": "Tuval Performansı"
|
||||
},
|
||||
@@ -257,6 +233,9 @@
|
||||
"Comfy_User_SignOut": {
|
||||
"label": "Çıkış Yap"
|
||||
},
|
||||
"Experimental_ToggleVueNodes": {
|
||||
"label": "Deneysel: Vue Düğümlerini Etkinleştir"
|
||||
},
|
||||
"Workspace_CloseWorkflow": {
|
||||
"label": "Mevcut İş Akışını Kapat"
|
||||
},
|
||||
@@ -290,6 +269,10 @@
|
||||
"Workspace_ToggleFocusMode": {
|
||||
"label": "Odak Modunu Aç/Kapat"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_assets": {
|
||||
"label": "Varlıklar Kenar Çubuğunu Aç/Kapat",
|
||||
"tooltip": "Varlıklar"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_model-library": {
|
||||
"label": "Model Kütüphanesi Kenar Çubuğunu Aç/Kapat",
|
||||
"tooltip": "Model Kütüphanesi"
|
||||
@@ -298,31 +281,8 @@
|
||||
"label": "Düğüm Kütüphanesi Kenar Çubuğunu Aç/Kapat",
|
||||
"tooltip": "Düğüm Kütüphanesi"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_queue": {
|
||||
"label": "Kuyruk Kenar Çubuğunu Aç/Kapat",
|
||||
"tooltip": "Kuyruk"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_workflows": {
|
||||
"label": "İş Akışları Kenar Çubuğunu Aç/Kapat",
|
||||
"tooltip": "İş Akışları"
|
||||
},
|
||||
"Comfy_BrowseModelAssets": {
|
||||
"label": "Deneysel: Model Varlıklarını Gözat"
|
||||
},
|
||||
"Comfy_Graph_EditSubgraphWidgets": {
|
||||
"label": "Alt Grafik Bileşenlerini Düzenle"
|
||||
},
|
||||
"Comfy_Graph_ToggleWidgetPromotion": {
|
||||
"label": "Vurgulanan bileşenin önceliğini değiştir"
|
||||
},
|
||||
"Comfy_ToggleAssetAPI": {
|
||||
"label": "Deneysel: AssetAPI'yi Etkinleştir"
|
||||
},
|
||||
"Experimental_ToggleVueNodes": {
|
||||
"label": "Deneysel: Vue Düğümlerini Etkinleştir"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_assets": {
|
||||
"label": "Varlıklar Kenar Çubuğunu Aç/Kapat",
|
||||
"tooltip": "Varlıklar"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1210,7 +1210,6 @@
|
||||
"Pin/Unpin Selected Nodes": "Seçili Düğümleri Sabitle/Kaldır",
|
||||
"Previous Opened Workflow": "Önceki Açılan İş Akışı",
|
||||
"Publish": "Yayınla",
|
||||
"Queue Panel": "Kuyruk Paneli",
|
||||
"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",
|
||||
@@ -1670,18 +1669,6 @@
|
||||
},
|
||||
"openWorkflow": "Yerel dosya sisteminde iş akışını aç",
|
||||
"queue": "Kuyruk",
|
||||
"queueTab": {
|
||||
"backToAllTasks": "Tüm Görevlere Geri Dön",
|
||||
"clearPendingTasks": "Bekleyen Görevleri Temizle",
|
||||
"containImagePreview": "Resim Önizlemesini Doldur",
|
||||
"coverImagePreview": "Resim Önizlemesine Sığdır",
|
||||
"filter": "Çıktıları Filtrele",
|
||||
"filters": {
|
||||
"hideCached": "Önbelleğe Alınanları Gizle",
|
||||
"hideCanceled": "İptal Edilenleri Gizle"
|
||||
},
|
||||
"showFlatList": "Düz Listeyi Göster"
|
||||
},
|
||||
"templates": "Şablonlar",
|
||||
"themeToggle": "Temayı Değiştir",
|
||||
"workflowTab": {
|
||||
|
||||
@@ -1,43 +1,10 @@
|
||||
{
|
||||
"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)"
|
||||
},
|
||||
"Comfy_BrowseModelAssets": {
|
||||
"label": "實驗性:瀏覽模型資源"
|
||||
},
|
||||
"Comfy_BrowseTemplates": {
|
||||
"label": "瀏覽範本"
|
||||
},
|
||||
@@ -125,6 +92,9 @@
|
||||
"Comfy_Graph_ConvertToSubgraph": {
|
||||
"label": "將選取內容轉換為子圖"
|
||||
},
|
||||
"Comfy_Graph_EditSubgraphWidgets": {
|
||||
"label": "編輯子圖表小工具"
|
||||
},
|
||||
"Comfy_Graph_ExitSubgraph": {
|
||||
"label": "離開子圖"
|
||||
},
|
||||
@@ -134,6 +104,9 @@
|
||||
"Comfy_Graph_GroupSelectedNodes": {
|
||||
"label": "群組所選節點"
|
||||
},
|
||||
"Comfy_Graph_ToggleWidgetPromotion": {
|
||||
"label": "切換懸停小工具的提升"
|
||||
},
|
||||
"Comfy_Graph_UnpackSubgraph": {
|
||||
"label": "解開所選子圖"
|
||||
},
|
||||
@@ -239,6 +212,9 @@
|
||||
"Comfy_ShowSettingsDialog": {
|
||||
"label": "顯示設定對話框"
|
||||
},
|
||||
"Comfy_ToggleAssetAPI": {
|
||||
"label": "實驗性:啟用 AssetAPI"
|
||||
},
|
||||
"Comfy_ToggleCanvasInfo": {
|
||||
"label": "畫布效能"
|
||||
},
|
||||
@@ -257,6 +233,9 @@
|
||||
"Comfy_User_SignOut": {
|
||||
"label": "登出"
|
||||
},
|
||||
"Experimental_ToggleVueNodes": {
|
||||
"label": "實驗性:啟用 Vue 節點"
|
||||
},
|
||||
"Workspace_CloseWorkflow": {
|
||||
"label": "關閉當前工作流程"
|
||||
},
|
||||
@@ -290,6 +269,10 @@
|
||||
"Workspace_ToggleFocusMode": {
|
||||
"label": "切換專注模式"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_assets": {
|
||||
"label": "切換資源側邊欄",
|
||||
"tooltip": "資源"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_model-library": {
|
||||
"label": "切換模型庫側邊欄",
|
||||
"tooltip": "模型庫"
|
||||
@@ -298,31 +281,8 @@
|
||||
"label": "切換節點庫側邊欄",
|
||||
"tooltip": "節點庫"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_queue": {
|
||||
"label": "切換佇列側邊欄",
|
||||
"tooltip": "佇列"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_workflows": {
|
||||
"label": "切換工作流程側邊欄",
|
||||
"tooltip": "工作流程"
|
||||
},
|
||||
"Comfy_BrowseModelAssets": {
|
||||
"label": "實驗性:瀏覽模型資源"
|
||||
},
|
||||
"Comfy_Graph_EditSubgraphWidgets": {
|
||||
"label": "編輯子圖表小工具"
|
||||
},
|
||||
"Comfy_Graph_ToggleWidgetPromotion": {
|
||||
"label": "切換懸停小工具的提升"
|
||||
},
|
||||
"Comfy_ToggleAssetAPI": {
|
||||
"label": "實驗性:啟用 AssetAPI"
|
||||
},
|
||||
"Experimental_ToggleVueNodes": {
|
||||
"label": "實驗性:啟用 Vue 節點"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_assets": {
|
||||
"label": "切換資源側邊欄",
|
||||
"tooltip": "資源"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1210,7 +1210,6 @@
|
||||
"Pin/Unpin Selected Nodes": "釘選/取消釘選選取節點",
|
||||
"Previous Opened Workflow": "上一個已開啟的工作流程",
|
||||
"Publish": "發佈",
|
||||
"Queue Panel": "佇列面板",
|
||||
"Queue Prompt": "加入提示至佇列",
|
||||
"Queue Prompt (Front)": "將提示加入佇列前端",
|
||||
"Queue Selected Output Nodes": "將選取的輸出節點加入佇列",
|
||||
@@ -1670,18 +1669,6 @@
|
||||
},
|
||||
"openWorkflow": "在本機檔案系統中開啟工作流程",
|
||||
"queue": "佇列",
|
||||
"queueTab": {
|
||||
"backToAllTasks": "返回所有任務",
|
||||
"clearPendingTasks": "清除待處理任務",
|
||||
"containImagePreview": "填滿圖片預覽",
|
||||
"coverImagePreview": "適合圖片預覽",
|
||||
"filter": "篩選輸出",
|
||||
"filters": {
|
||||
"hideCached": "隱藏快取",
|
||||
"hideCanceled": "隱藏已取消"
|
||||
},
|
||||
"showFlatList": "顯示平面清單"
|
||||
},
|
||||
"templates": "範本",
|
||||
"themeToggle": "切換主題",
|
||||
"workflowTab": {
|
||||
|
||||
@@ -1,43 +1,10 @@
|
||||
{
|
||||
"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 版)"
|
||||
},
|
||||
"Comfy_BrowseModelAssets": {
|
||||
"label": "实验性:浏览模型资源"
|
||||
},
|
||||
"Comfy_BrowseTemplates": {
|
||||
"label": "浏览模板"
|
||||
},
|
||||
@@ -125,6 +92,9 @@
|
||||
"Comfy_Graph_ConvertToSubgraph": {
|
||||
"label": "将选区转换为子图"
|
||||
},
|
||||
"Comfy_Graph_EditSubgraphWidgets": {
|
||||
"label": "编辑子图组件"
|
||||
},
|
||||
"Comfy_Graph_ExitSubgraph": {
|
||||
"label": "退出子图"
|
||||
},
|
||||
@@ -134,6 +104,9 @@
|
||||
"Comfy_Graph_GroupSelectedNodes": {
|
||||
"label": "添加框到选中节点"
|
||||
},
|
||||
"Comfy_Graph_ToggleWidgetPromotion": {
|
||||
"label": "切换悬停小部件的推广"
|
||||
},
|
||||
"Comfy_Graph_UnpackSubgraph": {
|
||||
"label": "解开所选子图"
|
||||
},
|
||||
@@ -239,6 +212,9 @@
|
||||
"Comfy_ShowSettingsDialog": {
|
||||
"label": "显示设置对话框"
|
||||
},
|
||||
"Comfy_ToggleAssetAPI": {
|
||||
"label": "实验性:启用 AssetAPI"
|
||||
},
|
||||
"Comfy_ToggleCanvasInfo": {
|
||||
"label": "画布性能"
|
||||
},
|
||||
@@ -257,6 +233,9 @@
|
||||
"Comfy_User_SignOut": {
|
||||
"label": "退出登录"
|
||||
},
|
||||
"Experimental_ToggleVueNodes": {
|
||||
"label": "实验性:启用 Vue 节点"
|
||||
},
|
||||
"Workspace_CloseWorkflow": {
|
||||
"label": "关闭当前工作流"
|
||||
},
|
||||
@@ -290,6 +269,10 @@
|
||||
"Workspace_ToggleFocusMode": {
|
||||
"label": "切换焦点模式"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_assets": {
|
||||
"label": "切换资产侧边栏",
|
||||
"tooltip": "资产"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_model-library": {
|
||||
"label": "切换模型库侧边栏",
|
||||
"tooltip": "模型库"
|
||||
@@ -298,31 +281,8 @@
|
||||
"label": "切换节点库侧边栏",
|
||||
"tooltip": "节点库"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_queue": {
|
||||
"label": "切换执行队列侧边栏",
|
||||
"tooltip": "执行队列"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_workflows": {
|
||||
"label": "切换工作流侧边栏",
|
||||
"tooltip": "工作流"
|
||||
},
|
||||
"Comfy_BrowseModelAssets": {
|
||||
"label": "实验性:浏览模型资源"
|
||||
},
|
||||
"Comfy_Graph_EditSubgraphWidgets": {
|
||||
"label": "编辑子图组件"
|
||||
},
|
||||
"Comfy_Graph_ToggleWidgetPromotion": {
|
||||
"label": "切换悬停小部件的推广"
|
||||
},
|
||||
"Comfy_ToggleAssetAPI": {
|
||||
"label": "实验性:启用 AssetAPI"
|
||||
},
|
||||
"Experimental_ToggleVueNodes": {
|
||||
"label": "实验性:启用 Vue 节点"
|
||||
},
|
||||
"Workspace_ToggleSidebarTab_assets": {
|
||||
"label": "切换资产侧边栏",
|
||||
"tooltip": "资产"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1210,7 +1210,6 @@
|
||||
"Pin/Unpin Selected Nodes": "固定/取消固定选定节点",
|
||||
"Previous Opened Workflow": "上一个打开的工作流",
|
||||
"Publish": "发布",
|
||||
"Queue Panel": "队列面板",
|
||||
"Queue Prompt": "执行提示词",
|
||||
"Queue Prompt (Front)": "执行提示词 (优先执行)",
|
||||
"Queue Selected Output Nodes": "将所选输出节点加入队列",
|
||||
@@ -1670,18 +1669,6 @@
|
||||
},
|
||||
"openWorkflow": "在本地文件系统中打开工作流",
|
||||
"queue": "队列",
|
||||
"queueTab": {
|
||||
"backToAllTasks": "返回",
|
||||
"clearPendingTasks": "清除待处理任务",
|
||||
"containImagePreview": "填充图像预览",
|
||||
"coverImagePreview": "适应图像预览",
|
||||
"filter": "过滤输出",
|
||||
"filters": {
|
||||
"hideCached": "隐藏缓存",
|
||||
"hideCanceled": "隐藏已取消"
|
||||
},
|
||||
"showFlatList": "平铺结果"
|
||||
},
|
||||
"templates": "模板",
|
||||
"themeToggle": "切换主题",
|
||||
"workflowTab": {
|
||||
|
||||
@@ -71,9 +71,10 @@
|
||||
</div>
|
||||
<div v-tooltip.top="$t('mediaAsset.actions.more')">
|
||||
<MoreButton
|
||||
ref="moreButtonRef"
|
||||
size="sm"
|
||||
@menu-opened="isMenuOpen = true"
|
||||
@menu-closed="isMenuOpen = false"
|
||||
@menu-opened="handleMenuOpened"
|
||||
@menu-closed="handleMenuClosed"
|
||||
@mouseenter="handleOverlayMouseEnter"
|
||||
@mouseleave="handleOverlayMouseLeave"
|
||||
>
|
||||
@@ -139,7 +140,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useElementHover } from '@vueuse/core'
|
||||
import { useElementHover, whenever } from '@vueuse/core'
|
||||
import { computed, defineAsyncComponent, provide, ref, toRef } from 'vue'
|
||||
|
||||
import IconButton from '@/components/button/IconButton.vue'
|
||||
@@ -189,7 +190,8 @@ const {
|
||||
selected,
|
||||
showOutputCount,
|
||||
outputCount,
|
||||
showDeleteButton
|
||||
showDeleteButton,
|
||||
openPopoverId
|
||||
} = defineProps<{
|
||||
asset?: AssetItem
|
||||
loading?: boolean
|
||||
@@ -197,15 +199,19 @@ const {
|
||||
showOutputCount?: boolean
|
||||
outputCount?: number
|
||||
showDeleteButton?: boolean
|
||||
openPopoverId?: string | null
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
zoom: [asset: AssetItem]
|
||||
'output-count-click': []
|
||||
'asset-deleted': []
|
||||
'popover-opened': []
|
||||
'popover-closed': []
|
||||
}>()
|
||||
|
||||
const cardContainerRef = ref<HTMLElement>()
|
||||
const moreButtonRef = ref<InstanceType<typeof MoreButton>>()
|
||||
|
||||
const isVideoPlaying = ref(false)
|
||||
const isMenuOpen = ref(false)
|
||||
@@ -339,4 +345,22 @@ const handleOutputCountClick = () => {
|
||||
const handleAssetDelete = () => {
|
||||
emit('asset-deleted')
|
||||
}
|
||||
|
||||
const handleMenuOpened = () => {
|
||||
isMenuOpen.value = true
|
||||
emit('popover-opened')
|
||||
}
|
||||
|
||||
const handleMenuClosed = () => {
|
||||
isMenuOpen.value = false
|
||||
emit('popover-closed')
|
||||
}
|
||||
|
||||
// Close this popover when another opens
|
||||
whenever(
|
||||
() => openPopoverId && openPopoverId !== asset?.id && isMenuOpen.value,
|
||||
() => {
|
||||
moreButtonRef.value?.hide()
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import type { TelemetryEventName } from '@/platform/telemetry/types'
|
||||
|
||||
/**
|
||||
* Server health alert configuration from the backend
|
||||
*/
|
||||
@@ -33,5 +31,4 @@ export type RemoteConfig = {
|
||||
comfy_api_base_url?: string
|
||||
comfy_platform_base_url?: string
|
||||
firebase_config?: FirebaseRuntimeConfig
|
||||
telemetry_disabled_events?: TelemetryEventName[]
|
||||
}
|
||||
|
||||
@@ -8,11 +8,9 @@ import {
|
||||
} from '@/platform/settings/settingStore'
|
||||
import type { ISettingGroup, SettingParams } from '@/platform/settings/types'
|
||||
import { normalizeI18nKey } from '@/utils/formatUtil'
|
||||
import { useVueFeatureFlags } from '@/composables/useVueFeatureFlags'
|
||||
|
||||
export function useSettingSearch() {
|
||||
const settingStore = useSettingStore()
|
||||
const { shouldRenderVueNodes } = useVueFeatureFlags()
|
||||
|
||||
const searchQuery = ref<string>('')
|
||||
const filteredSettingIds = ref<string[]>([])
|
||||
@@ -56,11 +54,7 @@ export function useSettingSearch() {
|
||||
const allSettings = Object.values(settingStore.settingsById)
|
||||
const filteredSettings = allSettings.filter((setting) => {
|
||||
// Filter out hidden and deprecated settings, just like in normal settings tree
|
||||
if (
|
||||
setting.type === 'hidden' ||
|
||||
setting.deprecated ||
|
||||
(shouldRenderVueNodes.value && setting.hideInVueNodes)
|
||||
) {
|
||||
if (setting.type === 'hidden' || setting.deprecated) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import type { SettingParams } from '@/platform/settings/types'
|
||||
import { isElectron } from '@/utils/envUtil'
|
||||
import { normalizeI18nKey } from '@/utils/formatUtil'
|
||||
import { buildTree } from '@/utils/treeUtil'
|
||||
import { useVueFeatureFlags } from '@/composables/useVueFeatureFlags'
|
||||
|
||||
interface SettingPanelItem {
|
||||
node: SettingTreeNode
|
||||
@@ -32,14 +31,10 @@ export function useSettingUI(
|
||||
const settingStore = useSettingStore()
|
||||
const activeCategory = ref<SettingTreeNode | null>(null)
|
||||
|
||||
const { shouldRenderVueNodes } = useVueFeatureFlags()
|
||||
|
||||
const settingRoot = computed<SettingTreeNode>(() => {
|
||||
const root = buildTree(
|
||||
Object.values(settingStore.settingsById).filter(
|
||||
(setting: SettingParams) =>
|
||||
setting.type !== 'hidden' &&
|
||||
!(shouldRenderVueNodes.value && setting.hideInVueNodes)
|
||||
(setting: SettingParams) => setting.type !== 'hidden'
|
||||
),
|
||||
(setting: SettingParams) => setting.category || setting.id.split('.')
|
||||
)
|
||||
|
||||
@@ -919,8 +919,7 @@ export const CORE_SETTINGS: SettingParams[] = [
|
||||
step: 1
|
||||
},
|
||||
defaultValue: 8,
|
||||
versionAdded: '1.26.7',
|
||||
hideInVueNodes: true
|
||||
versionAdded: '1.26.7'
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Canvas.SelectionToolbox',
|
||||
|
||||
@@ -47,7 +47,6 @@ export interface SettingParams<TValue = unknown> extends FormItem {
|
||||
// sortOrder for sorting settings within a group. Higher values appear first.
|
||||
// Default is 0 if not specified.
|
||||
sortOrder?: number
|
||||
hideInVueNodes?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { OverridedMixpanel } from 'mixpanel-browser'
|
||||
import { watch } from 'vue'
|
||||
|
||||
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
|
||||
import {
|
||||
@@ -42,30 +41,9 @@ import type {
|
||||
WorkflowCreatedMetadata,
|
||||
WorkflowImportMetadata
|
||||
} from '../../types'
|
||||
import { remoteConfig } from '@/platform/remoteConfig/remoteConfig'
|
||||
import type { RemoteConfig } from '@/platform/remoteConfig/types'
|
||||
import { TelemetryEvents } from '../../types'
|
||||
import { normalizeSurveyResponses } from '../../utils/surveyNormalization'
|
||||
|
||||
const DEFAULT_DISABLED_EVENTS = [
|
||||
TelemetryEvents.WORKFLOW_OPENED,
|
||||
TelemetryEvents.PAGE_VISIBILITY_CHANGED,
|
||||
TelemetryEvents.TAB_COUNT_TRACKING,
|
||||
TelemetryEvents.NODE_SEARCH,
|
||||
TelemetryEvents.NODE_SEARCH_RESULT_SELECTED,
|
||||
TelemetryEvents.TEMPLATE_FILTER_CHANGED,
|
||||
TelemetryEvents.SETTING_CHANGED,
|
||||
TelemetryEvents.HELP_CENTER_OPENED,
|
||||
TelemetryEvents.HELP_RESOURCE_CLICKED,
|
||||
TelemetryEvents.HELP_CENTER_CLOSED,
|
||||
TelemetryEvents.WORKFLOW_CREATED,
|
||||
TelemetryEvents.UI_BUTTON_CLICKED
|
||||
] as const satisfies TelemetryEventName[]
|
||||
|
||||
const TELEMETRY_EVENT_SET = new Set<TelemetryEventName>(
|
||||
Object.values(TelemetryEvents) as TelemetryEventName[]
|
||||
)
|
||||
|
||||
interface QueuedEvent {
|
||||
eventName: TelemetryEventName
|
||||
properties?: TelemetryEventProperties
|
||||
@@ -89,19 +67,8 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
|
||||
private eventQueue: QueuedEvent[] = []
|
||||
private isInitialized = false
|
||||
private lastTriggerSource: ExecutionTriggerSource | undefined
|
||||
private disabledEvents = new Set<TelemetryEventName>(DEFAULT_DISABLED_EVENTS)
|
||||
|
||||
constructor() {
|
||||
this.configureDisabledEvents(
|
||||
(window.__CONFIG__ as Partial<RemoteConfig> | undefined) ?? null
|
||||
)
|
||||
watch(
|
||||
remoteConfig,
|
||||
(config) => {
|
||||
this.configureDisabledEvents(config)
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
const token = window.__CONFIG__?.mixpanel_token
|
||||
|
||||
if (token) {
|
||||
@@ -164,10 +131,6 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.disabledEvents.has(eventName)) {
|
||||
return
|
||||
}
|
||||
|
||||
const event: QueuedEvent = { eventName, properties }
|
||||
|
||||
if (this.isInitialized && this.mixpanel) {
|
||||
@@ -183,27 +146,6 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private configureDisabledEvents(config: Partial<RemoteConfig> | null): void {
|
||||
const disabledSource =
|
||||
config?.telemetry_disabled_events ?? DEFAULT_DISABLED_EVENTS
|
||||
|
||||
this.disabledEvents = this.buildEventSet(disabledSource)
|
||||
}
|
||||
|
||||
private buildEventSet(values: TelemetryEventName[]): Set<TelemetryEventName> {
|
||||
return new Set(
|
||||
values.filter((value) => {
|
||||
const isValid = TELEMETRY_EVENT_SET.has(value)
|
||||
if (!isValid && import.meta.env.DEV) {
|
||||
console.warn(
|
||||
`Unknown telemetry event name in disabled list: ${value}`
|
||||
)
|
||||
}
|
||||
return isValid
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
trackSignupOpened(): void {
|
||||
this.trackEvent(TelemetryEvents.USER_SIGN_UP_OPENED)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import { clearPreservedQuery } from '@/platform/navigation/preservedQueryManager'
|
||||
import { PRESERVED_QUERY_NAMESPACES } from '@/platform/navigation/preservedQueryNamespaces'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
|
||||
import { useTemplateWorkflows } from './useTemplateWorkflows'
|
||||
|
||||
@@ -14,10 +13,9 @@ import { useTemplateWorkflows } from './useTemplateWorkflows'
|
||||
* Supports URLs like:
|
||||
* - /?template=flux_simple (loads with default source)
|
||||
* - /?template=flux_simple&source=custom (loads from custom source)
|
||||
* - /?template=flux_simple&mode=linear (loads template in linear mode)
|
||||
*
|
||||
* Input validation:
|
||||
* - Template, source, and mode parameters must match: ^[a-zA-Z0-9_-]+$
|
||||
* - Template and source parameters must match: ^[a-zA-Z0-9_-]+$
|
||||
* - Invalid formats are rejected with console warnings
|
||||
*/
|
||||
export function useTemplateUrlLoader() {
|
||||
@@ -26,10 +24,7 @@ export function useTemplateUrlLoader() {
|
||||
const { t } = useI18n()
|
||||
const toast = useToast()
|
||||
const templateWorkflows = useTemplateWorkflows()
|
||||
const canvasStore = useCanvasStore()
|
||||
const TEMPLATE_NAMESPACE = PRESERVED_QUERY_NAMESPACES.TEMPLATE
|
||||
const SUPPORTED_MODES = ['linear'] as const
|
||||
type SupportedMode = (typeof SUPPORTED_MODES)[number]
|
||||
|
||||
/**
|
||||
* Validates parameter format to prevent path traversal and injection attacks
|
||||
@@ -39,20 +34,12 @@ export function useTemplateUrlLoader() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if a value is a supported mode
|
||||
*/
|
||||
const isSupportedMode = (mode: string): mode is SupportedMode => {
|
||||
return SUPPORTED_MODES.includes(mode as SupportedMode)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes template, source, and mode parameters from URL
|
||||
* Removes template and source parameters from URL
|
||||
*/
|
||||
const cleanupUrlParams = () => {
|
||||
const newQuery = { ...route.query }
|
||||
delete newQuery.template
|
||||
delete newQuery.source
|
||||
delete newQuery.mode
|
||||
void router.replace({ query: newQuery })
|
||||
}
|
||||
|
||||
@@ -83,24 +70,6 @@ export function useTemplateUrlLoader() {
|
||||
return
|
||||
}
|
||||
|
||||
const modeParam = route.query.mode as string | undefined
|
||||
|
||||
if (
|
||||
modeParam &&
|
||||
(typeof modeParam !== 'string' || !isValidParameter(modeParam))
|
||||
) {
|
||||
console.warn(
|
||||
`[useTemplateUrlLoader] Invalid mode parameter format: ${modeParam}`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (modeParam && !isSupportedMode(modeParam)) {
|
||||
console.warn(
|
||||
`[useTemplateUrlLoader] Unsupported mode parameter: ${modeParam}. Supported modes: ${SUPPORTED_MODES.join(', ')}`
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
await templateWorkflows.loadTemplates()
|
||||
|
||||
@@ -118,9 +87,6 @@ export function useTemplateUrlLoader() {
|
||||
}),
|
||||
life: 3000
|
||||
})
|
||||
} else if (modeParam === 'linear') {
|
||||
// Set linear mode after successful template load
|
||||
canvasStore.linearMode = true
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
|
||||
@@ -29,6 +29,12 @@ vi.mock('@/renderer/core/layout/transform/useTransformState', () => {
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/renderer/extensions/vueNodes/lod/useLOD', () => ({
|
||||
useLOD: vi.fn(() => ({
|
||||
isLOD: false
|
||||
}))
|
||||
}))
|
||||
|
||||
function createMockCanvas(): LGraphCanvas {
|
||||
return {
|
||||
canvas: {
|
||||
|
||||
@@ -9,8 +9,6 @@ import { computed, customRef, ref } from 'vue'
|
||||
import type { ComputedRef, Ref } from 'vue'
|
||||
import * as Y from 'yjs'
|
||||
|
||||
import { removeNodeTitleHeight } from '@/renderer/core/layout/utils/nodeSizeUtil'
|
||||
|
||||
import { ACTOR_CONFIG } from '@/renderer/core/layout/constants'
|
||||
import { LayoutSource } from '@/renderer/core/layout/types'
|
||||
import type {
|
||||
@@ -1416,8 +1414,8 @@ class LayoutStoreImpl implements LayoutStore {
|
||||
batchUpdateNodeBounds(updates: NodeBoundsUpdate[]): void {
|
||||
if (updates.length === 0) return
|
||||
|
||||
// Set source to Vue for these DOM-driven updates
|
||||
const originalSource = this.currentSource
|
||||
const shouldNormalizeHeights = originalSource === LayoutSource.DOM
|
||||
this.currentSource = LayoutSource.Vue
|
||||
|
||||
const nodeIds: NodeId[] = []
|
||||
@@ -1428,15 +1426,8 @@ class LayoutStoreImpl implements LayoutStore {
|
||||
if (!ynode) continue
|
||||
const currentLayout = yNodeToLayout(ynode)
|
||||
|
||||
const normalizedBounds = shouldNormalizeHeights
|
||||
? {
|
||||
...bounds,
|
||||
height: removeNodeTitleHeight(bounds.height)
|
||||
}
|
||||
: bounds
|
||||
|
||||
boundsRecord[nodeId] = {
|
||||
bounds: normalizedBounds,
|
||||
bounds,
|
||||
previousBounds: currentLayout.bounds
|
||||
}
|
||||
nodeIds.push(nodeId)
|
||||
|
||||
@@ -8,7 +8,6 @@ import { onUnmounted, ref } from 'vue'
|
||||
|
||||
import type { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
|
||||
import { addNodeTitleHeight } from '@/renderer/core/layout/utils/nodeSizeUtil'
|
||||
|
||||
/**
|
||||
* Composable for syncing LiteGraph with the Layout system
|
||||
@@ -44,13 +43,12 @@ export function useLayoutSync() {
|
||||
liteNode.pos[1] = layout.position.y
|
||||
}
|
||||
|
||||
const targetHeight = addNodeTitleHeight(layout.size.height)
|
||||
if (
|
||||
liteNode.size[0] !== layout.size.width ||
|
||||
liteNode.size[1] !== targetHeight
|
||||
liteNode.size[1] !== layout.size.height
|
||||
) {
|
||||
// Use setSize() to trigger onResize callback
|
||||
liteNode.setSize([layout.size.width, targetHeight])
|
||||
liteNode.setSize([layout.size.width, layout.size.height])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
:class="
|
||||
cn(
|
||||
'absolute inset-0 w-full h-full pointer-events-none',
|
||||
isInteracting ? 'transform-pane--interacting' : 'will-change-auto'
|
||||
isInteracting ? 'transform-pane--interacting' : 'will-change-auto',
|
||||
isLOD && 'isLOD'
|
||||
)
|
||||
"
|
||||
:style="transformStyle"
|
||||
@@ -21,6 +22,7 @@ import { computed } from 'vue'
|
||||
import type { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
|
||||
import { useTransformSettling } from '@/renderer/core/layout/transform/useTransformSettling'
|
||||
import { useTransformState } from '@/renderer/core/layout/transform/useTransformState'
|
||||
import { useLOD } from '@/renderer/extensions/vueNodes/lod/useLOD'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
interface TransformPaneProps {
|
||||
@@ -29,7 +31,9 @@ interface TransformPaneProps {
|
||||
|
||||
const props = defineProps<TransformPaneProps>()
|
||||
|
||||
const { transformStyle, syncWithCanvas } = useTransformState()
|
||||
const { camera, transformStyle, syncWithCanvas } = useTransformState()
|
||||
|
||||
const { isLOD } = useLOD(camera)
|
||||
|
||||
const canvasElement = computed(() => props.canvas?.canvas)
|
||||
const { isTransforming: isInteracting } = useTransformSettling(canvasElement, {
|
||||
|
||||