Compare commits

..

17 Commits

Author SHA1 Message Date
Luke Mino-Altherr
7a7f6380f0 [backport core/1.41] fix: make zPreviewOutput accept text-only job outputs (#9751)
Backport of #9724 to core/1.41.

Resolves Zod validation crash when a jobs batch contains text-only
preview outputs (e.g. from LLM nodes), which caused the Assets sidebar
to show nothing.

- Makes `filename`, `subfolder`, and `type` optional in `zPreviewOutput`
- Adds `.passthrough()` for extra fields like `content`
- Adds tests for mixed batch parsing and text-only job filtering

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9751-backport-core-1-41-fix-make-zPreviewOutput-accept-text-only-job-outputs-3206d73d365081b384fbd93c78942f9d)
by [Unito](https://www.unito.io)
2026-03-11 12:36:14 -07:00
Comfy Org PR Bot
782599995e 1.41.14 (#9747)
Patch version increment to 1.41.14

**Base branch:** `core/1.41`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9747-1-41-14-3206d73d36508147bc4bdf7aed8d16f8)
by [Unito](https://www.unito.io)

Co-authored-by: AustinMroz <4284322+AustinMroz@users.noreply.github.com>
2026-03-11 11:10:03 -07:00
AustinMroz
078c414cdf [backport core/1.41] feat: show App/Node Graph type indicator on template cards (#9746)
Manual backport of #9695 to core/1.41.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9746-backport-core-1-41-feat-show-App-Node-Graph-type-indicator-on-template-cards-3206d73d36508166a8f9f257357d927f)
by [Unito](https://www.unito.io)

Co-authored-by: Robin Huang <robin.j.huang@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions <github-actions@github.com>
2026-03-11 11:00:30 -07:00
Comfy Org PR Bot
7c69c5f4c6 [backport core/1.41] Mobile input tweaks (#9710)
Backport of #9686 to `core/1.41`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9710-backport-core-1-41-Mobile-input-tweaks-31f6d73d365081048373f3616a84cd51)
by [Unito](https://www.unito.io)

Co-authored-by: AustinMroz <austin@comfy.org>
2026-03-09 23:35:03 -07:00
Comfy Org PR Bot
a1af7e454c [backport core/1.41] Use preview downscaling in fewer places (#9682)
Backport of #9678 to `core/1.41`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9682-backport-core-1-41-Use-preview-downscaling-in-fewer-places-31e6d73d365081fb845ff568d2088070)
by [Unito](https://www.unito.io)

Co-authored-by: AustinMroz <austin@comfy.org>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
2026-03-09 16:16:34 -07:00
Comfy Org PR Bot
0596e36202 [backport core/1.41] Restore hiding of linked inputs in app mode (#9676)
Backport of #9671 to `core/1.41`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9676-backport-core-1-41-Restore-hiding-of-linked-inputs-in-app-mode-31e6d73d365081f7bab6ee781b782810)
by [Unito](https://www.unito.io)

Co-authored-by: AustinMroz <austin@comfy.org>
2026-03-09 13:42:45 -07:00
Comfy Org PR Bot
bca61c75f2 [backport core/1.41] Always use interior nodeId for app mode (#9674)
Backport of #9669 to `core/1.41`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9674-backport-core-1-41-Always-use-interior-nodeId-for-app-mode-31e6d73d365081e1a227edf172c0b822)
by [Unito](https://www.unito.io)

Co-authored-by: AustinMroz <austin@comfy.org>
2026-03-09 13:25:16 -07:00
Comfy Org PR Bot
441ffec3bc [backport core/1.41] Even further app fixes (#9663)
Backport of #9617 to `core/1.41`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9663-backport-core-1-41-Even-further-app-fixes-31e6d73d365081e0a64bc958cf8d1bbb)
by [Unito](https://www.unito.io)

Co-authored-by: AustinMroz <austin@comfy.org>
2026-03-09 09:45:34 -07:00
Comfy Org PR Bot
be14ce3348 [backport core/1.41] fix: prevent showing outputs in app mode when no output nodes configured (#9632)
Backport of #9625 to `core/1.41`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9632-backport-core-1-41-fix-prevent-showing-outputs-in-app-mode-when-no-output-nodes-config-31e6d73d3650813b99d6d40fd2db8084)
by [Unito](https://www.unito.io)

Co-authored-by: pythongosssss <125205205+pythongosssss@users.noreply.github.com>
2026-03-08 17:44:29 -07:00
Comfy Org PR Bot
ffd334cdaa [backport core/1.41] fix: use previewable output count for asset sidebar badge (#9559)
Backport of #9535 to `core/1.41`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9559-backport-core-1-41-fix-use-previewable-output-count-for-asset-sidebar-badge-31d6d73d36508161ac9be3c361dab360)
by [Unito](https://www.unito.io)

Co-authored-by: Christian Byrne <cbyrne@comfy.org>
2026-03-07 19:02:39 -08:00
Christian Byrne
98ad2a9672 [backport core/1.41] Yet further app fixes (#9523) (#9566)
Backport of #9523 to core/1.41.

Cherry-pick of merge commit 83ffaf30 with minor conflict in
`src/locales/en/main.json` (added new i18n keys). Resolved by accepting
incoming keys.

**Original PR:** https://github.com/Comfy-Org/ComfyUI_frontend/pull/9523
**Pipeline ticket:** 15e1f241-efaa-4fe5-88ca-4ccc7bfb3345

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9566-backport-core-1-41-Yet-further-app-fixes-9523-31d6d73d3650815c9c06eee813f0e72e)
by [Unito](https://www.unito.io)

Co-authored-by: AustinMroz <austin@comfy.org>
2026-03-07 18:19:54 -08:00
Christian Byrne
95e5981f2f [backport core/1.41] feat/fix: App mode further updates (#9545) (#9567)
Backport of #9545 to core/1.41.

Cherry-pick of merge commit c4156d70 applied cleanly.

**Original PR:** https://github.com/Comfy-Org/ComfyUI_frontend/pull/9545
**Pipeline ticket:** 15e1f241-efaa-4fe5-88ca-4ccc7bfb3345

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9567-backport-core-1-41-feat-fix-App-mode-further-updates-9545-31d6d73d3650812394d5cb53af3292e1)
by [Unito](https://www.unito.io)

Co-authored-by: pythongosssss <125205205+pythongosssss@users.noreply.github.com>
2026-03-07 18:18:24 -08:00
Christian Byrne
e3287d4c95 [backport core/1.41] feat/fix: App mode QA fixes (#9530) (#9565)
Backport of #9530 to core/1.41.

Cherry-pick of merge commit 5bb742ac applied cleanly.

**Original PR:** https://github.com/Comfy-Org/ComfyUI_frontend/pull/9530
**Pipeline ticket:** 15e1f241-efaa-4fe5-88ca-4ccc7bfb3345

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9565-backport-core-1-41-feat-fix-App-mode-QA-fixes-9530-31d6d73d36508116b377fafed6f5aec5)
by [Unito](https://www.unito.io)

Co-authored-by: pythongosssss <125205205+pythongosssss@users.noreply.github.com>
2026-03-07 18:18:20 -08:00
Christian Byrne
d65d8ec06e [backport core/1.41] fix: remove workspace switching confirmation dialog (#9250) (#9564)
Backport of #9250 to core/1.41.

Cherry-pick of merge commit 2875f897 applied cleanly (auto-merge on
locale files).

**Original PR:** https://github.com/Comfy-Org/ComfyUI_frontend/pull/9250
**Pipeline ticket:** 15e1f241-efaa-4fe5-88ca-4ccc7bfb3345

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9564-backport-core-1-41-fix-remove-workspace-switching-confirmation-dialog-9250-31d6d73d3650818b86eeeedc79b3d6c5)
by [Unito](https://www.unito.io)
2026-03-07 18:18:18 -08:00
Christian Byrne
648a964531 [backport core/1.41] fix: prevent non-widget inputs on nested subgraphs from appearing as button widgets (#9542) (#9563)
Backport of #9542 to core/1.41.

Cherry-pick of merge commit 8a5bcde1 applied cleanly.

**Original PR:** https://github.com/Comfy-Org/ComfyUI_frontend/pull/9542
**Pipeline ticket:** 15e1f241-efaa-4fe5-88ca-4ccc7bfb3345

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9563-backport-core-1-41-fix-prevent-non-widget-inputs-on-nested-subgraphs-from-appearing-as-31d6d73d36508154927fd4b7b55ea87f)
by [Unito](https://www.unito.io)

Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: Amp <amp@ampcode.com>
2026-03-07 18:18:15 -08:00
Christian Byrne
0f889a95d9 [backport core/1.41] fix: remove timeouts from error toasts so they persist until dismissed (#9543) (#9562)
Backport of #9543 to core/1.41.

Cherry-pick of merge commit 725a0a2b applied cleanly.

**Original PR:** https://github.com/Comfy-Org/ComfyUI_frontend/pull/9543
**Pipeline ticket:** 15e1f241-efaa-4fe5-88ca-4ccc7bfb3345

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9562-backport-core-1-41-fix-remove-timeouts-from-error-toasts-so-they-persist-until-dismiss-31d6d73d365081caa825ff32ca622b2c)
by [Unito](https://www.unito.io)
2026-03-07 18:18:12 -08:00
Christian Byrne
1fc437bb41 [backport core/1.41] fix: Prevent corruption of workflow data due to checkState during graph loading (#9531) (#9561)
Backport of #9531 to core/1.41.

Cherry-pick of merge commit ec129de6 applied cleanly.

**Original PR:** https://github.com/Comfy-Org/ComfyUI_frontend/pull/9531
**Pipeline ticket:** 15e1f241-efaa-4fe5-88ca-4ccc7bfb3345

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9561-backport-core-1-41-fix-Prevent-corruption-of-workflow-data-due-to-checkState-during-gr-31d6d73d365081aabc05c0409ee49615)
by [Unito](https://www.unito.io)

Co-authored-by: pythongosssss <125205205+pythongosssss@users.noreply.github.com>
2026-03-07 18:18:10 -08:00
184 changed files with 872 additions and 3224 deletions

View File

@@ -23,7 +23,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
node-version: lts/*
cache: 'pnpm'
- name: Update electron types

View File

@@ -28,7 +28,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
node-version: lts/*
cache: 'pnpm'
- name: Install dependencies

View File

@@ -27,7 +27,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
node-version: lts/*
cache: 'pnpm'
- name: Install dependencies

View File

@@ -26,7 +26,7 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: '.nvmrc'
node-version: 'lts/*'
cache: 'pnpm'
- name: Install dependencies

View File

@@ -27,7 +27,7 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: '.nvmrc'
node-version: 'lts/*'
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-file: '.nvmrc'
node-version: 'lts/*'
cache: 'pnpm'
- name: Install dependencies

View File

@@ -36,7 +36,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
node-version: '20'
cache: 'pnpm'
- name: Install dependencies for analysis tools

View File

@@ -25,7 +25,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
node-version: 22
- name: Download PR metadata
uses: dawidd6/action-download-artifact@0bd50d53a6d7fb5cb921e607957e9cc12b4ce392 # v12

View File

@@ -28,7 +28,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
node-version: '24.x'
- name: Read desktop-ui version
id: get_version

View File

@@ -91,7 +91,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
node-version: '24.x'
cache: 'pnpm'
registry-url: https://registry.npmjs.org

View File

@@ -82,7 +82,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: 'frontend/.nvmrc'
node-version: lts/*
- name: Install dependencies
working-directory: frontend

View File

@@ -26,7 +26,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
node-version: 'lts/*'
- name: Check version bump type
id: check_version

View File

@@ -26,7 +26,7 @@ jobs:
version: 10
- uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
node-version: 'lts/*'
cache: 'pnpm'
- name: Get current version

View File

@@ -82,7 +82,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
node-version: 'lts/*'
cache: 'pnpm'
registry-url: https://registry.npmjs.org

View File

@@ -22,7 +22,7 @@ jobs:
version: 10
- uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
node-version: 'lts/*'
cache: 'pnpm'
- name: Get current version

View File

@@ -149,7 +149,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
node-version: lts/*
- name: Bump version
id: bump-version

View File

@@ -58,7 +58,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
node-version: '24.x'
cache: 'pnpm'
- name: Bump desktop-ui version

View File

@@ -35,7 +35,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
node-version: '20'
cache: 'pnpm'
- name: Install dependencies for analysis tools

View File

@@ -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 (see `.nvmrc`, currently v24) and pnpm
- Node.js (v24) and pnpm
- Git for version control
- A running ComfyUI backend instance (otherwise, you can use `pnpm dev:cloud`)

View File

@@ -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')
})
}

View File

@@ -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')
})
}
}

View File

@@ -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

View File

@@ -27,8 +27,7 @@ cp -r tools/devtools/* /path/to/your/ComfyUI/custom_nodes/ComfyUI_devtools/
### Node.js & Playwright Prerequisites
Ensure you have the Node.js version from `.nvmrc` installed (currently v24).
Then, set up the Chromium test driver:
Ensure you have Node.js v20 or v22 installed. Then, set up the Chromium test driver:
```bash
pnpm exec playwright install chromium --with-deps

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 107 KiB

View File

@@ -1,6 +1,6 @@
{
"name": "@comfyorg/comfyui-frontend",
"version": "1.42.0",
"version": "1.41.14",
"private": true,
"description": "Official front-end implementation of ComfyUI",
"homepage": "https://comfy.org",
@@ -195,9 +195,6 @@
"zip-dir": "^2.0.0",
"zod-to-json-schema": "catalog:"
},
"engines": {
"node": "24.x"
},
"pnpm": {
"overrides": {
"vite": "catalog:"

View File

@@ -166,22 +166,13 @@ 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 = createLegacyTabBarWrapper()
const wrapper = createWrapper()
expect(wrapper.findComponent(CurrentUserButton).exists()).toBe(true)
expect(wrapper.findComponent(LoginButton).exists()).toBe(false)
})
@@ -195,7 +186,7 @@ describe('TopMenuSection', () => {
describe('on desktop platform', () => {
it('should display LoginButton and not display CurrentUserButton', () => {
mockData.isDesktop = true
const wrapper = createLegacyTabBarWrapper()
const wrapper = createWrapper()
expect(wrapper.findComponent(LoginButton).exists()).toBe(true)
expect(wrapper.findComponent(CurrentUserButton).exists()).toBe(false)
})
@@ -203,7 +194,7 @@ describe('TopMenuSection', () => {
describe('on web platform', () => {
it('should not display CurrentUserButton and not display LoginButton', () => {
const wrapper = createLegacyTabBarWrapper()
const wrapper = createWrapper()
expect(wrapper.findComponent(CurrentUserButton).exists()).toBe(false)
expect(wrapper.findComponent(LoginButton).exists()).toBe(false)
})

View File

@@ -183,7 +183,7 @@ const isActionbarFloating = computed(
() => isActionbarEnabled.value && !isActionbarDocked.value
)
const isIntegratedTabBar = computed(
() => settingStore.get('Comfy.UI.TabBarLayout') !== 'Legacy'
() => settingStore.get('Comfy.UI.TabBarLayout') === 'Integrated'
)
const { isQueuePanelV2Enabled, isRunProgressBarEnabled } =
useQueueFeatureFlags()

View File

@@ -8,9 +8,9 @@ import DraggableList from '@/components/common/DraggableList.vue'
import IoItem from '@/components/builder/IoItem.vue'
import PropertiesAccordionItem from '@/components/rightSidePanel/layout/PropertiesAccordionItem.vue'
import WidgetItem from '@/components/rightSidePanel/parameters/WidgetItem.vue'
import { isPromotedWidgetView } from '@/core/graph/subgraph/promotedWidgetTypes'
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,10 +25,10 @@ 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 { nodeTypeValidForApp, useAppModeStore } from '@/stores/appModeStore'
import { resolveNode } from '@/utils/litegraphUtil'
import { resolveNodeWidget } from '@/utils/litegraphUtil'
import { cn } from '@/utils/tailwindUtil'
import { HideLayoutFieldKey } from '@/types/widgetTypes'
@@ -53,18 +53,15 @@ workflowStore.activeWorkflow?.changeTracker?.reset()
const arrangeInputs = computed(() =>
appModeStore.selectedInputs
.map(([nodeId, widgetName]) => {
const node = resolveNode(nodeId)
if (!node) return null
const widget = node.widgets?.find((w) => w.name === widgetName)
return { nodeId, widgetName, node, widget }
const [node, widget] = resolveNodeWidget(nodeId, widgetName)
return node ? { nodeId, widgetName, node, widget } : null
})
.filter((item): item is NonNullable<typeof item> => item !== null)
)
const inputsWithState = computed(() =>
appModeStore.selectedInputs.map(([nodeId, widgetName]) => {
const node = resolveNode(nodeId)
const widget = node?.widgets?.find((w) => w.name === widgetName)
const [node, widget] = resolveNodeWidget(nodeId, widgetName)
if (!node || !widget) {
return {
nodeId,
@@ -73,15 +70,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 +86,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] {
@@ -126,7 +106,7 @@ function getHovered(
function getBounding(nodeId: NodeId, widgetName?: string) {
if (settingStore.get('Comfy.VueNodes.Enabled')) return undefined
const node = app.rootGraph.getNodeById(nodeId)
const [node, widget] = resolveNodeWidget(nodeId, widgetName)
if (!node) return
const titleOffset =
@@ -139,7 +119,6 @@ function getBounding(nodeId: NodeId, widgetName?: string) {
left: `${node.pos[0]}px`,
top: `${node.pos[1] - titleOffset}px`
}
const widget = node.widgets?.find((w) => w.name === widgetName)
if (!widget) return
const margin = widget instanceof DOMWidgetImpl ? widget.margin : undefined
@@ -178,12 +157,16 @@ function handleClick(e: MouseEvent) {
else appModeStore.selectedOutputs.splice(index, 1)
return
}
if (!isSelectInputsMode.value) return
if (!isSelectInputsMode.value || widget.options.canvasOnly) return
const storeId = isPromotedWidgetView(widget) ? widget.sourceNodeId : node.id
const storeName = isPromotedWidgetView(widget)
? widget.sourceWidgetName
: widget.name
const index = appModeStore.selectedInputs.findIndex(
([nodeId, widgetName]) => node.id == nodeId && widget.name === widgetName
([nodeId, widgetName]) => storeId == nodeId && storeName === widgetName
)
if (index === -1) appModeStore.selectedInputs.push([node.id, widget.name])
if (index === -1) appModeStore.selectedInputs.push([storeId, storeName])
else appModeStore.selectedInputs.splice(index, 1)
}
@@ -266,7 +249,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>
@@ -321,7 +304,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>

View File

@@ -0,0 +1,51 @@
<script setup lang="ts">
import {
DialogClose,
DialogContent,
DialogOverlay,
DialogPortal,
DialogRoot,
DialogTitle,
DialogTrigger
} from 'reka-ui'
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
defineProps<{ title?: string; to?: string | HTMLElement }>()
const { t } = useI18n()
</script>
<template>
<DialogRoot v-slot="{ close }">
<DialogTrigger as-child>
<slot name="button" />
</DialogTrigger>
<DialogPortal :to>
<DialogOverlay
class="data-[state=open]:animate-overlayShow fixed inset-0 z-30 bg-black/70"
/>
<DialogContent
v-bind="$attrs"
class="data-[state=open]:animate-contentShow fixed top-[50%] left-[50%] z-1700 max-h-[85vh] w-[90vw] max-w-[450px] translate-x-[-50%] translate-y-[-50%] rounded-2xl border border-border-subtle bg-base-background p-2 shadow-sm"
>
<div
v-if="title"
class="flex w-full items-center justify-between border-b border-border-subtle px-4"
>
<DialogTitle class="text-sm">{{ title }}</DialogTitle>
<DialogClose as-child>
<Button
:aria-label="t('g.close')"
size="icon"
variant="muted-textonly"
>
<i class="icon-[lucide--x]" />
</Button>
</DialogClose>
</div>
<slot :close />
</DialogContent>
</DialogPortal>
</DialogRoot>
</template>

View File

@@ -54,11 +54,12 @@ defineProps<{ itemClass: string; contentClass: string; item: MenuItem }>()
:disabled="toValue(item.disabled) ?? !item.command"
@select="item.command?.({ originalEvent: $event, item })"
>
<i class="size-5" :class="item.icon" />
{{ item.label }}
<i class="size-5 shrink-0" :class="item.icon" />
<div class="mr-auto truncate" v-text="item.label" />
<i v-if="item.checked" class="icon-[lucide--check] shrink-0" />
<div
v-if="item.new"
class="ml-auto flex items-center rounded-full bg-primary-background px-1 text-xxs leading-none font-bold"
v-else-if="item.new"
class="flex shrink-0 items-center rounded-full bg-primary-background px-1 text-xxs leading-none font-bold"
v-text="t('contextMenu.new')"
/>
</DropdownMenuItem>

View File

@@ -27,7 +27,7 @@ const { itemClass: itemProp, contentClass: contentProp } = defineProps<{
const itemClass = computed(() =>
cn(
'm-1 flex cursor-pointer gap-1 rounded-lg p-2 leading-none data-disabled:pointer-events-none data-disabled:text-muted-foreground data-highlighted:bg-secondary-background-hover',
'm-1 flex cursor-pointer items-center-safe gap-1 rounded-lg p-2 leading-none data-disabled:pointer-events-none data-disabled:text-muted-foreground data-highlighted:bg-secondary-background-hover',
itemProp
)
)

View File

@@ -33,19 +33,20 @@
spellcheck="false"
@blur="handleBlur"
@keyup.enter="handleBlur"
@dragstart.prevent
@keydown.up.prevent="updateValueBy(step)"
@keydown.down.prevent="updateValueBy(-step)"
@keydown.page-up.prevent="updateValueBy(10 * step)"
@keydown.page-down.prevent="updateValueBy(-10 * step)"
/>
<div
ref="swipeElement"
:class="
cn(
'absolute inset-0 z-10 cursor-ew-resize',
'absolute inset-0 z-10 cursor-ew-resize touch-pan-y',
textEdit && 'pointer-events-none hidden'
)
"
@pointerdown="handlePointerDown"
@pointermove="handlePointerMove"
@pointerup="handlePointerUp"
@pointercancel="resetDrag"
/>
</div>
<slot />
@@ -65,7 +66,7 @@
</template>
<script setup lang="ts">
import { onClickOutside } from '@vueuse/core'
import { onClickOutside, usePointerSwipe, whenever } from '@vueuse/core'
import { computed, ref, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n'
@@ -73,8 +74,8 @@ import Button from '@/components/ui/button/Button.vue'
import { cn } from '@/utils/tailwindUtil'
const {
min,
max,
min = -Number.MAX_VALUE,
max = Number.MAX_VALUE,
step = 1,
disabled = false,
hideButtons = false,
@@ -96,6 +97,7 @@ const modelValue = defineModel<number>({ default: 0 })
const container = useTemplateRef<HTMLDivElement>('container')
const inputField = useTemplateRef<HTMLInputElement>('inputField')
const swipeElement = useTemplateRef('swipeElement')
const textEdit = ref(false)
onClickOutside(container, () => {
@@ -103,21 +105,11 @@ onClickOutside(container, () => {
})
function clamp(value: number): number {
const lo = min ?? -Infinity
const hi = max ?? Infinity
return Math.min(hi, Math.max(lo, value))
return Math.min(max, Math.max(min, value))
}
const canDecrement = computed(
() => modelValue.value > (min ?? -Infinity) && !disabled
)
const canIncrement = computed(
() => modelValue.value < (max ?? Infinity) && !disabled
)
const dragging = ref(false)
const dragDelta = ref(0)
const hasDragged = ref(false)
const canDecrement = computed(() => modelValue.value > min && !disabled)
const canIncrement = computed(() => modelValue.value < max && !disabled)
function handleBlur(e: Event) {
const target = e.target as HTMLInputElement
@@ -135,41 +127,27 @@ function handleBlur(e: Event) {
textEdit.value = false
}
function handlePointerDown(e: PointerEvent) {
if (e.button !== 0) return
if (disabled) return
const target = e.target as HTMLElement
target.setPointerCapture(e.pointerId)
dragging.value = true
dragDelta.value = 0
hasDragged.value = false
}
function handlePointerMove(e: PointerEvent) {
if (!dragging.value) return
dragDelta.value += e.movementX
const steps = (dragDelta.value / 10) | 0
if (steps === 0) return
hasDragged.value = true
const unclipped = modelValue.value + steps * step
dragDelta.value %= 10
modelValue.value = clamp(unclipped)
}
let dragDelta = 0
function handlePointerUp() {
if (!dragging.value) return
if (isSwiping.value) return
if (!hasDragged.value) {
textEdit.value = true
inputField.value?.focus()
inputField.value?.select()
}
resetDrag()
textEdit.value = true
inputField.value?.focus()
inputField.value?.select()
}
function resetDrag() {
dragging.value = false
dragDelta.value = 0
const { distanceX, isSwiping } = usePointerSwipe(swipeElement, {
onSwipeEnd: () => (dragDelta = 0)
})
whenever(distanceX, () => {
if (disabled) return
const delta = ((distanceX.value - dragDelta) / 10) | 0
dragDelta += delta * 10
modelValue.value = clamp(modelValue.value - delta * step)
})
function updateValueBy(delta: number) {
modelValue.value = Math.min(max, Math.max(min, modelValue.value + delta))
}
</script>

View File

@@ -1,6 +1,6 @@
<template>
<Avatar
class="aspect-square bg-interface-panel-selected-surface"
class="bg-interface-panel-selected-surface"
:image="photoUrl ?? undefined"
:icon="hasAvatar ? undefined : 'icon-[lucide--user]'"
:pt:icon:class="{ 'size-4': !hasAvatar }"

View File

@@ -178,7 +178,7 @@
v-show="isTemplateVisibleOnDistribution(template)"
:key="template.name"
ref="cardRefs"
size="compact"
size="tall"
variant="ghost"
rounded="lg"
:data-testid="`template-workflow-${template.name}`"
@@ -318,6 +318,20 @@
</Button>
</div>
</div>
<div class="flex">
<span
class="text-neutral flex items-center gap-1.5 text-xs font-bold"
>
<template v-if="isAppTemplate(template)">
<i class="icon-[lucide--panels-top-left]" />
{{ $t('builderToolbar.app', 'App') }}
</template>
<template v-else>
<i class="icon-[lucide--workflow]" />
{{ $t('builderToolbar.nodeGraph', 'Node Graph') }}
</template>
</span>
</div>
</div>
</CardBottom>
</template>
@@ -483,6 +497,8 @@ const {
const getEffectiveSourceModule = (template: TemplateInfo) =>
template.sourceModule || 'default'
const isAppTemplate = (template: TemplateInfo) => template.name.endsWith('.app')
const getBaseThumbnailSrc = (template: TemplateInfo) => {
const sm = getEffectiveSourceModule(template)
return getTemplateThumbnailUrl(template, sm, sm === 'default' ? '1' : '')

View File

@@ -138,8 +138,7 @@ onMounted(async () => {
toast.add({
severity: 'error',
summary: t('g.error'),
detail: t('toastMessages.failedToFetchLogs'),
life: 5000
detail: t('toastMessages.failedToFetchLogs')
})
}
})

View File

@@ -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

View File

@@ -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

View File

@@ -50,7 +50,9 @@
{{ t('g.dismiss') }}
</Button>
<Button variant="secondary" size="lg" @click="seeErrors">
{{ t('errorOverlay.seeErrors') }}
{{
appMode ? t('linearMode.error.goto') : t('errorOverlay.seeErrors')
}}
</Button>
</div>
</div>
@@ -69,6 +71,8 @@ import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useErrorGroups } from '@/components/rightSidePanel/errors/useErrorGroups'
defineProps<{ appMode?: boolean }>()
const { t } = useI18n()
const executionErrorStore = useExecutionErrorStore()
const rightSidePanelStore = useRightSidePanelStore()
@@ -94,6 +98,7 @@ function dismiss() {
}
function seeErrors() {
canvasStore.linearMode = false
if (canvasStore.canvas) {
canvasStore.canvas.deselectAll()
canvasStore.updateSelectedItems()

View File

@@ -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')
})
}
}

View File

@@ -5,8 +5,11 @@
v-if="isHelpCenterVisible"
class="help-center-popup"
:class="{
'sidebar-left': sidebarLocation === 'left',
'sidebar-right': sidebarLocation === 'right',
'sidebar-left':
triggerLocation === 'sidebar' && sidebarLocation === 'left',
'sidebar-right':
triggerLocation === 'sidebar' && sidebarLocation === 'right',
'topbar-right': triggerLocation === 'topbar',
'small-sidebar': isSmall
}"
>
@@ -60,6 +63,7 @@ const { isSmall = false } = defineProps<{
const {
isHelpCenterVisible,
triggerLocation,
sidebarLocation,
closeHelpCenter,
handleWhatsNewDismissed
@@ -97,6 +101,25 @@ 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;

View File

@@ -141,7 +141,7 @@ onMounted(async () => {
if (isStandaloneMode && props.modelUrl) {
await viewer.initializeStandaloneViewer(containerRef.value, props.modelUrl)
} else if (props.node) {
const source = await useLoad3dService().getLoad3dAsync(props.node)
const source = useLoad3dService().getLoad3d(props.node)
if (source) {
await viewer.initializeViewer(containerRef.value, source)
}

View File

@@ -1,5 +1,5 @@
import { computed, reactive, ref, watch } from 'vue'
import type { Ref } from 'vue'
import { computed, reactive, ref, toValue, watch } from 'vue'
import type { MaybeRefOrGetter } from 'vue'
import Fuse from 'fuse.js'
import type { IFuseOptions } from 'fuse.js'
@@ -227,7 +227,7 @@ function searchErrorGroups(groups: ErrorGroup[], query: string) {
}
export function useErrorGroups(
searchQuery: Ref<string>,
searchQuery: MaybeRefOrGetter<string>,
t: (key: string) => string
) {
const executionErrorStore = useExecutionErrorStore()
@@ -584,7 +584,7 @@ export function useErrorGroups(
})
const filteredGroups = computed<ErrorGroup[]>(() => {
const query = searchQuery.value.trim()
const query = toValue(searchQuery).trim()
return searchErrorGroups(tabErrorGroups.value, query)
})

View File

@@ -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() {

View File

@@ -11,13 +11,12 @@
}"
@click="onLogoMenuClick($event)"
>
<div class="flex items-center gap-0.5">
<div class="flex size-8 items-center justify-center rounded-lg bg-black">
<ComfyLogo
alt="ComfyUI Logo"
class="comfyui-logo h-[18px] w-[18px]"
class="comfyui-logo h-[18px] w-[18px] text-white"
mode="fill"
/>
<i class="icon-[lucide--chevron-down] size-3 text-muted-foreground" />
</div>
</div>

View File

@@ -41,7 +41,7 @@
v-if="userStore.isMultiUserServer"
:is-small="isSmall"
/>
<SidebarHelpCenterIcon :is-small="isSmall" />
<SidebarHelpCenterIcon v-if="!isIntegratedTabBar" :is-small="isSmall" />
<SidebarBottomPanelToggleButton v-if="!isCloud" :is-small="isSmall" />
<SidebarShortcutsToggleButton :is-small="isSmall" />
<SidebarSettingsButton :is-small="isSmall" />
@@ -95,6 +95,9 @@ 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 ||

View File

@@ -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>

View File

@@ -615,8 +615,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 +661,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')
})
}
}

View File

@@ -30,7 +30,7 @@
<UserAvatar
v-else
:photo-url="photoURL"
:class="compact && 'h-full w-auto'"
:class="compact && 'size-full'"
/>
<i v-if="showArrow" class="icon-[lucide--chevron-down] size-4 px-1" />

View File

@@ -0,0 +1,21 @@
<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>

View File

@@ -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
})

View File

@@ -83,18 +83,13 @@
v-if="isIntegratedTabBar"
class="ml-auto flex shrink-0 items-center gap-2 px-2"
>
<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" />
<TopMenuHelpButton />
<CurrentUserButton
v-if="isLoggedIn"
:show-arrow="false"
compact
class="grid w-10 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" />
@@ -107,20 +102,21 @@ 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 { isCloud, isDesktop, isNightly } from '@/platform/distribution/types'
import { isDesktop } from '@/platform/distribution/types'
import { whileMouseDown } from '@/utils/mouseDownUtil'
import WorkflowOverflowMenu from './WorkflowOverflowMenu.vue'
@@ -142,14 +138,8 @@ const commandStore = useCommandStore()
const { isLoggedIn } = useCurrentUser()
const isIntegratedTabBar = computed(
() => settingStore.get('Comfy.UI.TabBarLayout') !== 'Legacy'
() => settingStore.get('Comfy.UI.TabBarLayout') === 'Integrated'
)
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)

View File

@@ -24,7 +24,7 @@ function handleWheel(e: WheelEvent) {
let dragging = false
function handleDown(e: PointerEvent) {
if (e.button !== 0) return
if (e.button !== 0 && e.button !== 1) return
const zoomPaneEl = zoomPane.value
if (!zoomPaneEl) return

View File

@@ -2,7 +2,7 @@ import type { VariantProps } from 'cva'
import { cva } from 'cva'
export const buttonVariants = cva({
base: 'relative inline-flex items-center justify-center gap-2 cursor-pointer whitespace-nowrap appearance-none border-none rounded-md text-sm font-medium font-inter transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([width]):not([height])]:size-4 [&_svg]:shrink-0',
base: 'relative inline-flex items-center justify-center gap-2 cursor-pointer touch-manipulation whitespace-nowrap appearance-none border-none rounded-md text-sm font-medium font-inter transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([width]):not([height])]:size-4 [&_svg]:shrink-0',
variants: {
variant: {
secondary:

View File

@@ -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
}

View File

@@ -5,15 +5,19 @@ 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() {
export function useHelpCenter(
triggerFrom: HelpCenterTriggerLocation = 'sidebar'
) {
const settingStore = useSettingStore()
const releaseStore = useReleaseStore()
const helpCenterStore = useHelpCenterStore()
const { isVisible: isHelpCenterVisible } = storeToRefs(helpCenterStore)
const { isVisible: isHelpCenterVisible, triggerLocation } =
storeToRefs(helpCenterStore)
const { shouldShowRedDot: showReleaseRedDot } = storeToRefs(releaseStore)
const conflictDetection = useConflictDetection()
@@ -38,9 +42,9 @@ export function useHelpCenter() {
*/
const toggleHelpCenter = () => {
useTelemetry()?.trackUiButtonClicked({
button_id: 'sidebar_help_center_toggled'
button_id: `${triggerFrom}_help_center_toggled`
})
helpCenterStore.toggle()
helpCenterStore.toggle(triggerFrom)
}
const closeHelpCenter = () => {
@@ -86,6 +90,7 @@ export function useHelpCenter() {
return {
isHelpCenterVisible,
triggerLocation,
shouldShowRedDot,
sidebarLocation,
toggleHelpCenter,

View File

@@ -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 []
}

View File

@@ -1,7 +1,6 @@
import { computed, watch } from 'vue'
import { computed } 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'
@@ -18,20 +17,16 @@ 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() {

View File

@@ -1,14 +1,21 @@
import { t } from '@/i18n'
import { useSettingStore } from '@/platform/settings/settingStore'
import { buildFeedbackUrl } from '@/platform/support/config'
import { getDistribution, ZENDESK_FIELDS } from '@/platform/support/config'
import { useExtensionService } from '@/services/extensionService'
import type { ActionBarButton } from '@/types/comfy'
const feedbackUrl = buildFeedbackUrl()
const ZENDESK_BASE_URL = 'https://support.comfy.org/hc/en-us/requests/new'
const ZENDESK_FEEDBACK_FORM_ID = '43066738713236'
const distribution = getDistribution()
const params = new URLSearchParams({
ticket_form_id: ZENDESK_FEEDBACK_FORM_ID,
[ZENDESK_FIELDS.DISTRIBUTION]: distribution
})
const feedbackUrl = `${ZENDESK_BASE_URL}?${params.toString()}`
const buttons: ActionBarButton[] = [
{
icon: 'icon-[lucide--message-square-text]',
icon: 'icon-[lucide--message-circle-question-mark]',
label: t('actionbar.feedback'),
tooltip: t('actionbar.feedbackTooltip'),
onClick: () => {
@@ -18,10 +25,6 @@ const buttons: ActionBarButton[] = [
]
useExtensionService().registerExtension({
name: 'Comfy.FeedbackButton',
get actionBarButtons() {
return useSettingStore().get('Comfy.UI.TabBarLayout') === 'Legacy'
? buttons
: []
}
name: 'Comfy.Cloud.FeedbackButton',
actionBarButtons: buttons
})

View File

@@ -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')
})
}
}

View File

@@ -1,6 +1,5 @@
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
import type { NodeOutputWith } from '@/schemas/apiSchema'
import { appendCloudResParam } from '@/platform/distribution/cloudPreviewUtil'
import { api } from '@/scripts/api'
import { app } from '@/scripts/app'
import { useExtensionService } from '@/services/extensionService'
@@ -29,7 +28,6 @@ useExtensionService().registerExtension({
const toUrl = (record: Record<string, string>) => {
const params = new URLSearchParams(record)
appendCloudResParam(params, record.filename)
return api.apiURL(`/view?${params}${rand}`)
}

View File

@@ -559,8 +559,6 @@ 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
@@ -5182,10 +5180,8 @@ 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 - (lineCount + 1) * lineHeight
y = y || this.canvas.offsetHeight - 80
ctx.save()
ctx.translate(x, y)
@@ -5193,26 +5189,18 @@ 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,
lineHeight * line++
)
ctx.fillText(`I: ${this.graph.iteration}`, 5, lineHeight * line++)
ctx.fillText(`T: ${this.graph.globaltime.toFixed(2)}s`, 5, 13 * 1)
ctx.fillText(`I: ${this.graph.iteration}`, 5, 13 * 2)
ctx.fillText(
`N: ${this.graph._nodes.length} [${this.visible_nodes.length}]`,
5,
lineHeight * line++
13 * 3
)
ctx.fillText(`V: ${this.graph._version}`, 5, lineHeight * line++)
ctx.fillText(`FPS:${this.fps.toFixed(2)}`, 5, lineHeight * line++)
ctx.fillText(`V: ${this.graph._version}`, 5, 13 * 4)
ctx.fillText(`FPS:${this.fps.toFixed(2)}`, 5, 13 * 5)
} else {
ctx.fillText('No graph selected', 5, lineHeight * line++)
}
if (this.info_text) {
ctx.fillText(this.info_text, 5, lineHeight * line++)
ctx.fillText('No graph selected', 5, 13 * 1)
}
ctx.restore()
}

View File

@@ -71,9 +71,6 @@
"Comfy_Canvas_PasteFromClipboard": {
"label": "لصق"
},
"Comfy_Canvas_PasteFromClipboardWithConnect": {
"label": "لصق مع الاتصال"
},
"Comfy_Canvas_ResetView": {
"label": "إعادة تعيين العرض"
},

View File

@@ -2,9 +2,7 @@
"actionbar": {
"dockToTop": "إلصق بالأعلى",
"feedback": "ملاحظات",
"feedbackTooltip": "إرسال ملاحظات",
"share": "مشاركة",
"shareTooltip": "مشاركة سير العمل"
"feedbackTooltip": "إرسال ملاحظات"
},
"apiNodesCostBreakdown": {
"costPerRun": "التكلفة لكل تشغيل",
@@ -323,24 +321,18 @@
"y": "ص"
},
"breadcrumbsMenu": {
"app": "التطبيق",
"clearWorkflow": "مسح سير العمل",
"deleteBlueprint": "حذف المخطط",
"deleteWorkflow": "حذف سير العمل",
"duplicate": "تكرار",
"editBuilderMode": "تعديل التطبيق",
"enterAppMode": "الدخول إلى وضع التطبيق",
"enterBuilderMode": "دخول وضع بناء التطبيق",
"enterNewName": "أدخل اسمًا جديدًا",
"enterNodeGraph": "دخول رسم العقد",
"exitAppMode": "الخروج من وضع التطبيق",
"graph": "الرسم البياني",
"missingNodesWarning": "يحتوي سير العمل على عقد غير مدعومة (مظللة باللون الأحمر).",
"share": "مشاركة",
"workflowActions": "إجراءات سير العمل"
},
"builderMenu": {
"enterAppMode": "الدخول إلى وضع التطبيق",
"exitAppBuilder": "الخروج من مُنشئ التطبيق"
},
"builderToolbar": {
@@ -362,7 +354,6 @@
"defaultViewTitle": "تعيين العرض الافتراضي لهذا سير العمل",
"emptyWorkflowPrompt": "هل ترغب في البدء بقالب؟",
"emptyWorkflowTitle": "لا يحتوي سير العمل هذا على أي عقد",
"exitToWorkflow": "الخروج إلى سير العمل",
"inputs": "المدخلات",
"inputsDescription": "اختر المدخلات",
"label": "منشئ التطبيقات",
@@ -516,80 +507,6 @@
"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": "فتح مساحة القص",
@@ -1425,7 +1342,7 @@
"appBuilder": "منشئ التطبيقات",
"apps": "التطبيقات",
"appsEmptyMessage": "سيتم عرض التطبيقات المحفوظة هنا.\nانقر أدناه لبناء تطبيقك الأول.",
"appsEmptyMessageAction": "انقر أدناه لإنشاء أول تطبيق لك."
"enterAppMode": "الدخول إلى وضع التطبيق"
},
"arrange": {
"atLeastOne": "عقدة واحدة على الأقل",
@@ -1439,18 +1356,14 @@
},
"backToWorkflow": "العودة إلى سير العمل",
"beta": "وضع التطبيق تجريبي - أرسل ملاحظاتك",
"buildAnApp": "أنشئ تطبيقًا",
"builder": {
"exit": "خروج من البناء",
"exitConfirmMessage": "لديك تغييرات غير محفوظة ستفقد\nهل تريد الخروج بدون حفظ؟",
"exitConfirmTitle": "الخروج من بناء التطبيق؟",
"inputPlaceholder": "سيتم عرض المدخلات هنا",
"inputsDesc": "سيتفاعل المستخدمون مع هذه المدخلات ويعدلونها لإنشاء النتائج.",
"inputsExample": "أمثلة: \"تحميل صورة\"، \"موجه نصي\"، \"خطوات\"",
"noInputs": "لم تتم إضافة أي مدخلات بعد",
"noOutputs": "لم تتم إضافة أي عقد إخراج بعد",
"outputPlaceholder": "سيتم عرض عقد الإخراج هنا",
"outputRequiredPlaceholder": "مطلوب عقدة واحدة على الأقل",
"outputsDesc": "وصل عقدة إخراج واحدة على الأقل حتى يتمكن المستخدمون من رؤية النتائج بعد التشغيل.",
"outputsExample": "أمثلة: \"حفظ صورة\" أو \"حفظ فيديو\"",
"promptAddInputs": "انقر على معلمات العقدة لإضافتها هنا كمدخلات",
@@ -1458,7 +1371,6 @@
"title": "وضع بناء التطبيق",
"unknownWidget": "عنصر الواجهة غير مرئي"
},
"cancelThisRun": "إلغاء هذا التشغيل",
"downloadAll": "تنزيل الكل",
"dragAndDropImage": "اسحب وأسقط صورة",
"emptyWorkflowExplanation": "سير العمل الخاص بك فارغ. تحتاج إلى بعض العقد أولاً لبدء بناء التطبيق.",
@@ -1481,8 +1393,6 @@
"controls": "تظهر المخرجات في الأسفل، وعناصر التحكم على اليمين. كل شيء آخر يبقى بعيدًا.",
"getStarted": "انقر على {runButton} للبدء.",
"message": "عرض مبسط يخفي رسم العقد حتى تتمكن من التركيز على الإنشاء.",
"noOutputs": "يحتاج التطبيق إلى {count} على الأقل ليكون قابلاً للاستخدام.",
"oneOutput": "مخرج واحد",
"sharing": "المشاركة سهلة: أنشئ سير العمل الخاص بك، افتح وضع التطبيق، انقر بزر الماوس الأيمن على علامة التبويب، ثم صدّر. عندما يفتح الآخرون ملفك، سيتم تشغيله مباشرة في هذا العرض النظيف. يمكنك مشاركة سير عمل قوي كأداة بسيطة دون الحاجة لفهم مخططات العقد.",
"title": "مرحبًا بك في وضع التطبيق"
}
@@ -1855,6 +1765,7 @@
"execute": "تنفيذ",
"fullscreen": "ملء الشاشة",
"help": "مساعدة",
"helpAndFeedback": "المساعدة والتعليقات",
"hideMenu": "إخفاء القائمة",
"instant": "فوري",
"instantTooltip": "سيتم وضع سير العمل في قائمة الانتظار فور انتهاء التوليد",
@@ -1955,7 +1866,6 @@
"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": "سير العمل السابق المفتوح",
@@ -2127,7 +2037,6 @@
"lotus": "lotus",
"ltxv": "ltxv",
"mask": "قناع",
"math": "رياضيات",
"model": "نموذج",
"model_merging": "دمج النماذج",
"model_patches": "تصحيحات النموذج",
@@ -2231,18 +2140,6 @@
},
"title": "جهازك غير مدعوم"
},
"openSharedWorkflow": {
"author": "المؤلف:",
"copyAssetsAndOpen": "استيراد الأصول وفتح سير العمل",
"copyDescription": "فتح سير العمل سينشئ نسخة جديدة في مساحة العمل الخاصة بك",
"dialogTitle": "فتح سير العمل المشترك",
"importFailed": "فشل استيراد أصول سير العمل",
"loadError": "تعذر تحميل سير العمل المشترك هذا. يرجى المحاولة لاحقًا.",
"nonPublicAssetsWarningLine1": "يأتي هذا سير العمل مع أصول غير عامة.",
"nonPublicAssetsWarningLine2": "سيتم استيراد هذه الأصول إلى مكتبتك عند فتح سير العمل",
"openWithoutImporting": "فتح بدون استيراد",
"openWorkflow": "فتح سير العمل"
},
"painter": {
"background": "الخلفية",
"brush": "فرشاة",
@@ -2701,47 +2598,6 @@
"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": "اختصارات لوحة المفاتيح",

View File

@@ -1419,25 +1419,6 @@
}
}
},
"ComfyMathExpression": {
"display_name": "تعبير رياضي",
"inputs": {
"expression": {
"name": "تعبير"
},
"values": {
"name": "القيم"
}
},
"outputs": {
"0": {
"tooltip": null
},
"1": {
"tooltip": null
}
}
},
"ComfySwitchNode": {
"display_name": "مفتاح التحويل",
"inputs": {
@@ -15131,64 +15112,6 @@
}
}
},
"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": {

View File

@@ -71,9 +71,6 @@
"Comfy_Canvas_PasteFromClipboard": {
"label": "Paste"
},
"Comfy_Canvas_PasteFromClipboardWithConnect": {
"label": "Paste with Connect"
},
"Comfy_Canvas_ResetView": {
"label": "Reset View"
},

View File

@@ -301,7 +301,6 @@
"1x": "1x",
"2x": "2x",
"beta": "BETA",
"comfyCloud": "Comfy Cloud",
"nightly": "NIGHTLY",
"profile": "Profile",
"noItems": "No items",
@@ -969,6 +968,7 @@
"customNodesManager": "Custom Nodes Manager",
"settings": "Settings",
"help": "Help",
"helpAndFeedback": "Help & Feedback",
"queue": "Queue Panel",
"fullscreen": "Fullscreen"
},
@@ -1330,6 +1330,7 @@
"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",
@@ -1596,7 +1597,6 @@
"kandinsky5": "kandinsky5",
"hooks": "hooks",
"combine": "combine",
"math": "math",
"logic": "logic",
"cond single": "cond single",
"context": "context",
@@ -3180,6 +3180,8 @@
"cancelThisRun": "Cancel this run",
"deleteAllAssets": "Delete all assets from this run",
"hasCreditCost": "Requires additional credits",
"viewGraph": "View node graph",
"mobileNoWorkflow": "This workflow hasn't been built for app mode. Try a different one.",
"welcome": {
"title": "App Mode",
"message": "A simplified view that hides the node graph so you can focus on creating.",
@@ -3224,6 +3226,19 @@
"outputPlaceholder": "Output nodes will show up here",
"outputRequiredPlaceholder": "At least one node is required"
},
"error": {
"header": "This app encountered an error",
"log": "Error Logs",
"mobileFixable": "Check {0} for errors",
"requiresGraph": "Something went wrong during generation. This could be due to invalid hidden inputs, missing resources, or workflow configuration issues.",
"promptVisitGraph": "View the node graph to see the full error.",
"getHelp": "For help, view our {0}, {1}, or {2} with the copied error.",
"goto": "Show errors in graph",
"github": "submit a GitHub issue",
"guide": "troubleshooting guide",
"support": "contact our support",
"promptShow": "Show error report"
},
"queue": {
"clickToClear": "Click to clear queue",
"clear": "Clear queue"

View File

@@ -1419,25 +1419,6 @@
}
}
},
"ComfyMathExpression": {
"display_name": "Math Expression",
"inputs": {
"expression": {
"name": "expression"
},
"values": {
"name": "values"
}
},
"outputs": {
"0": {
"tooltip": null
},
"1": {
"tooltip": null
}
}
},
"ComfySwitchNode": {
"display_name": "Switch",
"inputs": {
@@ -15131,64 +15112,6 @@
}
}
},
"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": {

View File

@@ -71,9 +71,6 @@
"Comfy_Canvas_PasteFromClipboard": {
"label": "Pegar"
},
"Comfy_Canvas_PasteFromClipboardWithConnect": {
"label": "Pegar con conectar"
},
"Comfy_Canvas_ResetView": {
"label": "Restablecer vista"
},

View File

@@ -2,9 +2,7 @@
"actionbar": {
"dockToTop": "Acoplar en la parte superior",
"feedback": "Comentarios",
"feedbackTooltip": "Comentarios",
"share": "Compartir",
"shareTooltip": "Compartir flujo de trabajo"
"feedbackTooltip": "Comentarios"
},
"apiNodesCostBreakdown": {
"costPerRun": "Costo por ejecución",
@@ -323,24 +321,18 @@
"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": {
@@ -362,7 +354,6 @@
"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",
@@ -516,80 +507,6 @@
"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",
@@ -1425,7 +1342,7 @@
"appBuilder": "Constructor de aplicaciones",
"apps": "Aplicaciones",
"appsEmptyMessage": "Las aplicaciones guardadas aparecerán aquí.\nHaz clic abajo para crear tu primera aplicación.",
"appsEmptyMessageAction": "Haz clic abajo para crear tu primera aplicación."
"enterAppMode": "Entrar en modo de aplicación"
},
"arrange": {
"atLeastOne": "al menos uno",
@@ -1439,18 +1356,14 @@
},
"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",
@@ -1458,7 +1371,6 @@
"title": "Modo constructor de aplicaciones",
"unknownWidget": "Widget no visible"
},
"cancelThisRun": "Cancelar 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.",
@@ -1481,8 +1393,6 @@
"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"
}
@@ -1855,6 +1765,7 @@
"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",
@@ -1955,7 +1866,6 @@
"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",
@@ -2127,7 +2037,6 @@
"lotus": "lotus",
"ltxv": "ltxv",
"mask": "mask",
"math": "matemáticas",
"model": "modelo",
"model_merging": "fusión_de_modelos",
"model_patches": "parches_de_modelo",
@@ -2231,18 +2140,6 @@
},
"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",
@@ -2701,47 +2598,6 @@
"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",

View File

@@ -1419,25 +1419,6 @@
}
}
},
"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": {
@@ -15131,64 +15112,6 @@
}
}
},
"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": {

View File

@@ -71,9 +71,6 @@
"Comfy_Canvas_PasteFromClipboard": {
"label": "چسباندن"
},
"Comfy_Canvas_PasteFromClipboardWithConnect": {
"label": "چسباندن با اتصال"
},
"Comfy_Canvas_ResetView": {
"label": "بازنشانی نما"
},

View File

@@ -2,9 +2,7 @@
"actionbar": {
"dockToTop": "چسباندن به بالا",
"feedback": "بازخورد",
"feedbackTooltip": "بازخورد",
"share": "اشتراک‌گذاری",
"shareTooltip": "اشتراک‌گذاری گردش‌کار"
"feedbackTooltip": "بازخورد"
},
"apiNodesCostBreakdown": {
"costPerRun": "هزینه هر اجرا",
@@ -323,24 +321,18 @@
"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": {
@@ -362,7 +354,6 @@
"defaultViewTitle": "تنظیم نمای پیش‌فرض برای این گردش‌کار",
"emptyWorkflowPrompt": "آیا می‌خواهید با یک قالب شروع کنید؟",
"emptyWorkflowTitle": "این ورک‌فلو هیچ node ندارد",
"exitToWorkflow": "بازگشت به workflow",
"inputs": "ورودی‌ها",
"inputsDescription": "انتخاب ورودی‌ها",
"label": "سازنده اپلیکیشن",
@@ -516,80 +507,6 @@
"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",
@@ -1425,7 +1342,7 @@
"appBuilder": "سازنده اپلیکیشن",
"apps": "اپلیکیشن‌ها",
"appsEmptyMessage": "اپلیکیشن‌های ذخیره‌شده اینجا نمایش داده می‌شوند.\nبرای ساخت اولین اپلیکیشن خود، روی دکمه زیر کلیک کنید.",
"appsEmptyMessageAction": "برای ساخت اولین اپلیکیشن خود، روی دکمه زیر کلیک کنید."
"enterAppMode": "ورود به حالت اپلیکیشن"
},
"arrange": {
"atLeastOne": "یک",
@@ -1439,18 +1356,14 @@
},
"backToWorkflow": "بازگشت به جریان کاری",
"beta": "حالت برنامه بتا - ارسال بازخورد",
"buildAnApp": "ساخت اپلیکیشن",
"builder": {
"exit": "خروج از حالت ساخت",
"exitConfirmMessage": "تغییرات ذخیره‌نشده شما از بین خواهد رفت\nخروج بدون ذخیره؟",
"exitConfirmTitle": "خروج از حالت ساخت اپلیکیشن؟",
"inputPlaceholder": "ورودی‌ها اینجا نمایش داده می‌شوند",
"inputsDesc": "کاربران می‌توانند این موارد را تنظیم کنند تا خروجی مورد نظر خود را تولید نمایند.",
"inputsExample": "مثال‌ها: «بارگذاری تصویر»، «متن راهنما»، «تعداد مراحل»",
"noInputs": "هنوز ورودی‌ای اضافه نشده است",
"noOutputs": "هنوز گره خروجی اضافه نشده است",
"outputPlaceholder": "نودهای خروجی اینجا نمایش داده می‌شوند",
"outputRequiredPlaceholder": "حداقل یک نود لازم است",
"outputsDesc": "حداقل یک گره خروجی متصل کنید تا کاربران پس از اجرا نتایج را مشاهده کنند.",
"outputsExample": "مثال‌ها: «ذخیره تصویر» یا «ذخیره ویدیو»",
"promptAddInputs": "برای افزودن پارامترها به عنوان ورودی، روی پارامترهای گره کلیک کنید",
@@ -1458,7 +1371,6 @@
"title": "حالت ساخت اپلیکیشن",
"unknownWidget": "ویجت قابل مشاهده نیست"
},
"cancelThisRun": "لغو این اجرا",
"downloadAll": "دانلود همه",
"dragAndDropImage": "تصویر را بکشید و رها کنید",
"emptyWorkflowExplanation": "جریان کاری شما خالی است. ابتدا باید چند node اضافه کنید تا بتوانید یک برنامه بسازید.",
@@ -1481,8 +1393,6 @@
"controls": "خروجی‌های شما در پایین نمایش داده می‌شوند و کنترل‌ها در سمت راست قرار دارند. سایر موارد خارج از دید باقی می‌مانند.",
"getStarted": "برای شروع روی {runButton} کلیک کنید.",
"message": "نمای ساده‌شده‌ای که گراف node را مخفی می‌کند تا بتوانید بر خلق تمرکز کنید.",
"noOutputs": "یک اپلیکیشن باید حداقل {count} خروجی داشته باشد تا قابل استفاده باشد.",
"oneOutput": "۱ خروجی",
"sharing": "اشتراک‌گذاری آسان است: workflow خود را بسازید، حالت App را باز کنید، روی تب راست‌کلیک کنید و خروجی بگیرید. وقتی دیگران فایل شما را باز می‌کنند، مستقیماً وارد این نمای ساده می‌شوند. می‌توانید workflowهای قدرتمند را به ابزارهای ساده تبدیل و به اشتراک بگذارید بدون اینکه کسی نیاز به درک گراف node داشته باشد.",
"title": "به حالت App خوش آمدید"
}
@@ -1855,6 +1765,7 @@
"execute": "اجرا",
"fullscreen": "تمام‌صفحه",
"help": "راهنما",
"helpAndFeedback": "راهنما و بازخورد",
"hideMenu": "مخفی کردن منو",
"instant": "فوری",
"instantTooltip": "workflow بلافاصله پس از پایان تولید در صف قرار می‌گیرد",
@@ -1955,7 +1866,6 @@
"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 قبلی بازشده",
@@ -2127,7 +2037,6 @@
"lotus": "lotus",
"ltxv": "ltxv",
"mask": "ماسک",
"math": "ریاضی",
"model": "مدل",
"model_merging": "ادغام مدل",
"model_patches": "وصله‌های مدل",
@@ -2231,18 +2140,6 @@
},
"title": "دستگاه شما پشتیبانی نمی‌شود"
},
"openSharedWorkflow": {
"author": "نویسنده:",
"copyAssetsAndOpen": "وارد کردن دارایی‌ها و باز کردن گردش‌کار",
"copyDescription": "باز کردن گردش‌کار یک نسخه جدید در فضای کاری شما ایجاد می‌کند",
"dialogTitle": "باز کردن گردش‌کار اشتراکی",
"importFailed": "وارد کردن دارایی‌های گردش‌کار ناموفق بود",
"loadError": "امکان بارگذاری این گردش‌کار اشتراکی وجود ندارد. لطفاً بعداً دوباره تلاش کنید.",
"nonPublicAssetsWarningLine1": "این گردش‌کار دارای دارایی‌های غیرعمومی است.",
"nonPublicAssetsWarningLine2": "این موارد هنگام باز کردن گردش‌کار به کتابخانه شما وارد می‌شوند",
"openWithoutImporting": "باز کردن بدون وارد کردن",
"openWorkflow": "باز کردن گردش‌کار"
},
"painter": {
"background": "پس‌زمینه",
"brush": "براش",
@@ -2701,47 +2598,6 @@
"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": "میانبرهای صفحه‌کلید",

View File

@@ -1419,25 +1419,6 @@
}
}
},
"ComfyMathExpression": {
"display_name": "عبارت ریاضی",
"inputs": {
"expression": {
"name": "عبارت"
},
"values": {
"name": "مقادیر"
}
},
"outputs": {
"0": {
"tooltip": null
},
"1": {
"tooltip": null
}
}
},
"ComfySwitchNode": {
"display_name": "سوئیچ",
"inputs": {
@@ -15131,64 +15112,6 @@
}
}
},
"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": {

View File

@@ -71,9 +71,6 @@
"Comfy_Canvas_PasteFromClipboard": {
"label": "Coller"
},
"Comfy_Canvas_PasteFromClipboardWithConnect": {
"label": "Coller avec connexion"
},
"Comfy_Canvas_ResetView": {
"label": "Réinitialiser la vue"
},

View File

@@ -2,9 +2,7 @@
"actionbar": {
"dockToTop": "Ancrer en haut",
"feedback": "Retour",
"feedbackTooltip": "Retour",
"share": "Partager",
"shareTooltip": "Partager le workflow"
"feedbackTooltip": "Retour"
},
"apiNodesCostBreakdown": {
"costPerRun": "Coût par exécution",
@@ -323,24 +321,18 @@
"y": "Y"
},
"breadcrumbsMenu": {
"app": "Application",
"clearWorkflow": "Effacer le workflow",
"deleteBlueprint": "Supprimer le plan",
"deleteWorkflow": "Supprimer le workflow",
"duplicate": "Dupliquer",
"editBuilderMode": "Modifier l'application",
"enterAppMode": "Entrer en mode application",
"enterBuilderMode": "Entrer dans le mode constructeur d'application",
"enterNewName": "Entrez un nouveau nom",
"enterNodeGraph": "Entrer dans le graphe de nœuds",
"exitAppMode": "Quitter le mode application",
"graph": "Graphe",
"missingNodesWarning": "Le workflow contient des nœuds non pris en charge (surlignés en rouge).",
"share": "Partager",
"workflowActions": "Actions du workflow"
},
"builderMenu": {
"enterAppMode": "Entrer en mode application",
"exitAppBuilder": "Quitter le constructeur d'application"
},
"builderToolbar": {
@@ -362,7 +354,6 @@
"defaultViewTitle": "Définir la vue par défaut pour ce workflow",
"emptyWorkflowPrompt": "Voulez-vous commencer avec un modèle ?",
"emptyWorkflowTitle": "Ce workflow ne contient aucun nœud",
"exitToWorkflow": "Quitter vers le workflow",
"inputs": "Entrées",
"inputsDescription": "Choisissez les entrées",
"label": "Créateur d'applications",
@@ -516,80 +507,6 @@
"red": "Rouge",
"yellow": "Jaune"
},
"comfyHubProfile": {
"checkingAccess": "Vérification de votre accès à la publication...",
"chooseProfilePicture": "Choisir une photo de profil",
"createProfile": "Créer le profil",
"createProfileButton": "Créer mon profil",
"createProfileTitle": "Créez votre profil Comfy Hub",
"creatingProfile": "Création du profil...",
"descriptionLabel": "Votre description",
"descriptionPlaceholder": "Parlez de vous à la communauté...",
"introDescription": "Publiez vos workflows, construisez votre portfolio et faites-vous découvrir par des millions d'utilisateurs",
"introSubtitle": "Pour partager votre workflow sur ComfyHub, créons d'abord votre profil.",
"introTitle": "Publier sur le ComfyHub",
"modalTitle": "Créez votre profil sur ComfyHub",
"nameLabel": "Votre nom",
"namePlaceholder": "Entrez votre nom ici",
"profileCreationNav": "Création du profil",
"startPublishingButton": "Commencer la publication",
"successDescription": "Vous pouvez maintenant télécharger votre workflow sur votre page créateur",
"successProfileLink": "comfy.com/p/{username}",
"successProfileUrl": "Votre page de profil est en ligne sur",
"successTitle": "Superbe, {'@'}{username} !",
"uploadCover": "+ Télécharger une couverture",
"uploadProfilePicture": "+ Télécharger une photo de profil",
"uploadWorkflowButton": "Télécharger mon workflow",
"usernameLabel": "Votre nom d'utilisateur (obligatoire)",
"usernamePlaceholder": "@"
},
"comfyHubPublish": {
"back": "Retour",
"createProfileCta": "Créer un profil",
"createProfileToPublish": "Créez un profil pour publier sur ComfyHub",
"exampleImage": "Image d'exemple {index}",
"examplesDescription": "Ajoutez jusqu'à {total} images d'exemple supplémentaires",
"maxExamples": "Vous pouvez sélectionner jusqu'à {max} exemples",
"next": "Suivant",
"publishButton": "Publier sur ComfyHub",
"selectAThumbnail": "Sélectionner une miniature",
"showLessTags": "Afficher moins...",
"showMoreTags": "Afficher plus...",
"stepDescribe": "Décrivez votre workflow",
"stepExamples": "Ajoutez des exemples de sortie",
"stepFinish": "Terminer la publication",
"suggestedTags": "Tags suggérés",
"tags": "Tags",
"tagsDescription": "Sélectionnez des tags pour que les utilisateurs trouvent votre workflow plus rapidement",
"tagsPlaceholder": "Entrez des tags correspondant à votre workflow pour aider les utilisateurs à le trouver, ex : #nanobanana, #anime ou #faceswap",
"thumbnailImage": "Image",
"thumbnailImageComparison": "Comparaison d'images",
"thumbnailPreview": "Aperçu de la miniature",
"thumbnailVideo": "Vidéo",
"title": "Publier sur ComfyHub",
"uploadAnImage": "Cliquez pour parcourir ou faites glisser une image",
"uploadComparison": "Télécharger avant et après",
"uploadComparisonAfterPrompt": "Après",
"uploadComparisonBeforePrompt": "Avant",
"uploadExampleImage": "Télécharger une image d'exemple",
"uploadPromptClickToBrowse": "Cliquez pour parcourir ou",
"uploadPromptDropImage": "déposez une image ici",
"uploadPromptDropVideo": "déposez une vidéo ici",
"uploadThumbnail": "Télécharger une image",
"uploadThumbnailHint": "Format 1:1 recommandé, 1080p max",
"uploadVideo": "Télécharger une vidéo",
"videoPreview": "Aperçu de la miniature vidéo",
"workflowDescription": "Description du workflow",
"workflowDescriptionPlaceholder": "Qu'est-ce qui rend votre workflow passionnant et unique ? Soyez précis pour que les utilisateurs sachent à quoi s'attendre.",
"workflowName": "Nom du workflow",
"workflowNamePlaceholder": "Astuce : saisissez un nom descriptif facile à rechercher",
"workflowType": "Type de workflow",
"workflowTypeEditing": "Édition",
"workflowTypeImageGeneration": "Génération d'image",
"workflowTypePlaceholder": "Sélectionnez le type",
"workflowTypeUpscaling": "Upscaling",
"workflowTypeVideoGeneration": "Génération de vidéo"
},
"commands": {
"clear": "Effacer le workflow",
"clipspace": "Ouvrir Clipspace",
@@ -1425,7 +1342,7 @@
"appBuilder": "Créateur d'applications",
"apps": "Applications",
"appsEmptyMessage": "Les applications enregistrées apparaîtront ici.\nCliquez ci-dessous pour créer votre première application.",
"appsEmptyMessageAction": "Cliquez ci-dessous pour créer votre première application."
"enterAppMode": "Entrer en mode application"
},
"arrange": {
"atLeastOne": "au moins un",
@@ -1439,18 +1356,14 @@
},
"backToWorkflow": "Retour au workflow",
"beta": "Mode App Bêta - Donnez votre avis",
"buildAnApp": "Créer une application",
"builder": {
"exit": "Quitter le mode créateur",
"exitConfirmMessage": "Vous avez des modifications non enregistrées qui seront perdues\nQuitter sans enregistrer ?",
"exitConfirmTitle": "Quitter le créateur dapplication ?",
"inputPlaceholder": "Les entrées apparaîtront ici",
"inputsDesc": "Les utilisateurs interagiront avec ces paramètres pour générer leurs résultats.",
"inputsExample": "Exemples : « Charger une image », « Prompt texte », « Étapes »",
"noInputs": "Aucune entrée ajoutée pour le moment",
"noOutputs": "Aucun nœud de sortie ajouté pour le moment",
"outputPlaceholder": "Les nœuds de sortie apparaîtront ici",
"outputRequiredPlaceholder": "Au moins un nœud est requis",
"outputsDesc": "Connectez au moins un nœud de sortie pour que les utilisateurs voient les résultats après lexécution.",
"outputsExample": "Exemples : « Enregistrer limage » ou « Enregistrer la vidéo »",
"promptAddInputs": "Cliquez sur les paramètres du nœud pour les ajouter ici comme entrées",
@@ -1458,7 +1371,6 @@
"title": "Mode créateur dapplication",
"unknownWidget": "Widget non visible"
},
"cancelThisRun": "Annuler cette exécution",
"downloadAll": "Tout télécharger",
"dragAndDropImage": "Glissez-déposez une image",
"emptyWorkflowExplanation": "Votre workflow est vide. Vous devez d'abord ajouter des nodes pour commencer à créer une application.",
@@ -1481,8 +1393,6 @@
"controls": "Vos sorties apparaissent en bas, vos contrôles sont à droite. Tout le reste reste à l'écart.",
"getStarted": "Cliquez sur {runButton} pour commencer.",
"message": "Une vue simplifiée qui masque le graphe de nœuds pour vous permettre de vous concentrer sur la création.",
"noOutputs": "Une application nécessite au moins {count} pour être utilisable.",
"oneOutput": "1 sortie",
"sharing": "Le partage est facile : créez votre workflow, ouvrez le mode App, faites un clic droit sur longlet et exportez. Quand dautres ouvrent votre fichier, il souvre directement dans cette vue épurée. Vous pouvez partager des workflows puissants comme des outils simples, sans que personne nait besoin de comprendre les graphes de nœuds.",
"title": "Bienvenue en mode App"
}
@@ -1855,6 +1765,7 @@
"execute": "Exécuter",
"fullscreen": "Plein écran",
"help": "Aide",
"helpAndFeedback": "Aide et commentaires",
"hideMenu": "Masquer le menu",
"instant": "Instantané",
"instantTooltip": "Le flux de travail sera mis en file d'attente immédiatement après la fin d'une génération",
@@ -1955,7 +1866,6 @@
"Open Sign In Dialog": "Ouvrir la boîte de dialogue de connexion",
"Open extra_model_paths_yaml": "Ouvrir extra_model_paths.yaml",
"Paste": "Coller",
"Paste with Connect": "Coller avec connexion",
"Pin/Unpin Selected Items": "Épingler/Désépingler les éléments sélectionnés",
"Pin/Unpin Selected Nodes": "Épingler/Désépingler les nœuds sélectionnés",
"Previous Opened Workflow": "Flux de travail ouvert précédent",
@@ -2127,7 +2037,6 @@
"lotus": "lotus",
"ltxv": "ltxv",
"mask": "masque",
"math": "math",
"model": "modèle",
"model_merging": "fusion_de_modèles",
"model_patches": "patches_de_modèle",
@@ -2231,18 +2140,6 @@
},
"title": "Votre appareil n'est pas pris en charge"
},
"openSharedWorkflow": {
"author": "Auteur :",
"copyAssetsAndOpen": "Importer les ressources et ouvrir le workflow",
"copyDescription": "Ouvrir le workflow créera une nouvelle copie dans votre espace de travail",
"dialogTitle": "Ouvrir un workflow partagé",
"importFailed": "Échec de l'importation des ressources du workflow",
"loadError": "Impossible de charger ce workflow partagé. Veuillez réessayer plus tard.",
"nonPublicAssetsWarningLine1": "Ce workflow contient des ressources non publiques.",
"nonPublicAssetsWarningLine2": "Celles-ci seront importées dans votre bibliothèque lors de l'ouverture du workflow",
"openWithoutImporting": "Ouvrir sans importer",
"openWorkflow": "Ouvrir le workflow"
},
"painter": {
"background": "Arrière-plan",
"brush": "Pinceau",
@@ -2701,47 +2598,6 @@
"default": "Par défaut",
"round": "Rond"
},
"shareNoOutputs": {
"message": "Vous êtes sur le point de partager une application sans sorties. Elle ne pourra pas être utilisée tant qu'une sortie n'est pas connectée.\n\nPartager quand même ?",
"shareAnyway": "Partager quand même",
"title": "L'application n'a pas de sorties"
},
"shareWorkflow": {
"acknowledgeCheckbox": "Je comprends que ces éléments médias seront publiés et rendus publics",
"checkingAssets": "Vérification de la visibilité des médias…",
"comfyHubButton": "Téléverser sur ComfyHub",
"comfyHubDescription": "ComfyHub est le hub communautaire officiel de ComfyUI.\nVotre workflow aura une page publique visible par tous.",
"comfyHubTitle": "Téléverser sur ComfyHub",
"copyLink": "Copier",
"createLinkButton": "Créer un lien",
"createLinkDescription": "Lorsque vous créez un lien pour votre workflow, vous partagerez ces éléments médias avec votre workflow",
"createLinkTitle": "Partager le workflow",
"creatingLink": "Création du lien...",
"hasChangesDescription": "Vous avez effectué des modifications depuis la dernière publication de ce workflow.",
"hasChangesTitle": "Partager le workflow",
"inLibrary": "Dans la bibliothèque",
"linkCopied": "Copié !",
"loadFailed": "Échec du chargement du workflow partagé",
"loadingTitle": "Partager le workflow",
"mediaLabel": "{count} fichier média | {count} fichiers médias",
"modelsLabel": "{count} modèle | {count} modèles",
"privateAssetsDescription": "Votre workflow contient des modèles et/ou fichiers médias privés",
"publishToHubTab": "Publier",
"publishedOn": "Publié le {date}",
"saveButton": "Enregistrer le workflow",
"saveFailedDescription": "Échec de l'enregistrement du workflow. Veuillez réessayer.",
"saveFailedTitle": "Échec de l'enregistrement",
"saving": "Enregistrement...",
"shareLinkTab": "Partager",
"shareUrlLabel": "URL de partage",
"successDescription": "Toute personne disposant de ce lien peut voir et utiliser ce workflow. Si vous apportez des modifications à ce workflow, vous pouvez republier pour mettre à jour la version partagée.",
"successTitle": "Workflow publié avec succès !",
"unsavedDescription": "Vous devez enregistrer votre workflow avant de le partager. Enregistrez-le maintenant pour continuer.",
"unsavedTitle": "Enregistrez d'abord le workflow",
"updateLinkButton": "Mettre à jour le lien",
"updatingLink": "Mise à jour du lien...",
"workflowNameLabel": "Nom du workflow"
},
"shortcuts": {
"essentials": "Essentiel",
"keyboardShortcuts": "Raccourcis clavier",

Some files were not shown because too many files have changed in this diff Show More