Compare commits
20 Commits
v1.41.20
...
media/refa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0447f2227d | ||
|
|
76fd80aa98 | ||
|
|
63c36d3f2f | ||
|
|
892a9cf2c5 | ||
|
|
308c22efc6 | ||
|
|
5728d240da | ||
|
|
acf2f4280c | ||
|
|
7ad6994d01 | ||
|
|
2829f78579 | ||
|
|
c4156d7059 | ||
|
|
725a0a2b89 | ||
|
|
8a5bcde168 | ||
|
|
83ffaf30c8 | ||
|
|
2875f897dc | ||
|
|
ec129de63d | ||
|
|
1687ca93b3 | ||
|
|
5bb742ac3a | ||
|
|
ca2d61f393 | ||
|
|
750a2d23e0 | ||
|
|
6d90bf3537 |
@@ -23,7 +23,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: lts/*
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Update electron types
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: lts/*
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: lts/*
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version: 'lts/*'
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version: 'lts/*'
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version: 'lts/*'
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
|
||||
2
.github/workflows/pr-claude-review.yaml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '20'
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies for analysis tools
|
||||
|
||||
2
.github/workflows/pr-perf-report.yaml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
- name: Download PR metadata
|
||||
uses: dawidd6/action-download-artifact@0bd50d53a6d7fb5cb921e607957e9cc12b4ce392 # v12
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '24.x'
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
- name: Read desktop-ui version
|
||||
id: get_version
|
||||
|
||||
2
.github/workflows/publish-desktop-ui.yaml
vendored
@@ -91,7 +91,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '24.x'
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'pnpm'
|
||||
registry-url: https://registry.npmjs.org
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: lts/*
|
||||
node-version-file: 'frontend/.nvmrc'
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: frontend
|
||||
|
||||
2
.github/workflows/release-branch-create.yaml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 'lts/*'
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
- name: Check version bump type
|
||||
id: check_version
|
||||
|
||||
2
.github/workflows/release-draft-create.yaml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
version: 10
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 'lts/*'
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Get current version
|
||||
|
||||
2
.github/workflows/release-npm-types.yaml
vendored
@@ -82,7 +82,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 'lts/*'
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'pnpm'
|
||||
registry-url: https://registry.npmjs.org
|
||||
|
||||
|
||||
2
.github/workflows/release-pypi-dev.yaml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
version: 10
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 'lts/*'
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Get current version
|
||||
|
||||
2
.github/workflows/release-version-bump.yaml
vendored
@@ -149,7 +149,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: lts/*
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
- name: Bump version
|
||||
id: bump-version
|
||||
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '24.x'
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Bump desktop-ui version
|
||||
|
||||
2
.github/workflows/weekly-docs-check.yaml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '20'
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies for analysis tools
|
||||
|
||||
@@ -17,7 +17,7 @@ Have another idea? Drop into Discord or open an issue, and let's chat!
|
||||
### Prerequisites & Technology Stack
|
||||
|
||||
- **Required Software**:
|
||||
- Node.js (v24) and pnpm
|
||||
- Node.js (see `.nvmrc`, currently v24) and pnpm
|
||||
- Git for version control
|
||||
- A running ComfyUI backend instance (otherwise, you can use `pnpm dev:cloud`)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<template v-if="filter.tasks.length === 0">
|
||||
<!-- Empty filter -->
|
||||
<Divider />
|
||||
<p class="text-neutral-400 w-full text-center">
|
||||
<p class="w-full text-center text-neutral-400">
|
||||
{{ $t('maintenance.allOk') }}
|
||||
</p>
|
||||
</template>
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
<!-- Display: Cards -->
|
||||
<template v-else>
|
||||
<div class="flex flex-wrap justify-evenly gap-8 pad-y my-4">
|
||||
<div class="pad-y my-4 flex flex-wrap justify-evenly gap-8">
|
||||
<TaskCard
|
||||
v-for="task in filter.tasks"
|
||||
:key="task.id"
|
||||
@@ -45,7 +45,8 @@ import { useConfirm, useToast } from 'primevue'
|
||||
import ConfirmPopup from 'primevue/confirmpopup'
|
||||
import Divider from 'primevue/divider'
|
||||
|
||||
import { t } from '@/i18n'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { useMaintenanceTaskStore } from '@/stores/maintenanceTaskStore'
|
||||
import type {
|
||||
MaintenanceFilter,
|
||||
@@ -55,6 +56,7 @@ import type {
|
||||
import TaskCard from './TaskCard.vue'
|
||||
import TaskListItem from './TaskListItem.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const toast = useToast()
|
||||
const confirm = useConfirm()
|
||||
const taskStore = useMaintenanceTaskStore()
|
||||
@@ -80,8 +82,7 @@ const executeTask = async (task: MaintenanceTask) => {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: t('maintenance.error.toastTitle'),
|
||||
detail: message ?? t('maintenance.error.defaultDescription'),
|
||||
life: 10_000
|
||||
detail: message ?? t('maintenance.error.defaultDescription')
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -189,8 +189,7 @@ const completeValidation = async () => {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: t('g.error'),
|
||||
detail: t('maintenance.error.cannotContinue'),
|
||||
life: 5_000
|
||||
detail: t('maintenance.error.cannotContinue')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<BaseViewTemplate dark hide-language-selector>
|
||||
<div class="h-full p-8 2xl:p-16 flex flex-col items-center justify-center">
|
||||
<div class="flex h-full flex-col items-center justify-center p-8 2xl:p-16">
|
||||
<div
|
||||
class="bg-neutral-800 rounded-lg shadow-lg p-6 w-full max-w-[600px] flex flex-col gap-6"
|
||||
class="flex w-full max-w-[600px] flex-col gap-6 rounded-lg bg-neutral-800 p-6 shadow-lg"
|
||||
>
|
||||
<h2 class="text-3xl font-semibold text-neutral-100">
|
||||
{{ $t('install.helpImprove') }}
|
||||
@@ -15,7 +15,7 @@
|
||||
<a
|
||||
href="https://comfy.org/privacy"
|
||||
target="_blank"
|
||||
class="text-blue-400 hover:text-blue-300 underline"
|
||||
class="text-blue-400 underline hover:text-blue-300"
|
||||
>
|
||||
{{ $t('install.privacyPolicy') }} </a
|
||||
>.
|
||||
@@ -33,7 +33,7 @@
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex pt-6 justify-end">
|
||||
<div class="flex justify-end pt-6">
|
||||
<Button
|
||||
:label="$t('g.ok')"
|
||||
icon="pi pi-check"
|
||||
@@ -72,8 +72,7 @@ const updateConsent = async () => {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: t('install.settings.errorUpdatingConsent'),
|
||||
detail: t('install.settings.errorUpdatingConsentDetail'),
|
||||
life: 3000
|
||||
detail: t('install.settings.errorUpdatingConsentDetail')
|
||||
})
|
||||
} finally {
|
||||
isUpdating.value = false
|
||||
|
||||
@@ -27,7 +27,8 @@ cp -r tools/devtools/* /path/to/your/ComfyUI/custom_nodes/ComfyUI_devtools/
|
||||
|
||||
### Node.js & Playwright Prerequisites
|
||||
|
||||
Ensure you have Node.js v20 or v22 installed. Then, set up the Chromium test driver:
|
||||
Ensure you have the Node.js version from `.nvmrc` installed (currently v24).
|
||||
Then, set up the Chromium test driver:
|
||||
|
||||
```bash
|
||||
pnpm exec playwright install chromium --with-deps
|
||||
|
||||
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 138 KiB |
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 107 KiB |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"version": "1.41.13",
|
||||
"version": "1.42.2",
|
||||
"private": true,
|
||||
"description": "Official front-end implementation of ComfyUI",
|
||||
"homepage": "https://comfy.org",
|
||||
@@ -195,6 +195,9 @@
|
||||
"zip-dir": "^2.0.0",
|
||||
"zod-to-json-schema": "catalog:"
|
||||
},
|
||||
"engines": {
|
||||
"node": "24.x"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"vite": "catalog:"
|
||||
|
||||
@@ -166,13 +166,22 @@ describe('TopMenuSection', () => {
|
||||
})
|
||||
|
||||
describe('authentication state', () => {
|
||||
function createLegacyTabBarWrapper() {
|
||||
const pinia = createTestingPinia({ createSpy: vi.fn })
|
||||
const settingStore = useSettingStore(pinia)
|
||||
vi.mocked(settingStore.get).mockImplementation((key) =>
|
||||
key === 'Comfy.UI.TabBarLayout' ? 'Legacy' : undefined
|
||||
)
|
||||
return createWrapper({ pinia })
|
||||
}
|
||||
|
||||
describe('when user is logged in', () => {
|
||||
beforeEach(() => {
|
||||
mockData.isLoggedIn = true
|
||||
})
|
||||
|
||||
it('should display CurrentUserButton and not display LoginButton', () => {
|
||||
const wrapper = createWrapper()
|
||||
const wrapper = createLegacyTabBarWrapper()
|
||||
expect(wrapper.findComponent(CurrentUserButton).exists()).toBe(true)
|
||||
expect(wrapper.findComponent(LoginButton).exists()).toBe(false)
|
||||
})
|
||||
@@ -186,7 +195,7 @@ describe('TopMenuSection', () => {
|
||||
describe('on desktop platform', () => {
|
||||
it('should display LoginButton and not display CurrentUserButton', () => {
|
||||
mockData.isDesktop = true
|
||||
const wrapper = createWrapper()
|
||||
const wrapper = createLegacyTabBarWrapper()
|
||||
expect(wrapper.findComponent(LoginButton).exists()).toBe(true)
|
||||
expect(wrapper.findComponent(CurrentUserButton).exists()).toBe(false)
|
||||
})
|
||||
@@ -194,7 +203,7 @@ describe('TopMenuSection', () => {
|
||||
|
||||
describe('on web platform', () => {
|
||||
it('should not display CurrentUserButton and not display LoginButton', () => {
|
||||
const wrapper = createWrapper()
|
||||
const wrapper = createLegacyTabBarWrapper()
|
||||
expect(wrapper.findComponent(CurrentUserButton).exists()).toBe(false)
|
||||
expect(wrapper.findComponent(LoginButton).exists()).toBe(false)
|
||||
})
|
||||
|
||||
@@ -34,17 +34,7 @@
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref="actionbarContainerRef"
|
||||
:class="
|
||||
cn(
|
||||
'actionbar-container pointer-events-auto relative flex h-12 items-center gap-2 rounded-lg border bg-comfy-menu-bg px-2 shadow-interface',
|
||||
hasAnyError
|
||||
? 'border-destructive-background-hover'
|
||||
: 'border-interface-stroke'
|
||||
)
|
||||
"
|
||||
>
|
||||
<div ref="actionbarContainerRef" :class="actionbarContainerClass">
|
||||
<ActionBarButtons />
|
||||
<!-- Support for legacy topbar elements attached by custom scripts, hidden if no elements present -->
|
||||
<div
|
||||
@@ -55,6 +45,7 @@
|
||||
<ComfyActionbar
|
||||
:top-menu-container="actionbarContainerRef"
|
||||
:queue-overlay-expanded="isQueueOverlayExpanded"
|
||||
:has-any-error="hasAnyError"
|
||||
@update:progress-target="updateProgressTarget"
|
||||
/>
|
||||
<CurrentUserButton
|
||||
@@ -123,7 +114,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useLocalStorage } from '@vueuse/core'
|
||||
import { useLocalStorage, useMutationObserver } from '@vueuse/core'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
@@ -145,6 +136,7 @@ import { buildTooltipConfig } from '@/composables/useTooltipConfig'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useExecutionErrorStore } from '@/stores/executionErrorStore'
|
||||
import { useActionBarButtonStore } from '@/stores/actionBarButtonStore'
|
||||
import { useQueueUIStore } from '@/stores/queueStore'
|
||||
import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
@@ -168,6 +160,7 @@ const { isLoggedIn } = useCurrentUser()
|
||||
const { t } = useI18n()
|
||||
const { toastErrorHandler } = useErrorHandling()
|
||||
const executionErrorStore = useExecutionErrorStore()
|
||||
const actionBarButtonStore = useActionBarButtonStore()
|
||||
const queueUIStore = useQueueUIStore()
|
||||
const { isOverlayExpanded: isQueueOverlayExpanded } = storeToRefs(queueUIStore)
|
||||
const { shouldShowRedDot: shouldShowConflictRedDot } =
|
||||
@@ -182,8 +175,45 @@ const isActionbarEnabled = computed(
|
||||
const isActionbarFloating = computed(
|
||||
() => isActionbarEnabled.value && !isActionbarDocked.value
|
||||
)
|
||||
/**
|
||||
* Whether the actionbar container has any visible docked buttons
|
||||
* (excluding ComfyActionbar, which uses position:fixed when floating
|
||||
* and does not contribute to the container's visual layout).
|
||||
*/
|
||||
const hasDockedButtons = computed(() => {
|
||||
if (actionBarButtonStore.buttons.length > 0) return true
|
||||
if (hasLegacyContent.value) return true
|
||||
if (isLoggedIn.value && !isIntegratedTabBar.value) return true
|
||||
if (isDesktop && !isIntegratedTabBar.value) return true
|
||||
if (isCloud && flags.workflowSharingEnabled) return true
|
||||
if (!isRightSidePanelOpen.value) return true
|
||||
return false
|
||||
})
|
||||
const isActionbarContainerEmpty = computed(
|
||||
() => isActionbarFloating.value && !hasDockedButtons.value
|
||||
)
|
||||
const actionbarContainerClass = computed(() => {
|
||||
const base =
|
||||
'actionbar-container pointer-events-auto relative flex h-12 items-center gap-2 rounded-lg border bg-comfy-menu-bg shadow-interface'
|
||||
|
||||
if (isActionbarContainerEmpty.value) {
|
||||
return cn(
|
||||
base,
|
||||
'-ml-2 w-0 min-w-0 border-transparent shadow-none',
|
||||
'has-[.border-dashed]:ml-0 has-[.border-dashed]:w-auto has-[.border-dashed]:min-w-auto',
|
||||
'has-[.border-dashed]:border-interface-stroke has-[.border-dashed]:pl-2 has-[.border-dashed]:shadow-interface'
|
||||
)
|
||||
}
|
||||
|
||||
const borderClass =
|
||||
!isActionbarFloating.value && hasAnyError.value
|
||||
? 'border-destructive-background-hover'
|
||||
: 'border-interface-stroke'
|
||||
|
||||
return cn(base, 'px-2', borderClass)
|
||||
})
|
||||
const isIntegratedTabBar = computed(
|
||||
() => settingStore.get('Comfy.UI.TabBarLayout') === 'Integrated'
|
||||
() => settingStore.get('Comfy.UI.TabBarLayout') !== 'Legacy'
|
||||
)
|
||||
const { isQueuePanelV2Enabled, isRunProgressBarEnabled } =
|
||||
useQueueFeatureFlags()
|
||||
@@ -233,6 +263,25 @@ const rightSidePanelTooltipConfig = computed(() =>
|
||||
|
||||
// Maintain support for legacy topbar elements attached by custom scripts
|
||||
const legacyCommandsContainerRef = ref<HTMLElement>()
|
||||
const hasLegacyContent = ref(false)
|
||||
|
||||
function checkLegacyContent() {
|
||||
const el = legacyCommandsContainerRef.value
|
||||
if (!el) {
|
||||
hasLegacyContent.value = false
|
||||
return
|
||||
}
|
||||
// Mirror the CSS: [&:not(:has(*>*:not(:empty)))]:hidden
|
||||
hasLegacyContent.value =
|
||||
el.querySelector(':scope > * > *:not(:empty)') !== null
|
||||
}
|
||||
|
||||
useMutationObserver(legacyCommandsContainerRef, checkLegacyContent, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
characterData: true
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (legacyCommandsContainerRef.value) {
|
||||
app.menu.element.style.width = 'fit-content'
|
||||
|
||||
@@ -119,9 +119,14 @@ import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
import ComfyRunButton from './ComfyRunButton'
|
||||
|
||||
const { topMenuContainer, queueOverlayExpanded = false } = defineProps<{
|
||||
const {
|
||||
topMenuContainer,
|
||||
queueOverlayExpanded = false,
|
||||
hasAnyError = false
|
||||
} = defineProps<{
|
||||
topMenuContainer?: HTMLElement | null
|
||||
queueOverlayExpanded?: boolean
|
||||
hasAnyError?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -435,7 +440,12 @@ const panelClass = computed(() =>
|
||||
isDragging.value && 'pointer-events-none select-none',
|
||||
isDocked.value
|
||||
? 'static border-none bg-transparent p-0'
|
||||
: 'fixed shadow-interface'
|
||||
: [
|
||||
'fixed shadow-interface',
|
||||
hasAnyError
|
||||
? 'border-destructive-background-hover'
|
||||
: 'border-interface-stroke'
|
||||
]
|
||||
)
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -46,71 +46,74 @@ function showApps() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="pointer-events-auto flex flex-col gap-2">
|
||||
<WorkflowActionsDropdown source="app_mode_toolbar" />
|
||||
|
||||
<Button
|
||||
v-if="enableAppBuilder"
|
||||
v-tooltip.right="{
|
||||
value: t('linearMode.appModeToolbar.appBuilder'),
|
||||
...tooltipOptions
|
||||
}"
|
||||
variant="secondary"
|
||||
size="unset"
|
||||
:disabled="!hasNodes"
|
||||
:aria-label="t('linearMode.appModeToolbar.appBuilder')"
|
||||
class="size-10 rounded-lg"
|
||||
@click="enterBuilder"
|
||||
>
|
||||
<i class="icon-[lucide--hammer] size-4" />
|
||||
</Button>
|
||||
<Button
|
||||
v-if="isCloud && flags.workflowSharingEnabled"
|
||||
v-tooltip.right="{
|
||||
value: t('actionbar.shareTooltip'),
|
||||
...tooltipOptions
|
||||
}"
|
||||
variant="secondary"
|
||||
size="unset"
|
||||
:aria-label="t('actionbar.shareTooltip')"
|
||||
class="size-10 rounded-lg"
|
||||
@click="() => openShareDialog().catch(toastErrorHandler)"
|
||||
@pointerenter="prefetchShareDialog"
|
||||
>
|
||||
<i class="icon-[lucide--send] size-4" />
|
||||
</Button>
|
||||
|
||||
<div
|
||||
class="flex w-10 flex-col overflow-hidden rounded-lg bg-secondary-background"
|
||||
>
|
||||
<div class="pointer-events-auto flex flex-row items-start gap-2">
|
||||
<div class="pointer-events-auto flex flex-col gap-2">
|
||||
<Button
|
||||
v-if="enableAppBuilder"
|
||||
v-tooltip.right="{
|
||||
value: t('sideToolbar.mediaAssets.title'),
|
||||
value: t('linearMode.appModeToolbar.appBuilder'),
|
||||
...tooltipOptions
|
||||
}"
|
||||
variant="textonly"
|
||||
variant="secondary"
|
||||
size="unset"
|
||||
:aria-label="t('sideToolbar.mediaAssets.title')"
|
||||
:class="
|
||||
cn('size-10', isAssetsActive && 'bg-secondary-background-hover')
|
||||
"
|
||||
@click="openAssets"
|
||||
:disabled="!hasNodes"
|
||||
:aria-label="t('linearMode.appModeToolbar.appBuilder')"
|
||||
class="size-10 rounded-lg"
|
||||
@click="enterBuilder"
|
||||
>
|
||||
<i class="icon-[comfy--image-ai-edit] size-4" />
|
||||
<i class="icon-[lucide--hammer] size-4" />
|
||||
</Button>
|
||||
<Button
|
||||
v-if="isCloud && flags.workflowSharingEnabled"
|
||||
v-tooltip.right="{
|
||||
value: t('linearMode.appModeToolbar.apps'),
|
||||
value: t('actionbar.shareTooltip'),
|
||||
...tooltipOptions
|
||||
}"
|
||||
variant="textonly"
|
||||
variant="secondary"
|
||||
size="unset"
|
||||
:aria-label="t('linearMode.appModeToolbar.apps')"
|
||||
:class="cn('size-10', isAppsActive && 'bg-secondary-background-hover')"
|
||||
@click="showApps"
|
||||
:aria-label="t('actionbar.shareTooltip')"
|
||||
class="size-10 rounded-lg"
|
||||
@click="() => openShareDialog().catch(toastErrorHandler)"
|
||||
@pointerenter="prefetchShareDialog"
|
||||
>
|
||||
<i class="icon-[lucide--panels-top-left] size-4" />
|
||||
<i class="icon-[lucide--send] size-4" />
|
||||
</Button>
|
||||
|
||||
<div
|
||||
class="flex w-10 flex-col overflow-hidden rounded-lg bg-secondary-background"
|
||||
>
|
||||
<Button
|
||||
v-tooltip.right="{
|
||||
value: t('sideToolbar.mediaAssets.title'),
|
||||
...tooltipOptions
|
||||
}"
|
||||
variant="textonly"
|
||||
size="unset"
|
||||
:aria-label="t('sideToolbar.mediaAssets.title')"
|
||||
:class="
|
||||
cn('size-10', isAssetsActive && 'bg-secondary-background-hover')
|
||||
"
|
||||
@click="openAssets"
|
||||
>
|
||||
<i class="icon-[comfy--image-ai-edit] size-4" />
|
||||
</Button>
|
||||
<Button
|
||||
v-tooltip.right="{
|
||||
value: t('linearMode.appModeToolbar.apps'),
|
||||
...tooltipOptions
|
||||
}"
|
||||
variant="textonly"
|
||||
size="unset"
|
||||
:aria-label="t('linearMode.appModeToolbar.apps')"
|
||||
:class="
|
||||
cn('size-10', isAppsActive && 'bg-secondary-background-hover')
|
||||
"
|
||||
@click="showApps"
|
||||
>
|
||||
<i class="icon-[lucide--panels-top-left] size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<WorkflowActionsDropdown source="app_mode_toolbar" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -10,7 +10,6 @@ import PropertiesAccordionItem from '@/components/rightSidePanel/layout/Properti
|
||||
import WidgetItem from '@/components/rightSidePanel/parameters/WidgetItem.vue'
|
||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import type { LGraphNode, NodeId } from '@/lib/litegraph/src/LGraphNode'
|
||||
import type { INodeInputSlot } from '@/lib/litegraph/src/interfaces'
|
||||
import type { LGraphCanvas } from '@/lib/litegraph/src/LGraphCanvas'
|
||||
import {
|
||||
LGraphEventMode,
|
||||
@@ -25,9 +24,9 @@ import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteracti
|
||||
import TransformPane from '@/renderer/core/layout/transform/TransformPane.vue'
|
||||
import { app } from '@/scripts/app'
|
||||
import { DOMWidgetImpl } from '@/scripts/domWidget'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { promptRenameWidget } from '@/utils/widgetUtil'
|
||||
import { useAppMode } from '@/composables/useAppMode'
|
||||
import { useAppModeStore } from '@/stores/appModeStore'
|
||||
import { nodeTypeValidForApp, useAppModeStore } from '@/stores/appModeStore'
|
||||
import { resolveNode } from '@/utils/litegraphUtil'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
import { HideLayoutFieldKey } from '@/types/widgetTypes'
|
||||
@@ -73,15 +72,12 @@ const inputsWithState = computed(() =>
|
||||
}
|
||||
}
|
||||
|
||||
const input = node.inputs.find((i) => i.widget?.name === widget.name)
|
||||
const rename = input && (() => renameWidget(widget, input))
|
||||
|
||||
return {
|
||||
nodeId,
|
||||
widgetName,
|
||||
label: widget.label,
|
||||
subLabel: node.title,
|
||||
rename
|
||||
rename: () => promptRenameWidget(widget, node, t)
|
||||
}
|
||||
})
|
||||
)
|
||||
@@ -92,20 +88,6 @@ const outputsWithState = computed<[NodeId, string][]>(() =>
|
||||
])
|
||||
)
|
||||
|
||||
async function renameWidget(widget: IBaseWidget, input: INodeInputSlot) {
|
||||
const newLabel = await useDialogService().prompt({
|
||||
title: t('g.rename'),
|
||||
message: t('g.enterNewNamePrompt'),
|
||||
defaultValue: widget.label,
|
||||
placeholder: widget.name
|
||||
})
|
||||
if (newLabel === null) return
|
||||
widget.label = newLabel || undefined
|
||||
input.label = newLabel || undefined
|
||||
widget.callback?.(widget.value)
|
||||
useCanvasStore().canvas?.setDirty(true)
|
||||
}
|
||||
|
||||
function getHovered(
|
||||
e: MouseEvent
|
||||
): undefined | [LGraphNode, undefined] | [LGraphNode, IBaseWidget] {
|
||||
@@ -162,7 +144,11 @@ function handleDown(e: MouseEvent) {
|
||||
}
|
||||
function handleClick(e: MouseEvent) {
|
||||
const [node, widget] = getHovered(e) ?? []
|
||||
if (node?.mode !== LGraphEventMode.ALWAYS)
|
||||
if (
|
||||
node?.mode !== LGraphEventMode.ALWAYS ||
|
||||
!nodeTypeValidForApp(node.type) ||
|
||||
node.has_errors
|
||||
)
|
||||
return canvasInteractions.forwardEventToCanvas(e)
|
||||
|
||||
if (!widget) {
|
||||
@@ -198,7 +184,9 @@ const renderedOutputs = computed(() => {
|
||||
return canvas
|
||||
.graph!.nodes.filter(
|
||||
(n) =>
|
||||
n.constructor.nodeData?.output_node && n.mode === LGraphEventMode.ALWAYS
|
||||
n.constructor.nodeData?.output_node &&
|
||||
n.mode === LGraphEventMode.ALWAYS &&
|
||||
!n.has_errors
|
||||
)
|
||||
.map(nodeToDisplayTuple)
|
||||
})
|
||||
@@ -260,7 +248,7 @@ const renderedInputs = computed<[string, MaybeRef<BoundStyle> | undefined][]>(
|
||||
<template #label>
|
||||
<div class="flex gap-3">
|
||||
{{ t('nodeHelpPage.inputs') }}
|
||||
<i class="icon-[lucide--circle-alert] bg-muted-foreground" />
|
||||
<i class="icon-[lucide--info] bg-muted-foreground" />
|
||||
</div>
|
||||
</template>
|
||||
<template #empty>
|
||||
@@ -315,7 +303,7 @@ const renderedInputs = computed<[string, MaybeRef<BoundStyle> | undefined][]>(
|
||||
<template #label>
|
||||
<div class="flex gap-3">
|
||||
{{ t('nodeHelpPage.outputs') }}
|
||||
<i class="icon-[lucide--circle-alert] bg-muted-foreground" />
|
||||
<i class="icon-[lucide--info] bg-muted-foreground" />
|
||||
</div>
|
||||
</template>
|
||||
<template #empty>
|
||||
|
||||
@@ -72,7 +72,7 @@ const menuItems = computed(() => [
|
||||
},
|
||||
{
|
||||
label: t('builderMenu.exitAppBuilder'),
|
||||
icon: 'icon-[lucide--square-pen]',
|
||||
icon: 'icon-[lucide--x]',
|
||||
action: onExitBuilder
|
||||
}
|
||||
])
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import Popover from '@/components/ui/Popover.vue'
|
||||
@@ -7,6 +7,13 @@ import Button from '@/components/ui/button/Button.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const titleTooltip = ref<string | null>(null)
|
||||
const subTitleTooltip = ref<string | null>(null)
|
||||
|
||||
function isTruncated(e: MouseEvent): boolean {
|
||||
const el = e.currentTarget as HTMLElement
|
||||
return el.scrollWidth > el.clientWidth
|
||||
}
|
||||
const { rename, remove } = defineProps<{
|
||||
title: string
|
||||
subTitle?: string
|
||||
@@ -32,15 +39,28 @@ const entries = computed(() => {
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="my-2 flex items-center-safe gap-2 rounded-lg p-2">
|
||||
<div
|
||||
class="drag-handle mr-auto inline max-w-max min-w-0 flex-[4_1_0%] truncate"
|
||||
v-text="title"
|
||||
/>
|
||||
<div
|
||||
class="drag-handle inline max-w-max min-w-0 flex-[2_1_0%] truncate text-end text-muted-foreground"
|
||||
v-text="subTitle"
|
||||
/>
|
||||
<div
|
||||
class="my-2 flex items-center-safe gap-2 rounded-lg p-2"
|
||||
data-testid="builder-io-item"
|
||||
>
|
||||
<div class="drag-handle mr-auto flex min-w-0 flex-col gap-1">
|
||||
<div
|
||||
v-tooltip.left="{ value: titleTooltip, showDelay: 300 }"
|
||||
class="drag-handle truncate text-sm"
|
||||
data-testid="builder-io-item-title"
|
||||
@mouseenter="titleTooltip = isTruncated($event) ? title : null"
|
||||
v-text="title"
|
||||
/>
|
||||
<div
|
||||
v-tooltip.left="{ value: subTitleTooltip, showDelay: 300 }"
|
||||
class="drag-handle truncate text-xs text-muted-foreground"
|
||||
data-testid="builder-io-item-subtitle"
|
||||
@mouseenter="
|
||||
subTitleTooltip = isTruncated($event) ? (subTitle ?? null) : null
|
||||
"
|
||||
v-text="subTitle"
|
||||
/>
|
||||
</div>
|
||||
<Popover :entries>
|
||||
<template #button>
|
||||
<Button variant="muted-textonly">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<Avatar
|
||||
class="bg-interface-panel-selected-surface"
|
||||
class="aspect-square bg-interface-panel-selected-surface"
|
||||
:image="photoUrl ?? undefined"
|
||||
:icon="hasAvatar ? undefined : 'icon-[lucide--user]'"
|
||||
:pt:icon:class="{ 'size-4': !hasAvatar }"
|
||||
|
||||
@@ -138,8 +138,7 @@ onMounted(async () => {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: t('g.error'),
|
||||
detail: t('toastMessages.failedToFetchLogs'),
|
||||
life: 5000
|
||||
detail: t('toastMessages.failedToFetchLogs')
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -275,8 +275,7 @@ async function handleBuy() {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: t('credits.topUp.purchaseError'),
|
||||
detail: t('credits.topUp.purchaseErrorDetail', { error: errorMessage }),
|
||||
life: 5000
|
||||
detail: t('credits.topUp.purchaseErrorDetail', { error: errorMessage })
|
||||
})
|
||||
} finally {
|
||||
loading.value = false
|
||||
|
||||
@@ -98,8 +98,7 @@ async function onConfirmCancel() {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: t('subscription.cancelDialog.failed'),
|
||||
detail: error instanceof Error ? error.message : t('g.unknownError'),
|
||||
life: 5000
|
||||
detail: error instanceof Error ? error.message : t('g.unknownError')
|
||||
})
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
|
||||
@@ -579,8 +579,7 @@ const onUpdateComfyUI = async (): Promise<void> => {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: t('g.error'),
|
||||
detail: error.value || t('helpCenter.updateComfyUIFailed'),
|
||||
life: 5000
|
||||
detail: error.value || t('helpCenter.updateComfyUIFailed')
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -597,8 +596,7 @@ const onUpdateComfyUI = async (): Promise<void> => {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: t('g.error'),
|
||||
detail: err instanceof Error ? err.message : t('g.unknownError'),
|
||||
life: 5000
|
||||
detail: err instanceof Error ? err.message : t('g.unknownError')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,8 @@
|
||||
v-if="isHelpCenterVisible"
|
||||
class="help-center-popup"
|
||||
:class="{
|
||||
'sidebar-left':
|
||||
triggerLocation === 'sidebar' && sidebarLocation === 'left',
|
||||
'sidebar-right':
|
||||
triggerLocation === 'sidebar' && sidebarLocation === 'right',
|
||||
'topbar-right': triggerLocation === 'topbar',
|
||||
'sidebar-left': sidebarLocation === 'left',
|
||||
'sidebar-right': sidebarLocation === 'right',
|
||||
'small-sidebar': isSmall
|
||||
}"
|
||||
>
|
||||
@@ -63,7 +60,6 @@ const { isSmall = false } = defineProps<{
|
||||
|
||||
const {
|
||||
isHelpCenterVisible,
|
||||
triggerLocation,
|
||||
sidebarLocation,
|
||||
closeHelpCenter,
|
||||
handleWhatsNewDismissed
|
||||
@@ -101,25 +97,6 @@ const {
|
||||
right: 1rem;
|
||||
}
|
||||
|
||||
.help-center-popup.topbar-right {
|
||||
top: 2rem;
|
||||
right: 1rem;
|
||||
bottom: auto;
|
||||
animation: slideInDown 0.2s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideInDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
|
||||
@@ -141,7 +141,7 @@ onMounted(async () => {
|
||||
if (isStandaloneMode && props.modelUrl) {
|
||||
await viewer.initializeStandaloneViewer(containerRef.value, props.modelUrl)
|
||||
} else if (props.node) {
|
||||
const source = useLoad3dService().getLoad3d(props.node)
|
||||
const source = await useLoad3dService().getLoad3dAsync(props.node)
|
||||
if (source) {
|
||||
await viewer.initializeViewer(containerRef.value, source)
|
||||
}
|
||||
|
||||
@@ -72,12 +72,12 @@
|
||||
/>
|
||||
</div>
|
||||
<SliderControl
|
||||
v-model="brushSize"
|
||||
v-model="brushSizeSliderValue"
|
||||
class="flex-1"
|
||||
label=""
|
||||
:min="1"
|
||||
:max="250"
|
||||
:step="1"
|
||||
:min="0"
|
||||
:max="1"
|
||||
:step="0.001"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -182,6 +182,26 @@ const brushSize = computed({
|
||||
set: (value: number) => store.setBrushSize(value)
|
||||
})
|
||||
|
||||
const rawSliderValue = ref<number | null>(null)
|
||||
|
||||
const brushSizeSliderValue = computed({
|
||||
get: () => {
|
||||
if (rawSliderValue.value !== null) {
|
||||
const cachedSize = Math.round(Math.pow(250, rawSliderValue.value))
|
||||
if (cachedSize === brushSize.value) {
|
||||
return rawSliderValue.value
|
||||
}
|
||||
}
|
||||
|
||||
return Math.log(brushSize.value) / Math.log(250)
|
||||
},
|
||||
set: (value: number) => {
|
||||
rawSliderValue.value = value
|
||||
const size = Math.round(Math.pow(250, value))
|
||||
store.setBrushSize(size)
|
||||
}
|
||||
})
|
||||
|
||||
const brushOpacity = computed({
|
||||
get: () => store.brushSettings.opacity,
|
||||
set: (value: number) => store.setBrushOpacity(value)
|
||||
|
||||
@@ -15,10 +15,9 @@ import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import type { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode'
|
||||
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { useNodeDefStore } from '@/stores/nodeDefStore'
|
||||
import { useFavoritedWidgetsStore } from '@/stores/workspace/favoritedWidgetsStore'
|
||||
import { getWidgetDefaultValue } from '@/utils/widgetUtil'
|
||||
import { getWidgetDefaultValue, promptWidgetLabel } from '@/utils/widgetUtil'
|
||||
import type { WidgetValue } from '@/utils/widgetUtil'
|
||||
|
||||
const {
|
||||
@@ -42,7 +41,6 @@ const label = defineModel<string>('label', { required: true })
|
||||
const canvasStore = useCanvasStore()
|
||||
const favoritedWidgetsStore = useFavoritedWidgetsStore()
|
||||
const nodeDefStore = useNodeDefStore()
|
||||
const dialogService = useDialogService()
|
||||
const { t } = useI18n()
|
||||
|
||||
const hasParents = computed(() => parents?.length > 0)
|
||||
@@ -67,15 +65,8 @@ const isCurrentValueDefault = computed(() => {
|
||||
})
|
||||
|
||||
async function handleRename() {
|
||||
const newLabel = await dialogService.prompt({
|
||||
title: t('g.rename'),
|
||||
message: t('g.enterNewNamePrompt'),
|
||||
defaultValue: widget.label,
|
||||
placeholder: widget.name
|
||||
})
|
||||
|
||||
if (newLabel === null) return
|
||||
label.value = newLabel
|
||||
const newLabel = await promptWidgetLabel(widget, t)
|
||||
if (newLabel !== null) label.value = newLabel
|
||||
}
|
||||
|
||||
function handleHideInput() {
|
||||
|
||||
@@ -11,12 +11,13 @@
|
||||
}"
|
||||
@click="onLogoMenuClick($event)"
|
||||
>
|
||||
<div class="flex size-8 items-center justify-center rounded-lg bg-black">
|
||||
<div class="flex items-center gap-0.5">
|
||||
<ComfyLogo
|
||||
alt="ComfyUI Logo"
|
||||
class="comfyui-logo h-[18px] w-[18px] text-white"
|
||||
class="comfyui-logo h-[18px] w-[18px]"
|
||||
mode="fill"
|
||||
/>
|
||||
<i class="icon-[lucide--chevron-down] size-3 text-muted-foreground" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
v-if="userStore.isMultiUserServer"
|
||||
:is-small="isSmall"
|
||||
/>
|
||||
<SidebarHelpCenterIcon v-if="!isIntegratedTabBar" :is-small="isSmall" />
|
||||
<SidebarHelpCenterIcon :is-small="isSmall" />
|
||||
<SidebarBottomPanelToggleButton v-if="!isCloud" :is-small="isSmall" />
|
||||
<SidebarShortcutsToggleButton :is-small="isSmall" />
|
||||
<SidebarSettingsButton :is-small="isSmall" />
|
||||
@@ -95,9 +95,6 @@ const sidebarLocation = computed<'left' | 'right'>(() =>
|
||||
settingStore.get('Comfy.Sidebar.Location')
|
||||
)
|
||||
const sidebarStyle = computed(() => settingStore.get('Comfy.Sidebar.Style'))
|
||||
const isIntegratedTabBar = computed(
|
||||
() => settingStore.get('Comfy.UI.TabBarLayout') === 'Integrated'
|
||||
)
|
||||
const isConnected = computed(
|
||||
() =>
|
||||
selectedTab.value ||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
:icon-badge="shouldShowRedDot ? '•' : ''"
|
||||
badge-class="-top-1 -right-1 min-w-2 w-2 h-2 p-0 rounded-full text-[0px] bg-[#ff3b30]"
|
||||
:is-small="isSmall"
|
||||
@click="toggleHelpCenter"
|
||||
@click="toggleHelpCenter()"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
tabindex="0"
|
||||
:aria-label="
|
||||
t('assetBrowser.ariaLabel.assetCard', {
|
||||
name: item.asset.name,
|
||||
name: getAssetDisplayName(item.asset),
|
||||
type: getAssetMediaType(item.asset)
|
||||
})
|
||||
"
|
||||
@@ -44,7 +44,7 @@
|
||||
)
|
||||
"
|
||||
:preview-url="getAssetPreviewUrl(item.asset)"
|
||||
:preview-alt="item.asset.name"
|
||||
:preview-alt="getAssetDisplayName(item.asset)"
|
||||
:icon-name="iconForMediaType(getAssetMediaType(item.asset))"
|
||||
:is-video-preview="isVideoAsset(item.asset)"
|
||||
:primary-text="getAssetPrimaryText(item.asset)"
|
||||
@@ -133,8 +133,12 @@ const listGridStyle = {
|
||||
gap: '0.5rem'
|
||||
}
|
||||
|
||||
function getAssetDisplayName(asset: AssetItem): string {
|
||||
return asset.display_name || asset.name
|
||||
}
|
||||
|
||||
function getAssetPrimaryText(asset: AssetItem): string {
|
||||
return truncateFilename(asset.name)
|
||||
return truncateFilename(getAssetDisplayName(asset))
|
||||
}
|
||||
|
||||
function getAssetMediaType(asset: AssetItem) {
|
||||
|
||||
@@ -170,9 +170,9 @@
|
||||
</div>
|
||||
</template>
|
||||
</SidebarTabTemplate>
|
||||
<ResultGallery
|
||||
<MediaViewer
|
||||
v-model:active-index="galleryActiveIndex"
|
||||
:all-gallery-items="galleryItems"
|
||||
:items="previewableVisibleAssets"
|
||||
/>
|
||||
<MediaAssetContextMenu
|
||||
v-if="contextMenuAsset"
|
||||
@@ -221,12 +221,12 @@ import AssetsSidebarGridView from '@/components/sidebar/tabs/AssetsSidebarGridVi
|
||||
import AssetsSidebarListView from '@/components/sidebar/tabs/AssetsSidebarListView.vue'
|
||||
import SidebarTabTemplate from '@/components/sidebar/tabs/SidebarTabTemplate.vue'
|
||||
import Skeleton from '@/components/ui/skeleton/Skeleton.vue'
|
||||
import ResultGallery from '@/components/sidebar/tabs/queue/ResultGallery.vue'
|
||||
import Tab from '@/components/tab/Tab.vue'
|
||||
import TabList from '@/components/tab/TabList.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import MediaAssetContextMenu from '@/platform/assets/components/MediaAssetContextMenu.vue'
|
||||
import MediaAssetFilterBar from '@/platform/assets/components/MediaAssetFilterBar.vue'
|
||||
import MediaViewer from '@/platform/assets/components/MediaViewer.vue'
|
||||
import { getAssetType } from '@/platform/assets/composables/media/assetMappers'
|
||||
import { useMediaAssets } from '@/platform/assets/composables/media/useMediaAssets'
|
||||
import { useAssetSelection } from '@/platform/assets/composables/useAssetSelection'
|
||||
@@ -240,7 +240,6 @@ import type { MediaKind } from '@/platform/assets/schemas/mediaAssetSchema'
|
||||
import { resolveOutputAssetItems } from '@/platform/assets/utils/outputAssetUtil'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
import { ResultItemImpl } from '@/stores/queueStore'
|
||||
import {
|
||||
formatDuration,
|
||||
getMediaTypeFromFilename,
|
||||
@@ -454,28 +453,6 @@ watch(galleryActiveIndex, (index) => {
|
||||
}
|
||||
})
|
||||
|
||||
const galleryItems = computed(() => {
|
||||
return previewableVisibleAssets.value.map((asset) => {
|
||||
const mediaType = getMediaTypeFromFilename(asset.name)
|
||||
const resultItem = new ResultItemImpl({
|
||||
filename: asset.name,
|
||||
subfolder: '',
|
||||
type: 'output',
|
||||
nodeId: '0',
|
||||
mediaType: mediaType === 'image' ? 'images' : mediaType
|
||||
})
|
||||
|
||||
Object.defineProperty(resultItem, 'url', {
|
||||
get() {
|
||||
return asset.preview_url || ''
|
||||
},
|
||||
configurable: true
|
||||
})
|
||||
|
||||
return resultItem
|
||||
})
|
||||
})
|
||||
|
||||
const refreshAssets = async () => {
|
||||
await currentAssets.value.fetchMediaList()
|
||||
if (error.value) {
|
||||
@@ -569,7 +546,7 @@ const handleZoomClick = (asset: AssetItem) => {
|
||||
const dialogStore = useDialogStore()
|
||||
dialogStore.showDialog({
|
||||
key: 'asset-3d-viewer',
|
||||
title: asset.name,
|
||||
title: asset.display_name || asset.name,
|
||||
component: Load3dViewerContent,
|
||||
props: {
|
||||
modelUrl: asset.preview_url || ''
|
||||
@@ -615,8 +592,7 @@ const enterFolderView = async (asset: AssetItem) => {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: t('sideToolbar.folderView.errorSummary'),
|
||||
detail: t('sideToolbar.folderView.errorDetail'),
|
||||
life: 5000
|
||||
detail: t('sideToolbar.folderView.errorDetail')
|
||||
})
|
||||
exitFolderView()
|
||||
}
|
||||
@@ -662,8 +638,7 @@ const copyJobId = async () => {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: t('mediaAsset.jobIdToast.error'),
|
||||
detail: t('mediaAsset.jobIdToast.jobIdCopyFailed'),
|
||||
life: 3000
|
||||
detail: t('mediaAsset.jobIdToast.jobIdCopyFailed')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<UserAvatar
|
||||
v-else
|
||||
:photo-url="photoURL"
|
||||
:class="compact && 'size-full'"
|
||||
:class="compact && 'h-full w-auto'"
|
||||
/>
|
||||
|
||||
<i v-if="showArrow" class="icon-[lucide--chevron-down] size-4 px-1" />
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
<template>
|
||||
<Button
|
||||
class="comfy-help-center-btn relative text-base-foreground"
|
||||
variant="textonly"
|
||||
@click="toggleHelpCenter"
|
||||
>
|
||||
<div class="not-md:hidden">{{ $t('menu.helpAndFeedback') }}</div>
|
||||
<i class="ml-0.5 icon-[lucide--circle-help]" />
|
||||
<span
|
||||
v-if="shouldShowRedDot"
|
||||
class="absolute top-[7px] right-[7px] size-1.5 rounded-full bg-[#ff3b30]"
|
||||
/>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { useHelpCenter } from '@/composables/useHelpCenter'
|
||||
|
||||
const { shouldShowRedDot, toggleHelpCenter } = useHelpCenter('topbar')
|
||||
</script>
|
||||
@@ -10,8 +10,9 @@
|
||||
@mouseup="handleMouseUp"
|
||||
@click="handleClick"
|
||||
>
|
||||
<i v-if="isBuilderState" class="bg-text-subtle icon-[lucide--hammer]" />
|
||||
<i
|
||||
v-if="workflowOption.workflow.initialMode === 'app'"
|
||||
v-else-if="workflowOption.workflow.initialMode === 'app'"
|
||||
class="icon-[lucide--panels-top-left] bg-primary-background"
|
||||
/>
|
||||
<span
|
||||
@@ -149,6 +150,11 @@ const shouldShowStatusIndicator = computed(() => {
|
||||
return false
|
||||
})
|
||||
|
||||
const isBuilderState = computed(() => {
|
||||
const currentMode = props.workflowOption.workflow.activeMode
|
||||
return typeof currentMode === 'string' && currentMode.startsWith('builder:')
|
||||
})
|
||||
|
||||
const isActiveTab = computed(() => {
|
||||
return workflowStore.activeWorkflow?.key === props.workflowOption.workflow.key
|
||||
})
|
||||
|
||||
@@ -83,13 +83,18 @@
|
||||
v-if="isIntegratedTabBar"
|
||||
class="ml-auto flex shrink-0 items-center gap-2 px-2"
|
||||
>
|
||||
<TopMenuHelpButton />
|
||||
<CurrentUserButton
|
||||
v-if="isLoggedIn"
|
||||
:show-arrow="false"
|
||||
compact
|
||||
class="grid w-10 shrink-0 p-1"
|
||||
/>
|
||||
<Button
|
||||
v-if="isCloud || isNightly"
|
||||
v-tooltip="{ value: $t('actionbar.feedbackTooltip'), showDelay: 300 }"
|
||||
variant="muted-textonly"
|
||||
size="icon"
|
||||
class="shrink-0 text-base-foreground"
|
||||
:aria-label="$t('actionbar.feedback')"
|
||||
@click="openFeedback"
|
||||
>
|
||||
<i class="icon-[lucide--message-square-text]" />
|
||||
</Button>
|
||||
<CurrentUserButton v-if="showCurrentUser" compact class="shrink-0 p-1" />
|
||||
<LoginButton v-else-if="isDesktop" class="p-1" />
|
||||
</div>
|
||||
<div v-if="isDesktop" class="window-actions-spacer app-drag shrink-0" />
|
||||
@@ -102,21 +107,20 @@ import ScrollPanel from 'primevue/scrollpanel'
|
||||
import SelectButton from 'primevue/selectbutton'
|
||||
import { computed, nextTick, onUpdated, ref, watch } from 'vue'
|
||||
import type { WatchStopHandle } from 'vue'
|
||||
|
||||
import CurrentUserButton from '@/components/topbar/CurrentUserButton.vue'
|
||||
import LoginButton from '@/components/topbar/LoginButton.vue'
|
||||
import TopMenuHelpButton from '@/components/topbar/TopMenuHelpButton.vue'
|
||||
import WorkflowTab from '@/components/topbar/WorkflowTab.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
|
||||
import { useOverflowObserver } from '@/composables/element/useOverflowObserver'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { buildFeedbackUrl } from '@/platform/support/config'
|
||||
import { useWorkflowService } from '@/platform/workflow/core/services/workflowService'
|
||||
import type { ComfyWorkflow } from '@/platform/workflow/management/stores/workflowStore'
|
||||
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
import { isDesktop } from '@/platform/distribution/types'
|
||||
import { isCloud, isDesktop, isNightly } from '@/platform/distribution/types'
|
||||
import { whileMouseDown } from '@/utils/mouseDownUtil'
|
||||
|
||||
import WorkflowOverflowMenu from './WorkflowOverflowMenu.vue'
|
||||
@@ -138,8 +142,14 @@ const commandStore = useCommandStore()
|
||||
const { isLoggedIn } = useCurrentUser()
|
||||
|
||||
const isIntegratedTabBar = computed(
|
||||
() => settingStore.get('Comfy.UI.TabBarLayout') === 'Integrated'
|
||||
() => settingStore.get('Comfy.UI.TabBarLayout') !== 'Legacy'
|
||||
)
|
||||
const showCurrentUser = computed(() => isCloud || isLoggedIn.value)
|
||||
|
||||
const feedbackUrl = buildFeedbackUrl()
|
||||
function openFeedback() {
|
||||
window.open(feedbackUrl, '_blank', 'noopener,noreferrer')
|
||||
}
|
||||
|
||||
const containerRef = ref<HTMLElement | null>(null)
|
||||
const showOverflowArrows = ref(false)
|
||||
|
||||
@@ -397,8 +397,7 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
if (app.canvas.empty) {
|
||||
toastStore.add({
|
||||
severity: 'error',
|
||||
summary: t('toastMessages.emptyCanvas'),
|
||||
life: 3000
|
||||
summary: t('toastMessages.emptyCanvas')
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -557,8 +556,7 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
toastStore.add({
|
||||
severity: 'error',
|
||||
summary: t('toastMessages.nothingToQueue'),
|
||||
detail: t('toastMessages.pleaseSelectOutputNodes'),
|
||||
life: 3000
|
||||
detail: t('toastMessages.pleaseSelectOutputNodes')
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -571,8 +569,7 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
toastStore.add({
|
||||
severity: 'error',
|
||||
summary: t('toastMessages.failedToQueue'),
|
||||
detail: t('toastMessages.failedExecutionPathResolution'),
|
||||
life: 3000
|
||||
detail: t('toastMessages.failedExecutionPathResolution')
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -602,8 +599,7 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
toastStore.add({
|
||||
severity: 'error',
|
||||
summary: t('toastMessages.nothingToGroup'),
|
||||
detail: t('toastMessages.pleaseSelectNodesToGroup'),
|
||||
life: 3000
|
||||
detail: t('toastMessages.pleaseSelectNodesToGroup')
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -962,8 +958,7 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
toastStore.add({
|
||||
severity: 'error',
|
||||
summary: t('g.error'),
|
||||
detail: t('manager.notAvailable'),
|
||||
life: 3000
|
||||
detail: t('manager.notAvailable')
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -1048,8 +1043,7 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
toastStore.add({
|
||||
severity: 'error',
|
||||
summary: t('toastMessages.cannotCreateSubgraph'),
|
||||
detail: t('toastMessages.failedToConvertToSubgraph'),
|
||||
life: 3000
|
||||
detail: t('toastMessages.failedToConvertToSubgraph')
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -1258,8 +1252,7 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
summary: t('g.error'),
|
||||
detail: t('g.commandProhibited', {
|
||||
command: 'Comfy.Memory.UnloadModels'
|
||||
}),
|
||||
life: 3000
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -1278,8 +1271,7 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
summary: t('g.error'),
|
||||
detail: t('g.commandProhibited', {
|
||||
command: 'Comfy.Memory.UnloadModelsAndExecutionCache'
|
||||
}),
|
||||
life: 3000
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -5,19 +5,15 @@ import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { useTelemetry } from '@/platform/telemetry'
|
||||
import { useReleaseStore } from '@/platform/updates/common/releaseStore'
|
||||
import { useHelpCenterStore } from '@/stores/helpCenterStore'
|
||||
import type { HelpCenterTriggerLocation } from '@/stores/helpCenterStore'
|
||||
import { useConflictAcknowledgment } from '@/workbench/extensions/manager/composables/useConflictAcknowledgment'
|
||||
import { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection'
|
||||
import { useNodeConflictDialog } from '@/workbench/extensions/manager/composables/useNodeConflictDialog'
|
||||
|
||||
export function useHelpCenter(
|
||||
triggerFrom: HelpCenterTriggerLocation = 'sidebar'
|
||||
) {
|
||||
export function useHelpCenter() {
|
||||
const settingStore = useSettingStore()
|
||||
const releaseStore = useReleaseStore()
|
||||
const helpCenterStore = useHelpCenterStore()
|
||||
const { isVisible: isHelpCenterVisible, triggerLocation } =
|
||||
storeToRefs(helpCenterStore)
|
||||
const { isVisible: isHelpCenterVisible } = storeToRefs(helpCenterStore)
|
||||
const { shouldShowRedDot: showReleaseRedDot } = storeToRefs(releaseStore)
|
||||
|
||||
const conflictDetection = useConflictDetection()
|
||||
@@ -42,9 +38,9 @@ export function useHelpCenter(
|
||||
*/
|
||||
const toggleHelpCenter = () => {
|
||||
useTelemetry()?.trackUiButtonClicked({
|
||||
button_id: `${triggerFrom}_help_center_toggled`
|
||||
button_id: 'sidebar_help_center_toggled'
|
||||
})
|
||||
helpCenterStore.toggle(triggerFrom)
|
||||
helpCenterStore.toggle()
|
||||
}
|
||||
|
||||
const closeHelpCenter = () => {
|
||||
@@ -90,7 +86,6 @@ export function useHelpCenter(
|
||||
|
||||
return {
|
||||
isHelpCenterVisible,
|
||||
triggerLocation,
|
||||
shouldShowRedDot,
|
||||
sidebarLocation,
|
||||
toggleHelpCenter,
|
||||
|
||||
@@ -81,8 +81,7 @@ function getParentNodes(): SubgraphNode[] {
|
||||
useToastStore().add({
|
||||
severity: 'error',
|
||||
summary: t('g.error'),
|
||||
detail: t('subgraphStore.promoteOutsideSubgraph'),
|
||||
life: 2000
|
||||
detail: t('subgraphStore.promoteOutsideSubgraph')
|
||||
})
|
||||
return []
|
||||
}
|
||||
|
||||
161
src/core/graph/subgraph/resolveSubgraphInputTarget.test.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest'
|
||||
|
||||
import { resolveSubgraphInputTarget } from '@/core/graph/subgraph/resolveSubgraphInputTarget'
|
||||
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import {
|
||||
createTestSubgraph,
|
||||
createTestSubgraphNode
|
||||
} from '@/lib/litegraph/src/subgraph/__fixtures__/subgraphHelpers'
|
||||
import type { Subgraph } from '@/lib/litegraph/src/subgraph/Subgraph'
|
||||
import type { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode'
|
||||
|
||||
vi.mock('@/renderer/core/canvas/canvasStore', () => ({
|
||||
useCanvasStore: () => ({})
|
||||
}))
|
||||
vi.mock('@/stores/domWidgetStore', () => ({
|
||||
useDomWidgetStore: () => ({ widgetStates: new Map() })
|
||||
}))
|
||||
vi.mock('@/services/litegraphService', () => ({
|
||||
useLitegraphService: () => ({ updatePreviews: () => ({}) })
|
||||
}))
|
||||
|
||||
function createOuterSubgraphSetup(inputNames: string[]): {
|
||||
outerSubgraph: Subgraph
|
||||
outerSubgraphNode: SubgraphNode
|
||||
} {
|
||||
const outerSubgraph = createTestSubgraph({
|
||||
inputs: inputNames.map((name) => ({ name, type: '*' }))
|
||||
})
|
||||
const outerSubgraphNode = createTestSubgraphNode(outerSubgraph, { id: 1 })
|
||||
return { outerSubgraph, outerSubgraphNode }
|
||||
}
|
||||
|
||||
function addLinkedNestedSubgraphNode(
|
||||
outerSubgraph: Subgraph,
|
||||
inputName: string,
|
||||
linkedInputName: string,
|
||||
options: { widget?: string } = {}
|
||||
): { innerSubgraphNode: SubgraphNode } {
|
||||
const innerSubgraph = createTestSubgraph({
|
||||
inputs: [{ name: linkedInputName, type: '*' }]
|
||||
})
|
||||
const innerSubgraphNode = createTestSubgraphNode(innerSubgraph, { id: 819 })
|
||||
outerSubgraph.add(innerSubgraphNode)
|
||||
|
||||
const inputSlot = outerSubgraph.inputNode.slots.find(
|
||||
(slot) => slot.name === inputName
|
||||
)
|
||||
if (!inputSlot) throw new Error(`Missing subgraph input slot: ${inputName}`)
|
||||
|
||||
const input = innerSubgraphNode.addInput(linkedInputName, '*')
|
||||
if (options.widget) {
|
||||
innerSubgraphNode.addWidget('number', options.widget, 0, () => undefined)
|
||||
input.widget = { name: options.widget }
|
||||
}
|
||||
inputSlot.connect(input, innerSubgraphNode)
|
||||
|
||||
if (input.link == null) {
|
||||
throw new Error(`Expected link to be created for input ${linkedInputName}`)
|
||||
}
|
||||
|
||||
return { innerSubgraphNode }
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('resolveSubgraphInputTarget', () => {
|
||||
test('returns target for widget-backed input on nested SubgraphNode', () => {
|
||||
const { outerSubgraph, outerSubgraphNode } = createOuterSubgraphSetup([
|
||||
'width'
|
||||
])
|
||||
addLinkedNestedSubgraphNode(outerSubgraph, 'width', 'width', {
|
||||
widget: 'width'
|
||||
})
|
||||
|
||||
const result = resolveSubgraphInputTarget(outerSubgraphNode, 'width')
|
||||
|
||||
expect(result).toMatchObject({
|
||||
nodeId: '819',
|
||||
widgetName: 'width'
|
||||
})
|
||||
})
|
||||
|
||||
test('returns undefined for non-widget input on nested SubgraphNode', () => {
|
||||
const { outerSubgraph, outerSubgraphNode } = createOuterSubgraphSetup([
|
||||
'audio'
|
||||
])
|
||||
addLinkedNestedSubgraphNode(outerSubgraph, 'audio', 'audio')
|
||||
|
||||
const result = resolveSubgraphInputTarget(outerSubgraphNode, 'audio')
|
||||
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
test('resolves widget inputs but not non-widget inputs on the same nested SubgraphNode', () => {
|
||||
const { outerSubgraph, outerSubgraphNode } = createOuterSubgraphSetup([
|
||||
'width',
|
||||
'audio'
|
||||
])
|
||||
addLinkedNestedSubgraphNode(outerSubgraph, 'width', 'width', {
|
||||
widget: 'width'
|
||||
})
|
||||
addLinkedNestedSubgraphNode(outerSubgraph, 'audio', 'audio')
|
||||
|
||||
expect(
|
||||
resolveSubgraphInputTarget(outerSubgraphNode, 'width')
|
||||
).toMatchObject({
|
||||
nodeId: '819',
|
||||
widgetName: 'width'
|
||||
})
|
||||
expect(
|
||||
resolveSubgraphInputTarget(outerSubgraphNode, 'audio')
|
||||
).toBeUndefined()
|
||||
})
|
||||
|
||||
test('returns target for widget-backed input on plain interior node', () => {
|
||||
const { outerSubgraph, outerSubgraphNode } = createOuterSubgraphSetup([
|
||||
'seed'
|
||||
])
|
||||
|
||||
const inputSlot = outerSubgraph.inputNode.slots.find(
|
||||
(slot) => slot.name === 'seed'
|
||||
)!
|
||||
const node = new LGraphNode('Interior-seed')
|
||||
node.id = 42
|
||||
const input = node.addInput('seed_input', '*')
|
||||
node.addWidget('number', 'seed', 0, () => undefined)
|
||||
input.widget = { name: 'seed' }
|
||||
outerSubgraph.add(node)
|
||||
inputSlot.connect(input, node)
|
||||
|
||||
const result = resolveSubgraphInputTarget(outerSubgraphNode, 'seed')
|
||||
|
||||
expect(result).toMatchObject({
|
||||
nodeId: '42',
|
||||
widgetName: 'seed'
|
||||
})
|
||||
})
|
||||
|
||||
test('returns undefined for non-widget input on plain interior node', () => {
|
||||
const { outerSubgraph, outerSubgraphNode } = createOuterSubgraphSetup([
|
||||
'image'
|
||||
])
|
||||
|
||||
const inputSlot = outerSubgraph.inputNode.slots.find(
|
||||
(slot) => slot.name === 'image'
|
||||
)!
|
||||
const node = new LGraphNode('Interior-image')
|
||||
const input = node.addInput('image_input', '*')
|
||||
outerSubgraph.add(node)
|
||||
inputSlot.connect(input, node)
|
||||
|
||||
const result = resolveSubgraphInputTarget(outerSubgraphNode, 'image')
|
||||
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
})
|
||||
@@ -16,6 +16,9 @@ export function resolveSubgraphInputTarget(
|
||||
inputName,
|
||||
({ inputNode, targetInput, getTargetWidget }) => {
|
||||
if (inputNode.isSubgraphNode()) {
|
||||
const targetWidget = getTargetWidget()
|
||||
if (!targetWidget) return undefined
|
||||
|
||||
return {
|
||||
nodeId: String(inputNode.id),
|
||||
widgetName: targetInput.name
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { computed, watch } from 'vue'
|
||||
import { remoteConfig } from '@/platform/remoteConfig/remoteConfig'
|
||||
import { t } from '@/i18n'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import { useExtensionService } from '@/services/extensionService'
|
||||
import type { TopbarBadge } from '@/types/comfy'
|
||||
|
||||
@@ -17,16 +18,20 @@ const badges = computed<TopbarBadge[]>(() => {
|
||||
tooltip: alert.tooltip
|
||||
})
|
||||
}
|
||||
|
||||
// Always add cloud badge last (furthest right)
|
||||
result.push({
|
||||
icon: 'icon-[lucide--cloud]',
|
||||
text: 'Comfy Cloud'
|
||||
})
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
const canvasStore = useCanvasStore()
|
||||
watch(
|
||||
() => canvasStore.canvas,
|
||||
(canvas) => {
|
||||
if (canvas) {
|
||||
canvas.info_text = t('g.comfyCloud')
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
useExtensionService().registerExtension({
|
||||
name: 'Comfy.Cloud.Badges',
|
||||
get topbarBadges() {
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
import { t } from '@/i18n'
|
||||
import { getDistribution, ZENDESK_FIELDS } from '@/platform/support/config'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { buildFeedbackUrl } from '@/platform/support/config'
|
||||
import { useExtensionService } from '@/services/extensionService'
|
||||
import type { ActionBarButton } from '@/types/comfy'
|
||||
|
||||
const ZENDESK_BASE_URL = 'https://support.comfy.org/hc/en-us/requests/new'
|
||||
const ZENDESK_FEEDBACK_FORM_ID = '43066738713236'
|
||||
|
||||
const distribution = getDistribution()
|
||||
const params = new URLSearchParams({
|
||||
ticket_form_id: ZENDESK_FEEDBACK_FORM_ID,
|
||||
[ZENDESK_FIELDS.DISTRIBUTION]: distribution
|
||||
})
|
||||
const feedbackUrl = `${ZENDESK_BASE_URL}?${params.toString()}`
|
||||
const feedbackUrl = buildFeedbackUrl()
|
||||
|
||||
const buttons: ActionBarButton[] = [
|
||||
{
|
||||
icon: 'icon-[lucide--message-circle-question-mark]',
|
||||
icon: 'icon-[lucide--message-square-text]',
|
||||
label: t('actionbar.feedback'),
|
||||
tooltip: t('actionbar.feedbackTooltip'),
|
||||
onClick: () => {
|
||||
@@ -25,6 +18,10 @@ const buttons: ActionBarButton[] = [
|
||||
]
|
||||
|
||||
useExtensionService().registerExtension({
|
||||
name: 'Comfy.Cloud.FeedbackButton',
|
||||
actionBarButtons: buttons
|
||||
name: 'Comfy.FeedbackButton',
|
||||
get actionBarButtons() {
|
||||
return useSettingStore().get('Comfy.UI.TabBarLayout') === 'Legacy'
|
||||
? buttons
|
||||
: []
|
||||
}
|
||||
})
|
||||
|
||||
@@ -204,8 +204,7 @@ import { electronAPI as getElectronAPI } from '@/utils/envUtil'
|
||||
toastStore.add({
|
||||
severity: 'error',
|
||||
summary: t('g.error'),
|
||||
detail: t('desktopUpdate.errorInstallingUpdate'),
|
||||
life: 10_000
|
||||
detail: t('desktopUpdate.errorInstallingUpdate')
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -214,8 +213,7 @@ import { electronAPI as getElectronAPI } from '@/utils/envUtil'
|
||||
toastStore.add({
|
||||
severity: 'error',
|
||||
summary: t('g.error'),
|
||||
detail: t('desktopUpdate.errorCheckingUpdate'),
|
||||
life: 10_000
|
||||
detail: t('desktopUpdate.errorCheckingUpdate')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -559,6 +559,8 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
clear_background_color: string
|
||||
render_only_selected: boolean
|
||||
show_info: boolean
|
||||
/** Additional text appended to the canvas info overlay (rendered by {@link renderInfo}). */
|
||||
info_text: string | undefined
|
||||
allow_dragcanvas: boolean
|
||||
allow_dragnodes: boolean
|
||||
allow_interaction: boolean
|
||||
@@ -5180,8 +5182,10 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
* draws some useful stats in the corner of the canvas
|
||||
*/
|
||||
renderInfo(ctx: CanvasRenderingContext2D, x: number, y: number): void {
|
||||
const lineHeight = 13
|
||||
const lineCount = (this.graph ? 5 : 1) + (this.info_text ? 1 : 0)
|
||||
x = x || 10
|
||||
y = y || this.canvas.offsetHeight - 80
|
||||
y = y || this.canvas.offsetHeight - (lineCount + 1) * lineHeight
|
||||
|
||||
ctx.save()
|
||||
ctx.translate(x, y)
|
||||
@@ -5189,18 +5193,26 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
ctx.font = `10px ${LiteGraph.DEFAULT_FONT}`
|
||||
ctx.fillStyle = '#888'
|
||||
ctx.textAlign = 'left'
|
||||
let line = 1
|
||||
if (this.graph) {
|
||||
ctx.fillText(`T: ${this.graph.globaltime.toFixed(2)}s`, 5, 13 * 1)
|
||||
ctx.fillText(`I: ${this.graph.iteration}`, 5, 13 * 2)
|
||||
ctx.fillText(
|
||||
`T: ${this.graph.globaltime.toFixed(2)}s`,
|
||||
5,
|
||||
lineHeight * line++
|
||||
)
|
||||
ctx.fillText(`I: ${this.graph.iteration}`, 5, lineHeight * line++)
|
||||
ctx.fillText(
|
||||
`N: ${this.graph._nodes.length} [${this.visible_nodes.length}]`,
|
||||
5,
|
||||
13 * 3
|
||||
lineHeight * line++
|
||||
)
|
||||
ctx.fillText(`V: ${this.graph._version}`, 5, 13 * 4)
|
||||
ctx.fillText(`FPS:${this.fps.toFixed(2)}`, 5, 13 * 5)
|
||||
ctx.fillText(`V: ${this.graph._version}`, 5, lineHeight * line++)
|
||||
ctx.fillText(`FPS:${this.fps.toFixed(2)}`, 5, lineHeight * line++)
|
||||
} else {
|
||||
ctx.fillText('No graph selected', 5, 13 * 1)
|
||||
ctx.fillText('No graph selected', 5, lineHeight * line++)
|
||||
}
|
||||
if (this.info_text) {
|
||||
ctx.fillText(this.info_text, 5, lineHeight * line++)
|
||||
}
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
@@ -71,6 +71,9 @@
|
||||
"Comfy_Canvas_PasteFromClipboard": {
|
||||
"label": "لصق"
|
||||
},
|
||||
"Comfy_Canvas_PasteFromClipboardWithConnect": {
|
||||
"label": "لصق مع الاتصال"
|
||||
},
|
||||
"Comfy_Canvas_ResetView": {
|
||||
"label": "إعادة تعيين العرض"
|
||||
},
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
"actionbar": {
|
||||
"dockToTop": "إلصق بالأعلى",
|
||||
"feedback": "ملاحظات",
|
||||
"feedbackTooltip": "إرسال ملاحظات"
|
||||
"feedbackTooltip": "إرسال ملاحظات",
|
||||
"share": "مشاركة",
|
||||
"shareTooltip": "مشاركة سير العمل"
|
||||
},
|
||||
"apiNodesCostBreakdown": {
|
||||
"costPerRun": "التكلفة لكل تشغيل",
|
||||
@@ -321,18 +323,24 @@
|
||||
"y": "ص"
|
||||
},
|
||||
"breadcrumbsMenu": {
|
||||
"app": "التطبيق",
|
||||
"clearWorkflow": "مسح سير العمل",
|
||||
"deleteBlueprint": "حذف المخطط",
|
||||
"deleteWorkflow": "حذف سير العمل",
|
||||
"duplicate": "تكرار",
|
||||
"editBuilderMode": "تعديل التطبيق",
|
||||
"enterAppMode": "الدخول إلى وضع التطبيق",
|
||||
"enterBuilderMode": "دخول وضع بناء التطبيق",
|
||||
"enterNewName": "أدخل اسمًا جديدًا",
|
||||
"enterNodeGraph": "دخول رسم العقد",
|
||||
"exitAppMode": "الخروج من وضع التطبيق",
|
||||
"graph": "الرسم البياني",
|
||||
"missingNodesWarning": "يحتوي سير العمل على عقد غير مدعومة (مظللة باللون الأحمر).",
|
||||
"share": "مشاركة",
|
||||
"workflowActions": "إجراءات سير العمل"
|
||||
},
|
||||
"builderMenu": {
|
||||
"enterAppMode": "الدخول إلى وضع التطبيق",
|
||||
"exitAppBuilder": "الخروج من مُنشئ التطبيق"
|
||||
},
|
||||
"builderToolbar": {
|
||||
@@ -354,6 +362,7 @@
|
||||
"defaultViewTitle": "تعيين العرض الافتراضي لهذا سير العمل",
|
||||
"emptyWorkflowPrompt": "هل ترغب في البدء بقالب؟",
|
||||
"emptyWorkflowTitle": "لا يحتوي سير العمل هذا على أي عقد",
|
||||
"exitToWorkflow": "الخروج إلى سير العمل",
|
||||
"inputs": "المدخلات",
|
||||
"inputsDescription": "اختر المدخلات",
|
||||
"label": "منشئ التطبيقات",
|
||||
@@ -507,6 +516,80 @@
|
||||
"red": "أحمر",
|
||||
"yellow": "أصفر"
|
||||
},
|
||||
"comfyHubProfile": {
|
||||
"checkingAccess": "جارٍ التحقق من صلاحية النشر...",
|
||||
"chooseProfilePicture": "اختر صورة الملف الشخصي",
|
||||
"createProfile": "إنشاء الملف الشخصي",
|
||||
"createProfileButton": "إنشاء ملفي الشخصي",
|
||||
"createProfileTitle": "أنشئ ملفك الشخصي على Comfy Hub",
|
||||
"creatingProfile": "جارٍ إنشاء الملف الشخصي...",
|
||||
"descriptionLabel": "وصفك",
|
||||
"descriptionPlaceholder": "أخبر المجتمع عن نفسك...",
|
||||
"introDescription": "انشر سير عملك، وابنِ محفظتك، واكتشفك ملايين المستخدمين",
|
||||
"introSubtitle": "لمشاركة سير عملك على ComfyHub، لنقم أولاً بإنشاء ملفك الشخصي.",
|
||||
"introTitle": "النشر على ComfyHub",
|
||||
"modalTitle": "إنشاء ملفك الشخصي على ComfyHub",
|
||||
"nameLabel": "اسمك",
|
||||
"namePlaceholder": "أدخل اسمك هنا",
|
||||
"profileCreationNav": "إنشاء الملف الشخصي",
|
||||
"startPublishingButton": "ابدأ النشر",
|
||||
"successDescription": "يمكنك الآن رفع سير عملك على صفحة المبدع الخاصة بك",
|
||||
"successProfileLink": "comfy.com/p/{username}",
|
||||
"successProfileUrl": "صفحتك الشخصية متاحة الآن على",
|
||||
"successTitle": "يبدو رائعًا، {'@'}{username}!",
|
||||
"uploadCover": "+ رفع صورة الغلاف",
|
||||
"uploadProfilePicture": "+ رفع صورة الملف الشخصي",
|
||||
"uploadWorkflowButton": "رفع سير عملي",
|
||||
"usernameLabel": "اسم المستخدم (إجباري)",
|
||||
"usernamePlaceholder": "@"
|
||||
},
|
||||
"comfyHubPublish": {
|
||||
"back": "رجوع",
|
||||
"createProfileCta": "إنشاء ملف شخصي",
|
||||
"createProfileToPublish": "أنشئ ملفًا شخصيًا للنشر على ComfyHub",
|
||||
"exampleImage": "صورة نموذجية {index}",
|
||||
"examplesDescription": "أضف حتى {total} صورة نموذجية إضافية",
|
||||
"maxExamples": "يمكنك اختيار حتى {max} أمثلة",
|
||||
"next": "التالي",
|
||||
"publishButton": "النشر على ComfyHub",
|
||||
"selectAThumbnail": "اختر صورة مصغرة",
|
||||
"showLessTags": "عرض أقل...",
|
||||
"showMoreTags": "عرض المزيد...",
|
||||
"stepDescribe": "وصف سير العمل",
|
||||
"stepExamples": "إضافة أمثلة للإخراج",
|
||||
"stepFinish": "إنهاء النشر",
|
||||
"suggestedTags": "وسوم مقترحة",
|
||||
"tags": "الوسوم",
|
||||
"tagsDescription": "اختر الوسوم ليسهل على الآخرين العثور على سير عملك",
|
||||
"tagsPlaceholder": "أدخل وسومًا تناسب سير عملك لمساعدة الآخرين في العثور عليه مثل #nanobanana أو #anime أو #faceswap",
|
||||
"thumbnailImage": "صورة",
|
||||
"thumbnailImageComparison": "مقارنة الصور",
|
||||
"thumbnailPreview": "معاينة الصورة المصغرة",
|
||||
"thumbnailVideo": "فيديو",
|
||||
"title": "النشر على ComfyHub",
|
||||
"uploadAnImage": "انقر للاستعراض أو اسحب صورة",
|
||||
"uploadComparison": "رفع صورة قبل وبعد",
|
||||
"uploadComparisonAfterPrompt": "بعد",
|
||||
"uploadComparisonBeforePrompt": "قبل",
|
||||
"uploadExampleImage": "رفع صورة نموذجية",
|
||||
"uploadPromptClickToBrowse": "انقر للاستعراض أو",
|
||||
"uploadPromptDropImage": "أسقط صورة هنا",
|
||||
"uploadPromptDropVideo": "أسقط فيديو هنا",
|
||||
"uploadThumbnail": "رفع صورة",
|
||||
"uploadThumbnailHint": "يفضل نسبة 1:1، الحد الأقصى 1080p",
|
||||
"uploadVideo": "رفع فيديو",
|
||||
"videoPreview": "معاينة صورة الفيديو المصغرة",
|
||||
"workflowDescription": "وصف سير العمل",
|
||||
"workflowDescriptionPlaceholder": "ما الذي يجعل سير عملك مميزًا ومثيرًا؟ كن محددًا حتى يعرف الآخرون ما يمكن توقعه.",
|
||||
"workflowName": "اسم سير العمل",
|
||||
"workflowNamePlaceholder": "نصيحة: أدخل اسمًا وصفيًا يسهل البحث عنه",
|
||||
"workflowType": "نوع سير العمل",
|
||||
"workflowTypeEditing": "تحرير",
|
||||
"workflowTypeImageGeneration": "توليد الصور",
|
||||
"workflowTypePlaceholder": "اختر النوع",
|
||||
"workflowTypeUpscaling": "تحسين الجودة",
|
||||
"workflowTypeVideoGeneration": "توليد الفيديو"
|
||||
},
|
||||
"commands": {
|
||||
"clear": "مسح سير العمل",
|
||||
"clipspace": "فتح مساحة القص",
|
||||
@@ -869,6 +952,7 @@
|
||||
"collapseAll": "طي الكل",
|
||||
"color": "اللون",
|
||||
"comfy": "Comfy",
|
||||
"comfyCloud": "Comfy Cloud",
|
||||
"comfyOrgLogoAlt": "شعار ComfyOrg",
|
||||
"comingSoon": "قريباً",
|
||||
"command": "أمر",
|
||||
@@ -1342,7 +1426,7 @@
|
||||
"appBuilder": "منشئ التطبيقات",
|
||||
"apps": "التطبيقات",
|
||||
"appsEmptyMessage": "سيتم عرض التطبيقات المحفوظة هنا.\nانقر أدناه لبناء تطبيقك الأول.",
|
||||
"enterAppMode": "الدخول إلى وضع التطبيق"
|
||||
"appsEmptyMessageAction": "انقر أدناه لإنشاء أول تطبيق لك."
|
||||
},
|
||||
"arrange": {
|
||||
"atLeastOne": "عقدة واحدة على الأقل",
|
||||
@@ -1356,14 +1440,18 @@
|
||||
},
|
||||
"backToWorkflow": "العودة إلى سير العمل",
|
||||
"beta": "وضع التطبيق تجريبي - أرسل ملاحظاتك",
|
||||
"buildAnApp": "أنشئ تطبيقًا",
|
||||
"builder": {
|
||||
"exit": "خروج من البناء",
|
||||
"exitConfirmMessage": "لديك تغييرات غير محفوظة ستفقد\nهل تريد الخروج بدون حفظ؟",
|
||||
"exitConfirmTitle": "الخروج من بناء التطبيق؟",
|
||||
"inputPlaceholder": "سيتم عرض المدخلات هنا",
|
||||
"inputsDesc": "سيتفاعل المستخدمون مع هذه المدخلات ويعدلونها لإنشاء النتائج.",
|
||||
"inputsExample": "أمثلة: \"تحميل صورة\"، \"موجه نصي\"، \"خطوات\"",
|
||||
"noInputs": "لم تتم إضافة أي مدخلات بعد",
|
||||
"noOutputs": "لم تتم إضافة أي عقد إخراج بعد",
|
||||
"outputPlaceholder": "سيتم عرض عقد الإخراج هنا",
|
||||
"outputRequiredPlaceholder": "مطلوب عقدة واحدة على الأقل",
|
||||
"outputsDesc": "وصل عقدة إخراج واحدة على الأقل حتى يتمكن المستخدمون من رؤية النتائج بعد التشغيل.",
|
||||
"outputsExample": "أمثلة: \"حفظ صورة\" أو \"حفظ فيديو\"",
|
||||
"promptAddInputs": "انقر على معلمات العقدة لإضافتها هنا كمدخلات",
|
||||
@@ -1371,12 +1459,15 @@
|
||||
"title": "وضع بناء التطبيق",
|
||||
"unknownWidget": "عنصر الواجهة غير مرئي"
|
||||
},
|
||||
"cancelThisRun": "إلغاء هذا التشغيل",
|
||||
"deleteAllAssets": "حذف جميع الأصول من هذه الجلسة",
|
||||
"downloadAll": "تنزيل الكل",
|
||||
"dragAndDropImage": "اسحب وأسقط صورة",
|
||||
"emptyWorkflowExplanation": "سير العمل الخاص بك فارغ. تحتاج إلى بعض العقد أولاً لبدء بناء التطبيق.",
|
||||
"enterNodeGraph": "دخول مخطط العقد",
|
||||
"giveFeedback": "إعطاء ملاحظات",
|
||||
"graphMode": "وضع الرسم البياني",
|
||||
"hasCreditCost": "يتطلب أرصدة إضافية",
|
||||
"linearMode": "وضع التطبيق",
|
||||
"loadTemplate": "تحميل قالب",
|
||||
"mobileControls": "تعديل وتشغيل",
|
||||
@@ -1393,6 +1484,8 @@
|
||||
"controls": "تظهر المخرجات في الأسفل، وعناصر التحكم على اليمين. كل شيء آخر يبقى بعيدًا.",
|
||||
"getStarted": "انقر على {runButton} للبدء.",
|
||||
"message": "عرض مبسط يخفي رسم العقد حتى تتمكن من التركيز على الإنشاء.",
|
||||
"noOutputs": "يحتاج التطبيق إلى {count} على الأقل ليكون قابلاً للاستخدام.",
|
||||
"oneOutput": "مخرج واحد",
|
||||
"sharing": "المشاركة سهلة: أنشئ سير العمل الخاص بك، افتح وضع التطبيق، انقر بزر الماوس الأيمن على علامة التبويب، ثم صدّر. عندما يفتح الآخرون ملفك، سيتم تشغيله مباشرة في هذا العرض النظيف. يمكنك مشاركة سير عمل قوي كأداة بسيطة دون الحاجة لفهم مخططات العقد.",
|
||||
"title": "مرحبًا بك في وضع التطبيق"
|
||||
}
|
||||
@@ -1765,7 +1858,6 @@
|
||||
"execute": "تنفيذ",
|
||||
"fullscreen": "ملء الشاشة",
|
||||
"help": "مساعدة",
|
||||
"helpAndFeedback": "المساعدة والتعليقات",
|
||||
"hideMenu": "إخفاء القائمة",
|
||||
"instant": "فوري",
|
||||
"instantTooltip": "سيتم وضع سير العمل في قائمة الانتظار فور انتهاء التوليد",
|
||||
@@ -1866,6 +1958,7 @@
|
||||
"Open Sign In Dialog": "فتح نافذة تسجيل الدخول",
|
||||
"Open extra_model_paths_yaml": "فتح ملف extra_model_paths.yaml",
|
||||
"Paste": "لصق",
|
||||
"Paste with Connect": "لصق مع الاتصال",
|
||||
"Pin/Unpin Selected Items": "تثبيت/إلغاء تثبيت العناصر المحددة",
|
||||
"Pin/Unpin Selected Nodes": "تثبيت/إلغاء تثبيت العقد المحددة",
|
||||
"Previous Opened Workflow": "سير العمل السابق المفتوح",
|
||||
@@ -2037,6 +2130,7 @@
|
||||
"lotus": "lotus",
|
||||
"ltxv": "ltxv",
|
||||
"mask": "قناع",
|
||||
"math": "رياضيات",
|
||||
"model": "نموذج",
|
||||
"model_merging": "دمج النماذج",
|
||||
"model_patches": "تصحيحات النموذج",
|
||||
@@ -2140,6 +2234,18 @@
|
||||
},
|
||||
"title": "جهازك غير مدعوم"
|
||||
},
|
||||
"openSharedWorkflow": {
|
||||
"author": "المؤلف:",
|
||||
"copyAssetsAndOpen": "استيراد الأصول وفتح سير العمل",
|
||||
"copyDescription": "فتح سير العمل سينشئ نسخة جديدة في مساحة العمل الخاصة بك",
|
||||
"dialogTitle": "فتح سير العمل المشترك",
|
||||
"importFailed": "فشل استيراد أصول سير العمل",
|
||||
"loadError": "تعذر تحميل سير العمل المشترك هذا. يرجى المحاولة لاحقًا.",
|
||||
"nonPublicAssetsWarningLine1": "يأتي هذا سير العمل مع أصول غير عامة.",
|
||||
"nonPublicAssetsWarningLine2": "سيتم استيراد هذه الأصول إلى مكتبتك عند فتح سير العمل",
|
||||
"openWithoutImporting": "فتح بدون استيراد",
|
||||
"openWorkflow": "فتح سير العمل"
|
||||
},
|
||||
"painter": {
|
||||
"background": "الخلفية",
|
||||
"brush": "فرشاة",
|
||||
@@ -2598,6 +2704,47 @@
|
||||
"default": "افتراضي",
|
||||
"round": "دائري"
|
||||
},
|
||||
"shareNoOutputs": {
|
||||
"message": "أنت على وشك مشاركة تطبيق بدون مخرجات. لا يمكن استخدامه حتى يتم توصيل مخرج.\n\nهل ترغب في المشاركة على أي حال؟",
|
||||
"shareAnyway": "مشاركة على أي حال",
|
||||
"title": "التطبيق لا يحتوي على مخرجات"
|
||||
},
|
||||
"shareWorkflow": {
|
||||
"acknowledgeCheckbox": "أفهم أن عناصر الوسائط هذه سيتم نشرها وجعلها عامة",
|
||||
"checkingAssets": "جارٍ التحقق من ظهور الوسائط…",
|
||||
"comfyHubButton": "رفع إلى ComfyHub",
|
||||
"comfyHubDescription": "ComfyHub هو مركز مجتمع ComfyUI الرسمي.\nسيكون لسير العمل الخاص بك صفحة عامة يمكن للجميع مشاهدتها.",
|
||||
"comfyHubTitle": "رفع إلى ComfyHub",
|
||||
"copyLink": "نسخ",
|
||||
"createLinkButton": "إنشاء رابط",
|
||||
"createLinkDescription": "عند إنشاء رابط لسير العمل الخاص بك، ستشارك عناصر الوسائط هذه مع سير العمل",
|
||||
"createLinkTitle": "مشاركة سير العمل",
|
||||
"creatingLink": "جارٍ إنشاء الرابط...",
|
||||
"hasChangesDescription": "لقد أجريت تغييرات منذ آخر نشر لهذا سير العمل.",
|
||||
"hasChangesTitle": "مشاركة سير العمل",
|
||||
"inLibrary": "في المكتبة",
|
||||
"linkCopied": "تم النسخ!",
|
||||
"loadFailed": "فشل تحميل سير العمل المشترك",
|
||||
"loadingTitle": "مشاركة سير العمل",
|
||||
"mediaLabel": "{count} ملف وسائط | {count} ملفات وسائط",
|
||||
"modelsLabel": "{count} نموذج | {count} نماذج",
|
||||
"privateAssetsDescription": "يحتوي سير العمل الخاص بك على نماذج و/أو ملفات وسائط خاصة",
|
||||
"publishToHubTab": "نشر",
|
||||
"publishedOn": "تم النشر في {date}",
|
||||
"saveButton": "حفظ سير العمل",
|
||||
"saveFailedDescription": "فشل حفظ سير العمل. يرجى المحاولة مرة أخرى.",
|
||||
"saveFailedTitle": "فشل الحفظ",
|
||||
"saving": "جارٍ الحفظ...",
|
||||
"shareLinkTab": "مشاركة",
|
||||
"shareUrlLabel": "رابط المشاركة",
|
||||
"successDescription": "أي شخص لديه هذا الرابط يمكنه عرض واستخدام سير العمل هذا. إذا قمت بإجراء تغييرات على سير العمل، يمكنك إعادة النشر لتحديث النسخة المشتركة.",
|
||||
"successTitle": "تم نشر سير العمل بنجاح!",
|
||||
"unsavedDescription": "يجب عليك حفظ سير العمل قبل المشاركة. احفظه الآن للمتابعة.",
|
||||
"unsavedTitle": "احفظ سير العمل أولاً",
|
||||
"updateLinkButton": "تحديث الرابط",
|
||||
"updatingLink": "جارٍ تحديث الرابط...",
|
||||
"workflowNameLabel": "اسم سير العمل"
|
||||
},
|
||||
"shortcuts": {
|
||||
"essentials": "أساسي",
|
||||
"keyboardShortcuts": "اختصارات لوحة المفاتيح",
|
||||
@@ -3229,10 +3376,7 @@
|
||||
"addedToWorkspace": "تمت إضافتك إلى {workspaceName}",
|
||||
"inviteAccepted": "تم قبول الدعوة",
|
||||
"inviteFailed": "فشل في قبول الدعوة",
|
||||
"unsavedChanges": {
|
||||
"message": "لديك تغييرات غير محفوظة. هل تريد تجاهلها والانتقال إلى مساحة عمل أخرى؟",
|
||||
"title": "تغييرات غير محفوظة"
|
||||
},
|
||||
"switchFailed": "فشل في تبديل مساحة العمل. يرجى المحاولة مرة أخرى.",
|
||||
"viewWorkspace": "عرض مساحة العمل"
|
||||
},
|
||||
"workspaceAuth": {
|
||||
|
||||
@@ -1419,6 +1419,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ComfyMathExpression": {
|
||||
"display_name": "تعبير رياضي",
|
||||
"inputs": {
|
||||
"expression": {
|
||||
"name": "تعبير"
|
||||
},
|
||||
"values": {
|
||||
"name": "القيم"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
},
|
||||
"1": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"ComfySwitchNode": {
|
||||
"display_name": "مفتاح التحويل",
|
||||
"inputs": {
|
||||
@@ -15112,6 +15131,64 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"TencentModelTo3DUVNode": {
|
||||
"description": "تنفيذ فك UV لنموذج ثلاثي الأبعاد لإنشاء نسيج UV. يجب أن يحتوي النموذج المُدخل على أقل من ٣٠٬٠٠٠ وجه.",
|
||||
"display_name": "Hunyuan3D: من نموذج إلى UV",
|
||||
"inputs": {
|
||||
"control_after_generate": {
|
||||
"name": "التحكم بعد التوليد"
|
||||
},
|
||||
"model_3d": {
|
||||
"name": "نموذج_ثلاثي_الأبعاد",
|
||||
"tooltip": "إدخال نموذج ثلاثي الأبعاد (GLB، OBJ، أو FBX)"
|
||||
},
|
||||
"seed": {
|
||||
"name": "البذرة",
|
||||
"tooltip": "تتحكم البذرة فيما إذا كان يجب إعادة تشغيل العقدة؛ النتائج غير حتمية بغض النظر عن البذرة."
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"name": "OBJ",
|
||||
"tooltip": null
|
||||
},
|
||||
"1": {
|
||||
"name": "FBX",
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"TencentSmartTopologyNode": {
|
||||
"description": "تنفيذ إعادة طوبولوجيا ذكية لنموذج ثلاثي الأبعاد. يدعم صيغ GLB/OBJ؛ الحد الأقصى ٢٠٠ ميجابايت؛ يُوصى به للنماذج عالية التفاصيل.",
|
||||
"display_name": "Hunyuan3D: طوبولوجيا ذكية",
|
||||
"inputs": {
|
||||
"control_after_generate": {
|
||||
"name": "التحكم بعد التوليد"
|
||||
},
|
||||
"face_level": {
|
||||
"name": "مستوى الوجوه",
|
||||
"tooltip": "مستوى تقليل المضلعات."
|
||||
},
|
||||
"model_3d": {
|
||||
"name": "نموذج_ثلاثي_الأبعاد",
|
||||
"tooltip": "إدخال نموذج ثلاثي الأبعاد (GLB أو OBJ)"
|
||||
},
|
||||
"polygon_type": {
|
||||
"name": "نوع المضلع",
|
||||
"tooltip": "نوع تركيب السطح."
|
||||
},
|
||||
"seed": {
|
||||
"name": "البذرة",
|
||||
"tooltip": "تتحكم البذرة فيما إذا كان يجب إعادة تشغيل العقدة؛ النتائج غير حتمية بغض النظر عن البذرة."
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"name": "OBJ",
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"TencentTextToModelNode": {
|
||||
"display_name": "Hunyuan3D: من نص إلى نموذج (احترافي)",
|
||||
"inputs": {
|
||||
|
||||
@@ -400,7 +400,7 @@
|
||||
"name": "تخطيط شريط التبويبات",
|
||||
"options": {
|
||||
"Default": "افتراضي",
|
||||
"Integrated": "مُدمج"
|
||||
"Legacy": "تقليدي"
|
||||
},
|
||||
"tooltip": "يتحكم في تخطيط شريط التبويبات. \"مُدمج\" ينقل عناصر المساعدة والتحكمات الخاصة بالمستخدم إلى منطقة شريط التبويبات."
|
||||
},
|
||||
|
||||
@@ -71,6 +71,9 @@
|
||||
"Comfy_Canvas_PasteFromClipboard": {
|
||||
"label": "Paste"
|
||||
},
|
||||
"Comfy_Canvas_PasteFromClipboardWithConnect": {
|
||||
"label": "Paste with Connect"
|
||||
},
|
||||
"Comfy_Canvas_ResetView": {
|
||||
"label": "Reset View"
|
||||
},
|
||||
|
||||
@@ -301,6 +301,7 @@
|
||||
"1x": "1x",
|
||||
"2x": "2x",
|
||||
"beta": "BETA",
|
||||
"comfyCloud": "Comfy Cloud",
|
||||
"nightly": "NIGHTLY",
|
||||
"profile": "Profile",
|
||||
"noItems": "No items",
|
||||
@@ -968,7 +969,6 @@
|
||||
"customNodesManager": "Custom Nodes Manager",
|
||||
"settings": "Settings",
|
||||
"help": "Help",
|
||||
"helpAndFeedback": "Help & Feedback",
|
||||
"queue": "Queue Panel",
|
||||
"fullscreen": "Fullscreen"
|
||||
},
|
||||
@@ -1330,7 +1330,6 @@
|
||||
"Rename": "Rename",
|
||||
"Save": "Save",
|
||||
"Save As": "Save As",
|
||||
"Share": "Share",
|
||||
"Show Settings Dialog": "Show Settings Dialog",
|
||||
"Set Subgraph Description": "Set Subgraph Description",
|
||||
"Set Subgraph Search Aliases": "Set Subgraph Search Aliases",
|
||||
@@ -1597,6 +1596,7 @@
|
||||
"kandinsky5": "kandinsky5",
|
||||
"hooks": "hooks",
|
||||
"combine": "combine",
|
||||
"math": "math",
|
||||
"logic": "logic",
|
||||
"cond single": "cond single",
|
||||
"context": "context",
|
||||
@@ -3178,6 +3178,8 @@
|
||||
"backToWorkflow": "Back to workflow",
|
||||
"loadTemplate": "Load a template",
|
||||
"cancelThisRun": "Cancel this run",
|
||||
"deleteAllAssets": "Delete all assets from this run",
|
||||
"hasCreditCost": "Requires additional credits",
|
||||
"welcome": {
|
||||
"title": "App Mode",
|
||||
"message": "A simplified view that hides the node graph so you can focus on creating.",
|
||||
@@ -3404,14 +3406,11 @@
|
||||
"retryDownload": "Retry download"
|
||||
},
|
||||
"workspace": {
|
||||
"unsavedChanges": {
|
||||
"title": "Unsaved Changes",
|
||||
"message": "You have unsaved changes. Do you want to discard them and switch workspaces?"
|
||||
},
|
||||
"inviteAccepted": "Invite Accepted",
|
||||
"addedToWorkspace": "You have been added to:",
|
||||
"inviteFailed": "Failed to Accept Invite",
|
||||
"viewWorkspace": "View workspace"
|
||||
"viewWorkspace": "View workspace",
|
||||
"switchFailed": "Failed to switch workspace. Please try again."
|
||||
},
|
||||
"workspaceAuth": {
|
||||
"errors": {
|
||||
|
||||
@@ -1419,6 +1419,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ComfyMathExpression": {
|
||||
"display_name": "Math Expression",
|
||||
"inputs": {
|
||||
"expression": {
|
||||
"name": "expression"
|
||||
},
|
||||
"values": {
|
||||
"name": "values"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
},
|
||||
"1": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"ComfySwitchNode": {
|
||||
"display_name": "Switch",
|
||||
"inputs": {
|
||||
@@ -15112,6 +15131,64 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"TencentModelTo3DUVNode": {
|
||||
"display_name": "Hunyuan3D: Model to UV",
|
||||
"description": "Perform UV unfolding on a 3D model to generate UV texture. Input model must have less than 30000 faces.",
|
||||
"inputs": {
|
||||
"model_3d": {
|
||||
"name": "model_3d",
|
||||
"tooltip": "Input 3D model (GLB, OBJ, or FBX)"
|
||||
},
|
||||
"seed": {
|
||||
"name": "seed",
|
||||
"tooltip": "Seed controls whether the node should re-run; results are non-deterministic regardless of seed."
|
||||
},
|
||||
"control_after_generate": {
|
||||
"name": "control after generate"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"name": "OBJ",
|
||||
"tooltip": null
|
||||
},
|
||||
"1": {
|
||||
"name": "FBX",
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"TencentSmartTopologyNode": {
|
||||
"display_name": "Hunyuan3D: Smart Topology",
|
||||
"description": "Perform smart retopology on a 3D model. Supports GLB/OBJ formats; max 200MB; recommended for high-poly models.",
|
||||
"inputs": {
|
||||
"model_3d": {
|
||||
"name": "model_3d",
|
||||
"tooltip": "Input 3D model (GLB or OBJ)"
|
||||
},
|
||||
"polygon_type": {
|
||||
"name": "polygon_type",
|
||||
"tooltip": "Surface composition type."
|
||||
},
|
||||
"face_level": {
|
||||
"name": "face_level",
|
||||
"tooltip": "Polygon reduction level."
|
||||
},
|
||||
"seed": {
|
||||
"name": "seed",
|
||||
"tooltip": "Seed controls whether the node should re-run; results are non-deterministic regardless of seed."
|
||||
},
|
||||
"control_after_generate": {
|
||||
"name": "control after generate"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"name": "OBJ",
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"TencentTextToModelNode": {
|
||||
"display_name": "Hunyuan3D: Text to Model",
|
||||
"inputs": {
|
||||
|
||||
@@ -398,10 +398,10 @@
|
||||
},
|
||||
"Comfy_UI_TabBarLayout": {
|
||||
"name": "Tab Bar Layout",
|
||||
"tooltip": "Controls the layout of the tab bar. \"Integrated\" moves Help and User controls into the tab bar area.",
|
||||
"tooltip": "Controls the elements contained in the integrated tab bar.",
|
||||
"options": {
|
||||
"Default": "Default",
|
||||
"Integrated": "Integrated"
|
||||
"Legacy": "Legacy"
|
||||
}
|
||||
},
|
||||
"Comfy_UseNewMenu": {
|
||||
|
||||
@@ -71,6 +71,9 @@
|
||||
"Comfy_Canvas_PasteFromClipboard": {
|
||||
"label": "Pegar"
|
||||
},
|
||||
"Comfy_Canvas_PasteFromClipboardWithConnect": {
|
||||
"label": "Pegar con conectar"
|
||||
},
|
||||
"Comfy_Canvas_ResetView": {
|
||||
"label": "Restablecer vista"
|
||||
},
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
"actionbar": {
|
||||
"dockToTop": "Acoplar en la parte superior",
|
||||
"feedback": "Comentarios",
|
||||
"feedbackTooltip": "Comentarios"
|
||||
"feedbackTooltip": "Comentarios",
|
||||
"share": "Compartir",
|
||||
"shareTooltip": "Compartir flujo de trabajo"
|
||||
},
|
||||
"apiNodesCostBreakdown": {
|
||||
"costPerRun": "Costo por ejecución",
|
||||
@@ -321,18 +323,24 @@
|
||||
"y": "Y"
|
||||
},
|
||||
"breadcrumbsMenu": {
|
||||
"app": "Aplicación",
|
||||
"clearWorkflow": "Limpiar flujo de trabajo",
|
||||
"deleteBlueprint": "Eliminar Plano",
|
||||
"deleteWorkflow": "Eliminar flujo de trabajo",
|
||||
"duplicate": "Duplicar",
|
||||
"editBuilderMode": "Editar aplicación",
|
||||
"enterAppMode": "Entrar en modo aplicación",
|
||||
"enterBuilderMode": "Entrar al constructor de aplicaciones",
|
||||
"enterNewName": "Ingrese un nuevo nombre",
|
||||
"enterNodeGraph": "Entrar al gráfico de nodos",
|
||||
"exitAppMode": "Salir del modo aplicación",
|
||||
"graph": "Gráfico",
|
||||
"missingNodesWarning": "El flujo de trabajo contiene nodos no compatibles (resaltados en rojo).",
|
||||
"share": "Compartir",
|
||||
"workflowActions": "Acciones del flujo de trabajo"
|
||||
},
|
||||
"builderMenu": {
|
||||
"enterAppMode": "Entrar en modo aplicación",
|
||||
"exitAppBuilder": "Salir del constructor de aplicaciones"
|
||||
},
|
||||
"builderToolbar": {
|
||||
@@ -354,6 +362,7 @@
|
||||
"defaultViewTitle": "Establecer la vista predeterminada para este flujo de trabajo",
|
||||
"emptyWorkflowPrompt": "¿Quieres empezar con una plantilla?",
|
||||
"emptyWorkflowTitle": "Este flujo de trabajo no tiene nodos",
|
||||
"exitToWorkflow": "Salir al flujo de trabajo",
|
||||
"inputs": "Entradas",
|
||||
"inputsDescription": "Elige entradas",
|
||||
"label": "Constructor de aplicaciones",
|
||||
@@ -507,6 +516,80 @@
|
||||
"red": "Rojo",
|
||||
"yellow": "Amarillo"
|
||||
},
|
||||
"comfyHubProfile": {
|
||||
"checkingAccess": "Verificando tu acceso de publicación...",
|
||||
"chooseProfilePicture": "Elige una foto de perfil",
|
||||
"createProfile": "Crear perfil",
|
||||
"createProfileButton": "Crear mi perfil",
|
||||
"createProfileTitle": "Crea tu perfil de Comfy Hub",
|
||||
"creatingProfile": "Creando perfil...",
|
||||
"descriptionLabel": "Tu descripción",
|
||||
"descriptionPlaceholder": "Cuéntale a la comunidad sobre ti...",
|
||||
"introDescription": "Publica tus flujos de trabajo, construye tu portafolio y sé descubierto por millones de usuarios",
|
||||
"introSubtitle": "Para compartir tu flujo de trabajo en ComfyHub, primero creemos tu perfil.",
|
||||
"introTitle": "Publica en ComfyHub",
|
||||
"modalTitle": "Crea tu perfil en ComfyHub",
|
||||
"nameLabel": "Tu nombre",
|
||||
"namePlaceholder": "Ingresa tu nombre aquí",
|
||||
"profileCreationNav": "Creación de perfil",
|
||||
"startPublishingButton": "Comenzar a publicar",
|
||||
"successDescription": "Ahora puedes subir tu flujo de trabajo a tu página de creador",
|
||||
"successProfileLink": "comfy.com/p/{username}",
|
||||
"successProfileUrl": "Tu página de perfil está activa en",
|
||||
"successTitle": "¡Te ves bien, {'@'}{username}!",
|
||||
"uploadCover": "+ Subir una portada",
|
||||
"uploadProfilePicture": "+ Subir una foto de perfil",
|
||||
"uploadWorkflowButton": "Subir mi flujo de trabajo",
|
||||
"usernameLabel": "Tu nombre de usuario (requerido)",
|
||||
"usernamePlaceholder": "@"
|
||||
},
|
||||
"comfyHubPublish": {
|
||||
"back": "Atrás",
|
||||
"createProfileCta": "Crear un perfil",
|
||||
"createProfileToPublish": "Crea un perfil para publicar en ComfyHub",
|
||||
"exampleImage": "Imagen de ejemplo {index}",
|
||||
"examplesDescription": "Agrega hasta {total} imágenes de ejemplo adicionales",
|
||||
"maxExamples": "Puedes seleccionar hasta {max} ejemplos",
|
||||
"next": "Siguiente",
|
||||
"publishButton": "Publicar en ComfyHub",
|
||||
"selectAThumbnail": "Selecciona una miniatura",
|
||||
"showLessTags": "Mostrar menos...",
|
||||
"showMoreTags": "Mostrar más...",
|
||||
"stepDescribe": "Describe tu flujo de trabajo",
|
||||
"stepExamples": "Agrega ejemplos de salida",
|
||||
"stepFinish": "Finalizar publicación",
|
||||
"suggestedTags": "Etiquetas sugeridas",
|
||||
"tags": "Etiquetas",
|
||||
"tagsDescription": "Selecciona etiquetas para que las personas encuentren tu flujo de trabajo más rápido",
|
||||
"tagsPlaceholder": "Ingresa etiquetas que coincidan con tu flujo de trabajo para ayudar a las personas a encontrarlo, por ejemplo #nanobanana, #anime o #faceswap",
|
||||
"thumbnailImage": "Imagen",
|
||||
"thumbnailImageComparison": "Comparación de imágenes",
|
||||
"thumbnailPreview": "Vista previa de la miniatura",
|
||||
"thumbnailVideo": "Video",
|
||||
"title": "Publicar en ComfyHub",
|
||||
"uploadAnImage": "Haz clic para buscar o arrastra una imagen",
|
||||
"uploadComparison": "Subir antes y después",
|
||||
"uploadComparisonAfterPrompt": "Después",
|
||||
"uploadComparisonBeforePrompt": "Antes",
|
||||
"uploadExampleImage": "Subir imagen de ejemplo",
|
||||
"uploadPromptClickToBrowse": "Haz clic para buscar o",
|
||||
"uploadPromptDropImage": "arrastra una imagen aquí",
|
||||
"uploadPromptDropVideo": "arrastra un video aquí",
|
||||
"uploadThumbnail": "Subir una imagen",
|
||||
"uploadThumbnailHint": "1:1 preferido, máximo 1080p",
|
||||
"uploadVideo": "Subir un video",
|
||||
"videoPreview": "Vista previa de miniatura de video",
|
||||
"workflowDescription": "Descripción del flujo de trabajo",
|
||||
"workflowDescriptionPlaceholder": "¿Qué hace que tu flujo de trabajo sea emocionante y especial? Sé específico para que las personas sepan qué esperar.",
|
||||
"workflowName": "Nombre del flujo de trabajo",
|
||||
"workflowNamePlaceholder": "Consejo: ingresa un nombre descriptivo y fácil de buscar",
|
||||
"workflowType": "Tipo de flujo de trabajo",
|
||||
"workflowTypeEditing": "Edición",
|
||||
"workflowTypeImageGeneration": "Generación de imágenes",
|
||||
"workflowTypePlaceholder": "Selecciona el tipo",
|
||||
"workflowTypeUpscaling": "Aumento de resolución",
|
||||
"workflowTypeVideoGeneration": "Generación de video"
|
||||
},
|
||||
"commands": {
|
||||
"clear": "Limpiar flujo de trabajo",
|
||||
"clipspace": "Abrir Clipspace",
|
||||
@@ -869,6 +952,7 @@
|
||||
"collapseAll": "Colapsar todo",
|
||||
"color": "Color",
|
||||
"comfy": "Comfy",
|
||||
"comfyCloud": "Comfy Cloud",
|
||||
"comfyOrgLogoAlt": "Logo de ComfyOrg",
|
||||
"comingSoon": "Próximamente",
|
||||
"command": "Comando",
|
||||
@@ -1342,7 +1426,7 @@
|
||||
"appBuilder": "Constructor de aplicaciones",
|
||||
"apps": "Aplicaciones",
|
||||
"appsEmptyMessage": "Las aplicaciones guardadas aparecerán aquí.\nHaz clic abajo para crear tu primera aplicación.",
|
||||
"enterAppMode": "Entrar en modo de aplicación"
|
||||
"appsEmptyMessageAction": "Haz clic abajo para crear tu primera aplicación."
|
||||
},
|
||||
"arrange": {
|
||||
"atLeastOne": "al menos uno",
|
||||
@@ -1356,14 +1440,18 @@
|
||||
},
|
||||
"backToWorkflow": "Volver al flujo de trabajo",
|
||||
"beta": "Modo App Beta - Enviar comentarios",
|
||||
"buildAnApp": "Crear una aplicación",
|
||||
"builder": {
|
||||
"exit": "Salir del constructor",
|
||||
"exitConfirmMessage": "Tienes cambios sin guardar que se perderán\n¿Salir sin guardar?",
|
||||
"exitConfirmTitle": "¿Salir del constructor de aplicaciones?",
|
||||
"inputPlaceholder": "Las entradas aparecerán aquí",
|
||||
"inputsDesc": "Los usuarios interactuarán y ajustarán estos para generar sus resultados.",
|
||||
"inputsExample": "Ejemplos: “Cargar imagen”, “Prompt de texto”, “Pasos”",
|
||||
"noInputs": "Aún no se han agregado entradas",
|
||||
"noOutputs": "Aún no se han agregado nodos de salida",
|
||||
"outputPlaceholder": "Los nodos de salida aparecerán aquí",
|
||||
"outputRequiredPlaceholder": "Se requiere al menos un nodo",
|
||||
"outputsDesc": "Conecta al menos un nodo de salida para que los usuarios vean los resultados después de ejecutar.",
|
||||
"outputsExample": "Ejemplos: “Guardar imagen” o “Guardar video”",
|
||||
"promptAddInputs": "Haz clic en los parámetros del nodo para agregarlos aquí como entradas",
|
||||
@@ -1371,12 +1459,15 @@
|
||||
"title": "Modo constructor de aplicaciones",
|
||||
"unknownWidget": "Widget no visible"
|
||||
},
|
||||
"cancelThisRun": "Cancelar esta ejecución",
|
||||
"deleteAllAssets": "Eliminar todos los recursos de esta ejecución",
|
||||
"downloadAll": "Descargar todo",
|
||||
"dragAndDropImage": "Arrastra y suelta una imagen",
|
||||
"emptyWorkflowExplanation": "Tu flujo de trabajo está vacío. Necesitas algunos nodos primero para empezar a construir una aplicación.",
|
||||
"enterNodeGraph": "Entrar al grafo de nodos",
|
||||
"giveFeedback": "Enviar comentarios",
|
||||
"graphMode": "Modo gráfico",
|
||||
"hasCreditCost": "Requiere créditos adicionales",
|
||||
"linearMode": "Modo App",
|
||||
"loadTemplate": "Cargar una plantilla",
|
||||
"mobileControls": "Editar y ejecutar",
|
||||
@@ -1393,6 +1484,8 @@
|
||||
"controls": "Tus resultados aparecen abajo, tus controles están a la derecha. Todo lo demás se mantiene fuera del camino.",
|
||||
"getStarted": "Haz clic en {runButton} para comenzar.",
|
||||
"message": "Una vista simplificada que oculta el grafo de nodos para que puedas concentrarte en crear.",
|
||||
"noOutputs": "Una aplicación necesita al menos {count} para ser utilizable.",
|
||||
"oneOutput": "1 salida",
|
||||
"sharing": "Compartir es fácil: crea tu flujo de trabajo, abre el Modo App, haz clic derecho en la pestaña y exporta. Cuando otros abran tu archivo, se lanzará directamente en esta vista limpia. Puedes compartir flujos de trabajo potentes como herramientas simples sin que nadie tenga que entender grafos de nodos.",
|
||||
"title": "Bienvenido al Modo App"
|
||||
}
|
||||
@@ -1765,7 +1858,6 @@
|
||||
"execute": "Ejecutar",
|
||||
"fullscreen": "Pantalla completa",
|
||||
"help": "Ayuda",
|
||||
"helpAndFeedback": "Ayuda y comentarios",
|
||||
"hideMenu": "Ocultar menú",
|
||||
"instant": "Instantáneo",
|
||||
"instantTooltip": "El flujo de trabajo se encolará instantáneamente después de que finalice una generación",
|
||||
@@ -1866,6 +1958,7 @@
|
||||
"Open Sign In Dialog": "Abrir diálogo de inicio de sesión",
|
||||
"Open extra_model_paths_yaml": "Abrir extra_model_paths.yaml",
|
||||
"Paste": "Pegar",
|
||||
"Paste with Connect": "Pegar con conectar",
|
||||
"Pin/Unpin Selected Items": "Anclar/Desanclar elementos seleccionados",
|
||||
"Pin/Unpin Selected Nodes": "Anclar/Desanclar nodos seleccionados",
|
||||
"Previous Opened Workflow": "Flujo de trabajo abierto anterior",
|
||||
@@ -2037,6 +2130,7 @@
|
||||
"lotus": "lotus",
|
||||
"ltxv": "ltxv",
|
||||
"mask": "mask",
|
||||
"math": "matemáticas",
|
||||
"model": "modelo",
|
||||
"model_merging": "fusión_de_modelos",
|
||||
"model_patches": "parches_de_modelo",
|
||||
@@ -2140,6 +2234,18 @@
|
||||
},
|
||||
"title": "Tu dispositivo no es compatible"
|
||||
},
|
||||
"openSharedWorkflow": {
|
||||
"author": "Autor:",
|
||||
"copyAssetsAndOpen": "Importar recursos y abrir flujo de trabajo",
|
||||
"copyDescription": "Abrir el flujo de trabajo creará una nueva copia en tu espacio de trabajo",
|
||||
"dialogTitle": "Abrir flujo de trabajo compartido",
|
||||
"importFailed": "No se pudieron importar los recursos del flujo de trabajo",
|
||||
"loadError": "No se pudo cargar este flujo de trabajo compartido. Por favor, inténtalo más tarde.",
|
||||
"nonPublicAssetsWarningLine1": "Este flujo de trabajo incluye recursos no públicos.",
|
||||
"nonPublicAssetsWarningLine2": "Estos se importarán a tu biblioteca al abrir el flujo de trabajo",
|
||||
"openWithoutImporting": "Abrir sin importar",
|
||||
"openWorkflow": "Abrir flujo de trabajo"
|
||||
},
|
||||
"painter": {
|
||||
"background": "Fondo",
|
||||
"brush": "Pincel",
|
||||
@@ -2598,6 +2704,47 @@
|
||||
"default": "Default",
|
||||
"round": "Redondo"
|
||||
},
|
||||
"shareNoOutputs": {
|
||||
"message": "Estás a punto de compartir una aplicación sin salidas. No se podrá usar hasta que se conecte una salida.\n\n¿Compartir de todos modos?",
|
||||
"shareAnyway": "Compartir de todos modos",
|
||||
"title": "La aplicación no tiene salidas"
|
||||
},
|
||||
"shareWorkflow": {
|
||||
"acknowledgeCheckbox": "Entiendo que estos archivos multimedia serán publicados y hechos públicos",
|
||||
"checkingAssets": "Comprobando visibilidad de los archivos multimedia…",
|
||||
"comfyHubButton": "Subir a ComfyHub",
|
||||
"comfyHubDescription": "ComfyHub es el centro comunitario oficial de ComfyUI.\nTu flujo de trabajo tendrá una página pública visible para todos.",
|
||||
"comfyHubTitle": "Subir a ComfyHub",
|
||||
"copyLink": "Copiar",
|
||||
"createLinkButton": "Crear un enlace",
|
||||
"createLinkDescription": "Cuando crees un enlace para tu flujo de trabajo, compartirás estos archivos multimedia junto con tu flujo de trabajo",
|
||||
"createLinkTitle": "Compartir flujo de trabajo",
|
||||
"creatingLink": "Creando enlace...",
|
||||
"hasChangesDescription": "Has realizado cambios desde la última vez que se publicó este flujo de trabajo.",
|
||||
"hasChangesTitle": "Compartir flujo de trabajo",
|
||||
"inLibrary": "En la biblioteca",
|
||||
"linkCopied": "¡Copiado!",
|
||||
"loadFailed": "No se pudo cargar el flujo de trabajo compartido",
|
||||
"loadingTitle": "Compartir flujo de trabajo",
|
||||
"mediaLabel": "{count} archivo multimedia | {count} archivos multimedia",
|
||||
"modelsLabel": "{count} modelo | {count} modelos",
|
||||
"privateAssetsDescription": "Tu flujo de trabajo contiene modelos y/o archivos multimedia privados",
|
||||
"publishToHubTab": "Publicar",
|
||||
"publishedOn": "Publicado el {date}",
|
||||
"saveButton": "Guardar flujo de trabajo",
|
||||
"saveFailedDescription": "No se pudo guardar el flujo de trabajo. Por favor, inténtalo de nuevo.",
|
||||
"saveFailedTitle": "Error al guardar",
|
||||
"saving": "Guardando...",
|
||||
"shareLinkTab": "Compartir",
|
||||
"shareUrlLabel": "URL para compartir",
|
||||
"successDescription": "Cualquiera con este enlace puede ver y usar este flujo de trabajo. Si realizas cambios en este flujo de trabajo, puedes volver a publicarlo para actualizar la versión compartida.",
|
||||
"successTitle": "¡Flujo de trabajo publicado con éxito!",
|
||||
"unsavedDescription": "Debes guardar tu flujo de trabajo antes de compartirlo. Guárdalo ahora para continuar.",
|
||||
"unsavedTitle": "Guarda el flujo de trabajo primero",
|
||||
"updateLinkButton": "Actualizar enlace",
|
||||
"updatingLink": "Actualizando enlace...",
|
||||
"workflowNameLabel": "Nombre del flujo de trabajo"
|
||||
},
|
||||
"shortcuts": {
|
||||
"essentials": "Esencial",
|
||||
"keyboardShortcuts": "Atajos de teclado",
|
||||
@@ -3229,10 +3376,7 @@
|
||||
"addedToWorkspace": "Has sido añadido a {workspaceName}",
|
||||
"inviteAccepted": "Invitación aceptada",
|
||||
"inviteFailed": "No se pudo aceptar la invitación",
|
||||
"unsavedChanges": {
|
||||
"message": "Tienes cambios no guardados. ¿Quieres descartarlos y cambiar de espacio de trabajo?",
|
||||
"title": "Cambios no guardados"
|
||||
},
|
||||
"switchFailed": "No se pudo cambiar de espacio de trabajo. Por favor, inténtalo de nuevo.",
|
||||
"viewWorkspace": "Ver espacio de trabajo"
|
||||
},
|
||||
"workspaceAuth": {
|
||||
|
||||
@@ -1419,6 +1419,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ComfyMathExpression": {
|
||||
"display_name": "Expresión matemática",
|
||||
"inputs": {
|
||||
"expression": {
|
||||
"name": "expresión"
|
||||
},
|
||||
"values": {
|
||||
"name": "valores"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
},
|
||||
"1": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"ComfySwitchNode": {
|
||||
"display_name": "Interruptor",
|
||||
"inputs": {
|
||||
@@ -15112,6 +15131,64 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"TencentModelTo3DUVNode": {
|
||||
"description": "Realiza el desplegado UV en un modelo 3D para generar la textura UV. El modelo de entrada debe tener menos de 30,000 caras.",
|
||||
"display_name": "Hunyuan3D: Modelo a UV",
|
||||
"inputs": {
|
||||
"control_after_generate": {
|
||||
"name": "control después de generar"
|
||||
},
|
||||
"model_3d": {
|
||||
"name": "modelo_3d",
|
||||
"tooltip": "Modelo 3D de entrada (GLB, OBJ o FBX)"
|
||||
},
|
||||
"seed": {
|
||||
"name": "semilla",
|
||||
"tooltip": "La semilla controla si el nodo debe ejecutarse de nuevo; los resultados son no deterministas independientemente de la semilla."
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"name": "OBJ",
|
||||
"tooltip": null
|
||||
},
|
||||
"1": {
|
||||
"name": "FBX",
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"TencentSmartTopologyNode": {
|
||||
"description": "Realiza retopología inteligente en un modelo 3D. Soporta formatos GLB/OBJ; máximo 200MB; recomendado para modelos de alta poligonización.",
|
||||
"display_name": "Hunyuan3D: Topología inteligente",
|
||||
"inputs": {
|
||||
"control_after_generate": {
|
||||
"name": "control después de generar"
|
||||
},
|
||||
"face_level": {
|
||||
"name": "nivel_de_caras",
|
||||
"tooltip": "Nivel de reducción de polígonos."
|
||||
},
|
||||
"model_3d": {
|
||||
"name": "modelo_3d",
|
||||
"tooltip": "Modelo 3D de entrada (GLB u OBJ)"
|
||||
},
|
||||
"polygon_type": {
|
||||
"name": "tipo_de_polígono",
|
||||
"tooltip": "Tipo de composición de la superficie."
|
||||
},
|
||||
"seed": {
|
||||
"name": "semilla",
|
||||
"tooltip": "La semilla controla si el nodo debe ejecutarse de nuevo; los resultados son no deterministas independientemente de la semilla."
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"name": "OBJ",
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"TencentTextToModelNode": {
|
||||
"display_name": "Hunyuan3D: Texto a Modelo (Pro)",
|
||||
"inputs": {
|
||||
|
||||
@@ -400,7 +400,7 @@
|
||||
"name": "Diseño de barra de pestañas",
|
||||
"options": {
|
||||
"Default": "Predeterminado",
|
||||
"Integrated": "Integrado"
|
||||
"Legacy": "Clásico"
|
||||
},
|
||||
"tooltip": "Controla el diseño de la barra de pestañas. \"Integrado\" mueve los controles de Ayuda y Usuario al área de la barra de pestañas."
|
||||
},
|
||||
|
||||
@@ -71,6 +71,9 @@
|
||||
"Comfy_Canvas_PasteFromClipboard": {
|
||||
"label": "چسباندن"
|
||||
},
|
||||
"Comfy_Canvas_PasteFromClipboardWithConnect": {
|
||||
"label": "چسباندن با اتصال"
|
||||
},
|
||||
"Comfy_Canvas_ResetView": {
|
||||
"label": "بازنشانی نما"
|
||||
},
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
"actionbar": {
|
||||
"dockToTop": "چسباندن به بالا",
|
||||
"feedback": "بازخورد",
|
||||
"feedbackTooltip": "بازخورد"
|
||||
"feedbackTooltip": "بازخورد",
|
||||
"share": "اشتراکگذاری",
|
||||
"shareTooltip": "اشتراکگذاری گردشکار"
|
||||
},
|
||||
"apiNodesCostBreakdown": {
|
||||
"costPerRun": "هزینه هر اجرا",
|
||||
@@ -321,18 +323,24 @@
|
||||
"y": "وای"
|
||||
},
|
||||
"breadcrumbsMenu": {
|
||||
"app": "برنامه",
|
||||
"clearWorkflow": "پاکسازی workflow",
|
||||
"deleteBlueprint": "حذف blueprint",
|
||||
"deleteWorkflow": "حذف workflow",
|
||||
"duplicate": "تکرار",
|
||||
"editBuilderMode": "ویرایش برنامه",
|
||||
"enterAppMode": "ورود به حالت اپلیکیشن",
|
||||
"enterBuilderMode": "ورود به حالت سازنده اپلیکیشن",
|
||||
"enterNewName": "نام جدید را وارد کنید",
|
||||
"enterNodeGraph": "ورود به گراف نود",
|
||||
"exitAppMode": "خروج از حالت اپلیکیشن",
|
||||
"graph": "گراف",
|
||||
"missingNodesWarning": "workflow شامل نودهای پشتیبانینشده است (با رنگ قرمز مشخص شدهاند).",
|
||||
"share": "اشتراکگذاری",
|
||||
"workflowActions": "عملیات گردشکار"
|
||||
},
|
||||
"builderMenu": {
|
||||
"enterAppMode": "ورود به حالت برنامه",
|
||||
"exitAppBuilder": "خروج از سازنده برنامه"
|
||||
},
|
||||
"builderToolbar": {
|
||||
@@ -354,6 +362,7 @@
|
||||
"defaultViewTitle": "تنظیم نمای پیشفرض برای این گردشکار",
|
||||
"emptyWorkflowPrompt": "آیا میخواهید با یک قالب شروع کنید؟",
|
||||
"emptyWorkflowTitle": "این ورکفلو هیچ node ندارد",
|
||||
"exitToWorkflow": "بازگشت به workflow",
|
||||
"inputs": "ورودیها",
|
||||
"inputsDescription": "انتخاب ورودیها",
|
||||
"label": "سازنده اپلیکیشن",
|
||||
@@ -507,6 +516,80 @@
|
||||
"red": "قرمز",
|
||||
"yellow": "زرد"
|
||||
},
|
||||
"comfyHubProfile": {
|
||||
"checkingAccess": "در حال بررسی دسترسی انتشار شما...",
|
||||
"chooseProfilePicture": "انتخاب تصویر پروفایل",
|
||||
"createProfile": "ساخت پروفایل",
|
||||
"createProfileButton": "ساخت پروفایل من",
|
||||
"createProfileTitle": "ساخت پروفایل Comfy Hub",
|
||||
"creatingProfile": "در حال ساخت پروفایل...",
|
||||
"descriptionLabel": "توضیحات شما",
|
||||
"descriptionPlaceholder": "درباره خودتان به جامعه توضیح دهید...",
|
||||
"introDescription": "جریانکارهای خود را منتشر کنید، نمونهکار بسازید و توسط میلیونها کاربر دیده شوید",
|
||||
"introSubtitle": "برای اشتراکگذاری جریانکار خود در ComfyHub، ابتدا پروفایل خود را بسازید.",
|
||||
"introTitle": "انتشار در ComfyHub",
|
||||
"modalTitle": "ساخت پروفایل در ComfyHub",
|
||||
"nameLabel": "نام شما",
|
||||
"namePlaceholder": "نام خود را اینجا وارد کنید",
|
||||
"profileCreationNav": "ساخت پروفایل",
|
||||
"startPublishingButton": "شروع انتشار",
|
||||
"successDescription": "اکنون میتوانید جریانکار خود را در صفحه سازنده خود بارگذاری کنید",
|
||||
"successProfileLink": "comfy.com/p/{username}",
|
||||
"successProfileUrl": "صفحه پروفایل شما فعال است در",
|
||||
"successTitle": "عالی به نظر میرسد، {'@'}{username}!",
|
||||
"uploadCover": "+ بارگذاری کاور",
|
||||
"uploadProfilePicture": "+ بارگذاری تصویر پروفایل",
|
||||
"uploadWorkflowButton": "بارگذاری جریانکار من",
|
||||
"usernameLabel": "نام کاربری شما (الزامی)",
|
||||
"usernamePlaceholder": "@"
|
||||
},
|
||||
"comfyHubPublish": {
|
||||
"back": "بازگشت",
|
||||
"createProfileCta": "ساخت پروفایل",
|
||||
"createProfileToPublish": "برای انتشار در ComfyHub یک پروفایل بسازید",
|
||||
"exampleImage": "تصویر نمونه {index}",
|
||||
"examplesDescription": "تا {total} تصویر نمونه اضافی اضافه کنید",
|
||||
"maxExamples": "شما میتوانید تا {max} نمونه انتخاب کنید",
|
||||
"next": "بعدی",
|
||||
"publishButton": "انتشار در ComfyHub",
|
||||
"selectAThumbnail": "یک تصویر بندانگشتی انتخاب کنید",
|
||||
"showLessTags": "نمایش کمتر...",
|
||||
"showMoreTags": "نمایش بیشتر...",
|
||||
"stepDescribe": "شرح جریانکار خود را وارد کنید",
|
||||
"stepExamples": "نمونههای خروجی را اضافه کنید",
|
||||
"stepFinish": "اتمام انتشار",
|
||||
"suggestedTags": "برچسبهای پیشنهادی",
|
||||
"tags": "برچسبها",
|
||||
"tagsDescription": "برچسبها را انتخاب کنید تا کاربران سریعتر جریانکار شما را پیدا کنند",
|
||||
"tagsPlaceholder": "برچسبهایی مرتبط با جریانکار خود وارد کنید تا کاربران راحتتر آن را پیدا کنند. مثال: #nanobanana، #anime یا #faceswap",
|
||||
"thumbnailImage": "تصویر",
|
||||
"thumbnailImageComparison": "مقایسه تصویر",
|
||||
"thumbnailPreview": "پیشنمایش بندانگشتی",
|
||||
"thumbnailVideo": "ویدیو",
|
||||
"title": "انتشار در ComfyHub",
|
||||
"uploadAnImage": "برای انتخاب کلیک کنید یا تصویر را بکشید",
|
||||
"uploadComparison": "بارگذاری قبل و بعد",
|
||||
"uploadComparisonAfterPrompt": "بعد",
|
||||
"uploadComparisonBeforePrompt": "قبل",
|
||||
"uploadExampleImage": "بارگذاری تصویر نمونه",
|
||||
"uploadPromptClickToBrowse": "برای انتخاب کلیک کنید یا",
|
||||
"uploadPromptDropImage": "یک تصویر را اینجا رها کنید",
|
||||
"uploadPromptDropVideo": "یک ویدیو را اینجا رها کنید",
|
||||
"uploadThumbnail": "بارگذاری تصویر",
|
||||
"uploadThumbnailHint": "نسبت ۱:۱ ترجیح داده میشود، حداکثر ۱۰۸۰p",
|
||||
"uploadVideo": "بارگذاری ویدیو",
|
||||
"videoPreview": "پیشنمایش بندانگشتی ویدیو",
|
||||
"workflowDescription": "توضیحات جریانکار",
|
||||
"workflowDescriptionPlaceholder": "چه چیزی جریانکار شما را هیجانانگیز و خاص میکند؟ مشخص توضیح دهید تا کاربران بدانند چه انتظاری داشته باشند.",
|
||||
"workflowName": "نام جریانکار",
|
||||
"workflowNamePlaceholder": "نکته: یک نام توصیفی وارد کنید که به راحتی قابل جستجو باشد",
|
||||
"workflowType": "نوع جریانکار",
|
||||
"workflowTypeEditing": "ویرایش",
|
||||
"workflowTypeImageGeneration": "تولید تصویر",
|
||||
"workflowTypePlaceholder": "نوع را انتخاب کنید",
|
||||
"workflowTypeUpscaling": "افزایش کیفیت",
|
||||
"workflowTypeVideoGeneration": "تولید ویدیو"
|
||||
},
|
||||
"commands": {
|
||||
"clear": "پاکسازی workflow",
|
||||
"clipspace": "باز کردن Clipspace",
|
||||
@@ -869,6 +952,7 @@
|
||||
"collapseAll": "بستن همه",
|
||||
"color": "رنگ",
|
||||
"comfy": "Comfy",
|
||||
"comfyCloud": "Comfy Cloud",
|
||||
"comfyOrgLogoAlt": "لوگوی ComfyOrg",
|
||||
"comingSoon": "بهزودی",
|
||||
"command": "دستور",
|
||||
@@ -1342,7 +1426,7 @@
|
||||
"appBuilder": "سازنده اپلیکیشن",
|
||||
"apps": "اپلیکیشنها",
|
||||
"appsEmptyMessage": "اپلیکیشنهای ذخیرهشده اینجا نمایش داده میشوند.\nبرای ساخت اولین اپلیکیشن خود، روی دکمه زیر کلیک کنید.",
|
||||
"enterAppMode": "ورود به حالت اپلیکیشن"
|
||||
"appsEmptyMessageAction": "برای ساخت اولین اپلیکیشن خود، روی دکمه زیر کلیک کنید."
|
||||
},
|
||||
"arrange": {
|
||||
"atLeastOne": "یک",
|
||||
@@ -1356,14 +1440,18 @@
|
||||
},
|
||||
"backToWorkflow": "بازگشت به جریان کاری",
|
||||
"beta": "حالت برنامه بتا - ارسال بازخورد",
|
||||
"buildAnApp": "ساخت اپلیکیشن",
|
||||
"builder": {
|
||||
"exit": "خروج از حالت ساخت",
|
||||
"exitConfirmMessage": "تغییرات ذخیرهنشده شما از بین خواهد رفت\nخروج بدون ذخیره؟",
|
||||
"exitConfirmTitle": "خروج از حالت ساخت اپلیکیشن؟",
|
||||
"inputPlaceholder": "ورودیها اینجا نمایش داده میشوند",
|
||||
"inputsDesc": "کاربران میتوانند این موارد را تنظیم کنند تا خروجی مورد نظر خود را تولید نمایند.",
|
||||
"inputsExample": "مثالها: «بارگذاری تصویر»، «متن راهنما»، «تعداد مراحل»",
|
||||
"noInputs": "هنوز ورودیای اضافه نشده است",
|
||||
"noOutputs": "هنوز گره خروجی اضافه نشده است",
|
||||
"outputPlaceholder": "نودهای خروجی اینجا نمایش داده میشوند",
|
||||
"outputRequiredPlaceholder": "حداقل یک نود لازم است",
|
||||
"outputsDesc": "حداقل یک گره خروجی متصل کنید تا کاربران پس از اجرا نتایج را مشاهده کنند.",
|
||||
"outputsExample": "مثالها: «ذخیره تصویر» یا «ذخیره ویدیو»",
|
||||
"promptAddInputs": "برای افزودن پارامترها به عنوان ورودی، روی پارامترهای گره کلیک کنید",
|
||||
@@ -1371,12 +1459,15 @@
|
||||
"title": "حالت ساخت اپلیکیشن",
|
||||
"unknownWidget": "ویجت قابل مشاهده نیست"
|
||||
},
|
||||
"cancelThisRun": "لغو این اجرا",
|
||||
"deleteAllAssets": "حذف همه داراییها از این اجرا",
|
||||
"downloadAll": "دانلود همه",
|
||||
"dragAndDropImage": "تصویر را بکشید و رها کنید",
|
||||
"emptyWorkflowExplanation": "جریان کاری شما خالی است. ابتدا باید چند node اضافه کنید تا بتوانید یک برنامه بسازید.",
|
||||
"enterNodeGraph": "ورود به گراف node",
|
||||
"giveFeedback": "ارسال بازخورد",
|
||||
"graphMode": "حالت گراف",
|
||||
"hasCreditCost": "نیازمند اعتبار اضافی",
|
||||
"linearMode": "حالت برنامه",
|
||||
"loadTemplate": "بارگذاری قالب",
|
||||
"mobileControls": "ویرایش و اجرا",
|
||||
@@ -1393,6 +1484,8 @@
|
||||
"controls": "خروجیهای شما در پایین نمایش داده میشوند و کنترلها در سمت راست قرار دارند. سایر موارد خارج از دید باقی میمانند.",
|
||||
"getStarted": "برای شروع روی {runButton} کلیک کنید.",
|
||||
"message": "نمای سادهشدهای که گراف node را مخفی میکند تا بتوانید بر خلق تمرکز کنید.",
|
||||
"noOutputs": "یک اپلیکیشن باید حداقل {count} خروجی داشته باشد تا قابل استفاده باشد.",
|
||||
"oneOutput": "۱ خروجی",
|
||||
"sharing": "اشتراکگذاری آسان است: workflow خود را بسازید، حالت App را باز کنید، روی تب راستکلیک کنید و خروجی بگیرید. وقتی دیگران فایل شما را باز میکنند، مستقیماً وارد این نمای ساده میشوند. میتوانید workflowهای قدرتمند را به ابزارهای ساده تبدیل و به اشتراک بگذارید بدون اینکه کسی نیاز به درک گراف node داشته باشد.",
|
||||
"title": "به حالت App خوش آمدید"
|
||||
}
|
||||
@@ -1765,7 +1858,6 @@
|
||||
"execute": "اجرا",
|
||||
"fullscreen": "تمامصفحه",
|
||||
"help": "راهنما",
|
||||
"helpAndFeedback": "راهنما و بازخورد",
|
||||
"hideMenu": "مخفی کردن منو",
|
||||
"instant": "فوری",
|
||||
"instantTooltip": "workflow بلافاصله پس از پایان تولید در صف قرار میگیرد",
|
||||
@@ -1866,6 +1958,7 @@
|
||||
"Open Sign In Dialog": "باز کردن پنجره ورود",
|
||||
"Open extra_model_paths_yaml": "باز کردن extra_model_paths.yaml",
|
||||
"Paste": "چسباندن",
|
||||
"Paste with Connect": "چسباندن با اتصال",
|
||||
"Pin/Unpin Selected Items": "سنجاق/برداشتن سنجاق موارد انتخابشده",
|
||||
"Pin/Unpin Selected Nodes": "سنجاق/برداشتن سنجاق Nodeهای انتخابشده",
|
||||
"Previous Opened Workflow": "Workflow قبلی بازشده",
|
||||
@@ -2037,6 +2130,7 @@
|
||||
"lotus": "lotus",
|
||||
"ltxv": "ltxv",
|
||||
"mask": "ماسک",
|
||||
"math": "ریاضی",
|
||||
"model": "مدل",
|
||||
"model_merging": "ادغام مدل",
|
||||
"model_patches": "وصلههای مدل",
|
||||
@@ -2140,6 +2234,18 @@
|
||||
},
|
||||
"title": "دستگاه شما پشتیبانی نمیشود"
|
||||
},
|
||||
"openSharedWorkflow": {
|
||||
"author": "نویسنده:",
|
||||
"copyAssetsAndOpen": "وارد کردن داراییها و باز کردن گردشکار",
|
||||
"copyDescription": "باز کردن گردشکار یک نسخه جدید در فضای کاری شما ایجاد میکند",
|
||||
"dialogTitle": "باز کردن گردشکار اشتراکی",
|
||||
"importFailed": "وارد کردن داراییهای گردشکار ناموفق بود",
|
||||
"loadError": "امکان بارگذاری این گردشکار اشتراکی وجود ندارد. لطفاً بعداً دوباره تلاش کنید.",
|
||||
"nonPublicAssetsWarningLine1": "این گردشکار دارای داراییهای غیرعمومی است.",
|
||||
"nonPublicAssetsWarningLine2": "این موارد هنگام باز کردن گردشکار به کتابخانه شما وارد میشوند",
|
||||
"openWithoutImporting": "باز کردن بدون وارد کردن",
|
||||
"openWorkflow": "باز کردن گردشکار"
|
||||
},
|
||||
"painter": {
|
||||
"background": "پسزمینه",
|
||||
"brush": "براش",
|
||||
@@ -2598,6 +2704,47 @@
|
||||
"default": "پیشفرض",
|
||||
"round": "گرد"
|
||||
},
|
||||
"shareNoOutputs": {
|
||||
"message": "شما در حال اشتراکگذاری برنامهای بدون خروجی هستید. تا زمانی که خروجی متصل نشود قابل استفاده نیست.\n\nآیا مایل به اشتراکگذاری هستید؟",
|
||||
"shareAnyway": "اشتراکگذاری",
|
||||
"title": "برنامه خروجی ندارد"
|
||||
},
|
||||
"shareWorkflow": {
|
||||
"acknowledgeCheckbox": "متوجه شدم که این رسانهها منتشر و عمومی خواهند شد",
|
||||
"checkingAssets": "در حال بررسی وضعیت رسانهها…",
|
||||
"comfyHubButton": "بارگذاری در ComfyHub",
|
||||
"comfyHubDescription": "ComfyHub مرکز رسمی جامعه ComfyUI است.\nگردشکار شما یک صفحه عمومی خواهد داشت که برای همه قابل مشاهده است.",
|
||||
"comfyHubTitle": "بارگذاری در ComfyHub",
|
||||
"copyLink": "کپی",
|
||||
"createLinkButton": "ایجاد لینک",
|
||||
"createLinkDescription": "هنگام ایجاد لینک برای گردشکار، این رسانهها نیز همراه با گردشکار شما به اشتراک گذاشته میشوند",
|
||||
"createLinkTitle": "اشتراکگذاری گردشکار",
|
||||
"creatingLink": "در حال ایجاد لینک...",
|
||||
"hasChangesDescription": "شما از آخرین انتشار این گردشکار تغییراتی ایجاد کردهاید.",
|
||||
"hasChangesTitle": "اشتراکگذاری گردشکار",
|
||||
"inLibrary": "در کتابخانه",
|
||||
"linkCopied": "کپی شد!",
|
||||
"loadFailed": "بارگذاری گردشکار اشتراکی ناموفق بود",
|
||||
"loadingTitle": "اشتراکگذاری گردشکار",
|
||||
"mediaLabel": "{count} فایل رسانهای | {count} فایل رسانهای",
|
||||
"modelsLabel": "{count} مدل | {count} مدل",
|
||||
"privateAssetsDescription": "گردشکار شما شامل مدلها و/یا فایلهای رسانهای خصوصی است",
|
||||
"publishToHubTab": "انتشار",
|
||||
"publishedOn": "منتشر شده در {date}",
|
||||
"saveButton": "ذخیره گردشکار",
|
||||
"saveFailedDescription": "ذخیره گردشکار ناموفق بود. لطفاً دوباره تلاش کنید.",
|
||||
"saveFailedTitle": "ذخیره ناموفق بود",
|
||||
"saving": "در حال ذخیره...",
|
||||
"shareLinkTab": "اشتراکگذاری",
|
||||
"shareUrlLabel": "آدرس اشتراکگذاری",
|
||||
"successDescription": "هر کسی که این لینک را داشته باشد میتواند این گردشکار را مشاهده و استفاده کند. اگر تغییری در این گردشکار ایجاد کنید، میتوانید با انتشار مجدد نسخه بهروزشده را به اشتراک بگذارید.",
|
||||
"successTitle": "گردشکار با موفقیت منتشر شد!",
|
||||
"unsavedDescription": "برای اشتراکگذاری باید ابتدا گردشکار خود را ذخیره کنید. اکنون ذخیره کنید تا ادامه دهید.",
|
||||
"unsavedTitle": "ابتدا گردشکار را ذخیره کنید",
|
||||
"updateLinkButton": "بهروزرسانی لینک",
|
||||
"updatingLink": "در حال بهروزرسانی لینک...",
|
||||
"workflowNameLabel": "نام گردشکار"
|
||||
},
|
||||
"shortcuts": {
|
||||
"essentials": "ضروری",
|
||||
"keyboardShortcuts": "میانبرهای صفحهکلید",
|
||||
@@ -3241,10 +3388,7 @@
|
||||
"addedToWorkspace": "شما به {workspaceName} اضافه شدید",
|
||||
"inviteAccepted": "دعوت پذیرفته شد",
|
||||
"inviteFailed": "پذیرش دعوت ناموفق بود",
|
||||
"unsavedChanges": {
|
||||
"message": "شما تغییرات ذخیرهنشده دارید. آیا میخواهید آنها را رها کرده و فضای کاری را تغییر دهید؟",
|
||||
"title": "تغییرات ذخیرهنشده"
|
||||
},
|
||||
"switchFailed": "تغییر ورکاسپیس ناموفق بود. لطفاً دوباره تلاش کنید.",
|
||||
"viewWorkspace": "مشاهده workspace"
|
||||
},
|
||||
"workspaceAuth": {
|
||||
|
||||
@@ -1419,6 +1419,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ComfyMathExpression": {
|
||||
"display_name": "عبارت ریاضی",
|
||||
"inputs": {
|
||||
"expression": {
|
||||
"name": "عبارت"
|
||||
},
|
||||
"values": {
|
||||
"name": "مقادیر"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"tooltip": null
|
||||
},
|
||||
"1": {
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"ComfySwitchNode": {
|
||||
"display_name": "سوئیچ",
|
||||
"inputs": {
|
||||
@@ -15112,6 +15131,64 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"TencentModelTo3DUVNode": {
|
||||
"description": "بازکردن UV روی یک مدل سهبعدی برای تولید بافت UV. مدل ورودی باید کمتر از ۳۰۰۰۰ وجه داشته باشد.",
|
||||
"display_name": "Hunyuan3D: مدل به UV",
|
||||
"inputs": {
|
||||
"control_after_generate": {
|
||||
"name": "کنترل پس از تولید"
|
||||
},
|
||||
"model_3d": {
|
||||
"name": "مدل سهبعدی",
|
||||
"tooltip": "مدل سهبعدی ورودی (GLB، OBJ یا FBX)"
|
||||
},
|
||||
"seed": {
|
||||
"name": "seed",
|
||||
"tooltip": "seed تعیین میکند که node باید دوباره اجرا شود یا خیر؛ نتایج صرفنظر از seed غیرقطعی هستند."
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"name": "OBJ",
|
||||
"tooltip": null
|
||||
},
|
||||
"1": {
|
||||
"name": "FBX",
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"TencentSmartTopologyNode": {
|
||||
"description": "اجرای رتپولوژی هوشمند روی یک مدل سهبعدی. پشتیبانی از فرمتهای GLB/OBJ؛ حداکثر ۲۰۰ مگابایت؛ توصیهشده برای مدلهای با چندضلعی بالا.",
|
||||
"display_name": "Hunyuan3D: توپولوژی هوشمند",
|
||||
"inputs": {
|
||||
"control_after_generate": {
|
||||
"name": "کنترل پس از تولید"
|
||||
},
|
||||
"face_level": {
|
||||
"name": "سطح وجه",
|
||||
"tooltip": "سطح کاهش چندضلعی."
|
||||
},
|
||||
"model_3d": {
|
||||
"name": "مدل سهبعدی",
|
||||
"tooltip": "مدل سهبعدی ورودی (GLB یا OBJ)"
|
||||
},
|
||||
"polygon_type": {
|
||||
"name": "نوع چندضلعی",
|
||||
"tooltip": "نوع ترکیب سطح."
|
||||
},
|
||||
"seed": {
|
||||
"name": "seed",
|
||||
"tooltip": "seed تعیین میکند که node باید دوباره اجرا شود یا خیر؛ نتایج صرفنظر از seed غیرقطعی هستند."
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"0": {
|
||||
"name": "OBJ",
|
||||
"tooltip": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"TencentTextToModelNode": {
|
||||
"display_name": "Hunyuan3D: تبدیل متن به مدل (پیشرفته)",
|
||||
"inputs": {
|
||||
|
||||
@@ -400,7 +400,7 @@
|
||||
"name": "چیدمان نوار تب",
|
||||
"options": {
|
||||
"Default": "پیشفرض",
|
||||
"Integrated": "یکپارچه"
|
||||
"Legacy": "قدیمی"
|
||||
},
|
||||
"tooltip": "چیدمان نوار تب را کنترل میکند. «یکپارچه» کنترلهای راهنما و کاربر را به ناحیه نوار تب منتقل میکند."
|
||||
},
|
||||
|
||||
@@ -71,6 +71,9 @@
|
||||
"Comfy_Canvas_PasteFromClipboard": {
|
||||
"label": "Coller"
|
||||
},
|
||||
"Comfy_Canvas_PasteFromClipboardWithConnect": {
|
||||
"label": "Coller avec connexion"
|
||||
},
|
||||
"Comfy_Canvas_ResetView": {
|
||||
"label": "Réinitialiser la vue"
|
||||
},
|
||||
|
||||