mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-21 12:57:30 +00:00
Compare commits
80 Commits
core/1.6
...
node-group
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1f30b96f9 | ||
|
|
c13190cd07 | ||
|
|
00f031e382 | ||
|
|
04153caaf5 | ||
|
|
210bfdeb7d | ||
|
|
ce0726d85e | ||
|
|
dd69f9dc30 | ||
|
|
3f261f0e53 | ||
|
|
3b2cc23f65 | ||
|
|
c50a86b258 | ||
|
|
1a8c2bba42 | ||
|
|
fc09951b3e | ||
|
|
76d5f39607 | ||
|
|
9d3bc0f173 | ||
|
|
d9b350e159 | ||
|
|
44610674ee | ||
|
|
9bfce5b8d0 | ||
|
|
8986fa356a | ||
|
|
0c4fd4af1c | ||
|
|
30cd46ce1f | ||
|
|
3122c33310 | ||
|
|
91d8d04dc6 | ||
|
|
8f5aa1ff08 | ||
|
|
e076783b89 | ||
|
|
04c23001fc | ||
|
|
cb265fb0bf | ||
|
|
e9211fe377 | ||
|
|
ffc7febeac | ||
|
|
b15e626607 | ||
|
|
906b5e35a3 | ||
|
|
e8cd9c7642 | ||
|
|
93e184e379 | ||
|
|
1d02cd3c47 | ||
|
|
b86e3f71cb | ||
|
|
1ece2462bd | ||
|
|
73ecacfa2d | ||
|
|
67e6df7c72 | ||
|
|
7e8510028d | ||
|
|
dd4dd8b68a | ||
|
|
7111022617 | ||
|
|
daee073045 | ||
|
|
a1a834a76d | ||
|
|
c437d32691 | ||
|
|
0130d41be5 | ||
|
|
527561d148 | ||
|
|
1c4481c342 | ||
|
|
07000a23d4 | ||
|
|
ea6c9e7ca5 | ||
|
|
90698fced6 | ||
|
|
9716aea10d | ||
|
|
077ded2cce | ||
|
|
ffb20b8789 | ||
|
|
549a2fdc92 | ||
|
|
e123295423 | ||
|
|
613b44610a | ||
|
|
e82d795ff9 | ||
|
|
ba31f8fa68 | ||
|
|
f2eb4e1519 | ||
|
|
975c2248c5 | ||
|
|
477f4b275d | ||
|
|
179b8c22a9 | ||
|
|
6525389273 | ||
|
|
59fc5ac77e | ||
|
|
ed844e04b8 | ||
|
|
972af1977d | ||
|
|
5ac0b7b181 | ||
|
|
31ea39e44c | ||
|
|
e65653c107 | ||
|
|
e46706777c | ||
|
|
1a817a48cb | ||
|
|
871967349f | ||
|
|
11258f4a95 | ||
|
|
c3e05c2a10 | ||
|
|
ea1e776dcc | ||
|
|
f0c273f845 | ||
|
|
ea489851ed | ||
|
|
5717c33a0b | ||
|
|
c31919c418 | ||
|
|
2e7de4701e | ||
|
|
f53723da0f |
47
.github/workflows/i18n-node-defs.yaml
vendored
Normal file
47
.github/workflows/i18n-node-defs.yaml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
name: Update Node Definitions Locales
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
trigger_type:
|
||||
description: 'Type of trigger (manual or automatic)'
|
||||
required: false
|
||||
type: string
|
||||
default: 'manual'
|
||||
|
||||
jobs:
|
||||
update-locales:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v2.1
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install chromium --with-deps
|
||||
working-directory: ComfyUI_frontend
|
||||
- name: Start dev server
|
||||
# Run electron dev server as it is a superset of the web dev server
|
||||
# We do want electron specific UIs to be translated.
|
||||
run: npm run dev:electron &
|
||||
working-directory: ComfyUI_frontend
|
||||
- name: Update en.json
|
||||
run: npm run collect-i18n -- scripts/collect-i18n-node-defs.ts
|
||||
env:
|
||||
PLAYWRIGHT_TEST_URL: http://localhost:5173
|
||||
working-directory: ComfyUI_frontend
|
||||
- name: Update translations
|
||||
run: npm run locale
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
working-directory: ComfyUI_frontend
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
token: ${{ secrets.PR_GH_TOKEN }}
|
||||
commit-message: "Update locales for node definitions"
|
||||
title: "Update locales for node definitions"
|
||||
body: |
|
||||
Automated PR to update locales for node definitions
|
||||
|
||||
This PR was created automatically by the frontend update workflow.
|
||||
branch: update-locales-node-defs-{{ github.event.inputs.trigger_type }}-{{ github.run_id }}
|
||||
base: main
|
||||
labels: dependencies
|
||||
7
.github/workflows/i18n.yaml
vendored
7
.github/workflows/i18n.yaml
vendored
@@ -5,6 +5,8 @@ on:
|
||||
|
||||
jobs:
|
||||
update-locales:
|
||||
# Don't run on fork PRs
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v2.1
|
||||
@@ -17,13 +19,12 @@ jobs:
|
||||
run: npm run dev:electron &
|
||||
working-directory: ComfyUI_frontend
|
||||
- name: Update en.json
|
||||
run: npm run collect-i18n
|
||||
run: npm run collect-i18n -- scripts/collect-i18n-general.ts
|
||||
env:
|
||||
PLAYWRIGHT_TEST_URL: http://localhost:5173
|
||||
working-directory: ComfyUI_frontend
|
||||
- name: Update translations
|
||||
# Pipe output so that it doesn't error out on stdout.clearLine
|
||||
run: npm run locale 2>&1 | cat
|
||||
run: npm run locale
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
working-directory: ComfyUI_frontend
|
||||
|
||||
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@@ -25,6 +25,8 @@ jobs:
|
||||
id: current_version
|
||||
run: echo ::set-output name=version::$(node -p "require('./package.json').version")
|
||||
- name: Build project
|
||||
env:
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
run: |
|
||||
npm ci
|
||||
npm run build
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -41,4 +41,7 @@ browser_tests/*/*-win32.png
|
||||
|
||||
dist.zip
|
||||
|
||||
/temp/
|
||||
/temp/
|
||||
|
||||
# Generated JSON Schemas
|
||||
/schemas/
|
||||
|
||||
12
.prettierrc
12
.prettierrc
@@ -6,5 +6,13 @@
|
||||
"printWidth": 80,
|
||||
"importOrder": ["^@core/(.*)$", "<THIRD_PARTY_MODULES>", "^@/(.*)$", "^[./]"],
|
||||
"importOrderSeparation": true,
|
||||
"importOrderSortSpecifiers": true
|
||||
}
|
||||
"importOrderSortSpecifiers": true,
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.{js,cjs,mjs,ts,cts,mts,tsx,vue}",
|
||||
"options": {
|
||||
"plugins": ["@trivago/prettier-plugin-sort-imports"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
59
README.md
59
README.md
@@ -58,7 +58,7 @@ Stable releases are published bi-weekly in the ComfyUI main repository.
|
||||
|
||||
### Major features
|
||||
|
||||
<details>
|
||||
<details id='feature-native-translation'>
|
||||
<summary>v1.5: Native translation (i18n)</summary>
|
||||
|
||||
ComfyUI now includes built-in translation support, replacing the need for third-party translation extensions. Select your language
|
||||
@@ -68,7 +68,7 @@ Stable releases are published bi-weekly in the ComfyUI main repository.
|
||||
More details available here: https://blog.comfy.org/p/native-localization-support-i18n
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<details id='feature-mask-editor'>
|
||||
<summary>v1.4: New mask editor</summary>
|
||||
|
||||
https://github.com/Comfy-Org/ComfyUI_frontend/pull/1284 implements a new mask editor.
|
||||
@@ -76,7 +76,7 @@ Stable releases are published bi-weekly in the ComfyUI main repository.
|
||||

|
||||
</details>
|
||||
|
||||
<details>
|
||||
<details id='feature-integrated-server-terminal'>
|
||||
<summary>v1.3.22: Integrated server terminal</summary>
|
||||
|
||||
Press Ctrl + ` to toggle integrated terminal.
|
||||
@@ -84,7 +84,7 @@ Press Ctrl + ` to toggle integrated terminal.
|
||||
https://github.com/user-attachments/assets/eddedc6a-07a3-4a83-9475-63b3977f6d94
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<details id='feature-keybinding-customization'>
|
||||
<summary>v1.3.7: Keybinding customization</summary>
|
||||
|
||||
## Basic UI
|
||||
@@ -101,7 +101,7 @@ https://github.com/user-attachments/assets/eddedc6a-07a3-4a83-9475-63b3977f6d94
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<details id='feature-node-library-sidebar'>
|
||||
<summary>v1.2.4: Node library sidebar tab</summary>
|
||||
|
||||
#### Drag & Drop
|
||||
@@ -111,13 +111,13 @@ https://github.com/user-attachments/assets/853e20b7-bc0e-49c9-bbce-a2ba7566f92f
|
||||
https://github.com/user-attachments/assets/4bbca3ee-318f-4cf0-be32-a5a5541066cf
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<details id='feature-queue-sidebar'>
|
||||
<summary>v1.2.0: Queue/History sidebar tab</summary>
|
||||
|
||||
https://github.com/user-attachments/assets/86e264fe-4d26-4f07-aa9a-83bdd2d02b8f
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<details id='feature-node-search'>
|
||||
<summary>v1.1.0: Node search box</summary>
|
||||
|
||||
#### Fuzzy search & Node preview
|
||||
@@ -129,26 +129,26 @@ https://github.com/user-attachments/assets/4bbca3ee-318f-4cf0-be32-a5a5541066cf
|
||||
|
||||
### QoL changes
|
||||
|
||||
<details>
|
||||
<details id='feature-nested-group'>
|
||||
<summary>v1.3.32: **Litegraph** Nested group</summary>
|
||||
|
||||
https://github.com/user-attachments/assets/f51adeb1-028e-40af-81e4-0ac13075198a
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<details id='feature-group-selection'>
|
||||
<summary>v1.3.24: **Litegraph** Group selection</summary>
|
||||
|
||||
https://github.com/user-attachments/assets/e6230a94-411e-4fba-90cb-6c694200adaa
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<details id='feature-toggle-link-visibility'>
|
||||
<summary>v1.3.6: **Litegraph** Toggle link visibility</summary>
|
||||
|
||||
[rec.webm](https://github.com/user-attachments/assets/34e460ac-fbbc-44ef-bfbb-99a84c2ae2be)
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<details id='feature-auto-widget-conversion'>
|
||||
<summary>v1.3.4: **Litegraph** Auto widget to input conversion</summary>
|
||||
|
||||
Dropping a link of correct type on node widget will automatically convert the widget to input.
|
||||
@@ -157,7 +157,7 @@ Dropping a link of correct type on node widget will automatically convert the wi
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<details id='feature-pan-mode'>
|
||||
<summary>v1.3.4: **Litegraph** Canvas pan mode</summary>
|
||||
|
||||
The canvas becomes readonly in pan mode. Pan mode is activated by clicking the pan mode button on the canvas menu
|
||||
@@ -167,42 +167,42 @@ or by holding the space key.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<details id='feature-shift-drag-link-creation'>
|
||||
<summary>v1.3.1: **Litegraph** Shift drag link to create a new link</summary>
|
||||
|
||||
[rec.webm](https://github.com/user-attachments/assets/7e73aaf9-79e2-4c3c-a26a-911cba3b85e4)
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<details id='feature-optional-input-donuts'>
|
||||
<summary>v1.2.62: **Litegraph** Show optional input slots as donuts</summary>
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<details id='feature-group-title-edit'>
|
||||
<summary>v1.2.44: **Litegraph** Double click group title to edit</summary>
|
||||
|
||||
https://github.com/user-attachments/assets/5bf0e2b6-8b3a-40a7-b44f-f0879e9ad26f
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<details id='feature-group-selection-shortcut'>
|
||||
<summary>v1.2.39: **Litegraph** Group selected nodes with Ctrl + G</summary>
|
||||
|
||||
https://github.com/user-attachments/assets/7805dc54-0854-4a28-8bcd-4b007fa01151
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<details id='feature-node-title-edit'>
|
||||
<summary>v1.2.38: **Litegraph** Double click node title to edit</summary>
|
||||
|
||||
https://github.com/user-attachments/assets/d61d5d0e-f200-4153-b293-3e3f6a212b30
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<details id='feature-drag-multi-link'>
|
||||
<summary>v1.2.7: **Litegraph** drags multiple links with shift pressed</summary>
|
||||
|
||||
https://github.com/user-attachments/assets/68826715-bb55-4b2a-be6e-675cfc424afe
|
||||
@@ -211,7 +211,7 @@ https://github.com/user-attachments/assets/c142c43f-2fe9-4030-8196-b3bfd4c6977d
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<details id='feature-auto-connect-link'>
|
||||
<summary>v1.2.2: **Litegraph** auto connects to correct slot</summary>
|
||||
|
||||
#### Before
|
||||
@@ -221,7 +221,7 @@ https://github.com/user-attachments/assets/c142c43f-2fe9-4030-8196-b3bfd4c6977d
|
||||
https://github.com/user-attachments/assets/b6360ac0-f0d2-447c-9daa-8a2e20c0dc1d
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<details id='feature-hide-text-overflow'>
|
||||
<summary>v1.1.8: **Litegraph** hides text overflow on widget value</summary>
|
||||
|
||||
https://github.com/user-attachments/assets/5696a89d-4a47-4fcc-9e8c-71e1264943f2
|
||||
@@ -298,7 +298,7 @@ app.registerExtension({
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<details id='extension-api-bottom-panel-tabs'>
|
||||
<summary>v1.3.22: Register bottom panel tabs</summary>
|
||||
|
||||
```js
|
||||
@@ -321,7 +321,7 @@ app.registerExtension({
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<details id='extension-api-settings'>
|
||||
<summary>v1.3.22: New settings API</summary>
|
||||
|
||||
Legacy settings API.
|
||||
@@ -367,7 +367,7 @@ app.extensionManager.setting.set('TestSetting', 'Hello, universe!')
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<details id='extension-api-commands-keybindings'>
|
||||
<summary>v1.3.7: Register commands and keybindings</summary>
|
||||
|
||||
Extensions can call the following API to register commands and keybindings. Do
|
||||
@@ -396,7 +396,7 @@ app.extensionManager.setting.set('TestSetting', 'Hello, universe!')
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<details id='extension-api-topbar-menu'>
|
||||
<summary>v1.3.1: Extension API to register custom topbar menu items</summary>
|
||||
|
||||
Extensions can call the following API to register custom topbar menu items.
|
||||
@@ -425,7 +425,7 @@ app.extensionManager.setting.set('TestSetting', 'Hello, universe!')
|
||||

|
||||
</details>
|
||||
|
||||
<details>
|
||||
<details id='extension-api-toast'>
|
||||
<summary>v1.2.27: Extension API to add toast message</summary>i
|
||||
|
||||
Extensions can call the following API to add toast messages.
|
||||
@@ -443,7 +443,7 @@ Documentation of all supported options can be found here: <https://primevue.org/
|
||||

|
||||
</details>
|
||||
|
||||
<details>
|
||||
<details id='extension-api-sidebar-tab'>
|
||||
<summary>v1.2.4: Extension API to register custom sidebar tab</summary>
|
||||
|
||||
Extensions now can call the following API to register a sidebar tab.
|
||||
@@ -626,10 +626,3 @@ You can switch languages by opening the ComfyUI Settings and selecting from the
|
||||
|
||||
- Option 1: Set `DEPLOY_COMFYUI_DIR` in `.env` and run `npm run deploy`.
|
||||
- Option 2: Copy everything under `dist/` to `ComfyUI/web/` in your ComfyUI checkout manually.
|
||||
|
||||
## Publish release to ComfyUI main repo
|
||||
|
||||
Run following command to publish a release to ComfyUI main repo. The script will create a new branch and do a commit to `web/` folder by checkout `dist.zip`
|
||||
from GitHub release.
|
||||
|
||||
- `python scripts/main_repo_release.py <path_to_comfyui_main_repo> <version>`
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import type { Palette } from '../src/types/colorPaletteTypes'
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
const customColorPalettes = {
|
||||
const customColorPalettes: Record<string, Palette> = {
|
||||
obsidian: {
|
||||
version: 102,
|
||||
id: 'obsidian',
|
||||
@@ -128,6 +129,19 @@ const customColorPalettes = {
|
||||
'tr-odd-bg-color': 'rgba(19,19,19,.9)'
|
||||
}
|
||||
}
|
||||
},
|
||||
// A custom light theme with fg color red
|
||||
light_red: {
|
||||
id: 'light_red',
|
||||
name: 'Light Red',
|
||||
light_theme: true,
|
||||
colors: {
|
||||
node_slot: {},
|
||||
litegraph_base: {},
|
||||
comfy_base: {
|
||||
'fg-color': '#ff0000'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,12 +150,18 @@ test.describe('Color Palette', () => {
|
||||
await comfyPage.setSetting('Comfy.CustomColorPalettes', customColorPalettes)
|
||||
// Reload to apply the new setting. Setting Comfy.CustomColorPalettes directly
|
||||
// doesn't update the store immediately.
|
||||
await comfyPage.reload()
|
||||
await comfyPage.setup()
|
||||
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'obsidian_dark')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'custom-color-palette-obsidian-dark.png'
|
||||
)
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'light_red')
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'custom-color-palette-light-red.png'
|
||||
)
|
||||
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'dark')
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('default-color-palette.png')
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 106 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 102 KiB |
@@ -11,6 +11,14 @@ test.describe('Copy Paste', () => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('copied-node.png')
|
||||
})
|
||||
|
||||
test('Can copy and paste node with link', async ({ comfyPage }) => {
|
||||
await comfyPage.clickTextEncodeNode1()
|
||||
await comfyPage.page.mouse.move(10, 10)
|
||||
await comfyPage.ctrlC()
|
||||
await comfyPage.page.keyboard.press('Control+Shift+V')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('copied-node-with-link.png')
|
||||
})
|
||||
|
||||
test('Can copy and paste text', async ({ comfyPage }) => {
|
||||
const textBox = comfyPage.widgetTextBox
|
||||
await textBox.click()
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 118 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 114 KiB |
@@ -1,5 +1,6 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { Keybinding } from '../src/types/keyBindingTypes'
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
test.describe('Load workflow warning', () => {
|
||||
@@ -43,6 +44,18 @@ test.describe('Execution error', () => {
|
||||
const executionError = comfyPage.page.locator('.comfy-error-report')
|
||||
await expect(executionError).toBeVisible()
|
||||
})
|
||||
|
||||
test('Can display Issue Report form', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('execution_error')
|
||||
await comfyPage.queueButton.click()
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
await comfyPage.page.getByLabel('Help Fix This').click()
|
||||
const issueReportForm = comfyPage.page.getByText(
|
||||
'Submit Error Report (Optional)'
|
||||
)
|
||||
await expect(issueReportForm).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Missing models warning', () => {
|
||||
@@ -103,4 +116,52 @@ test.describe('Settings', () => {
|
||||
expect(await comfyPage.getSetting('Comfy.Graph.ZoomSpeed')).toBe(maxSpeed)
|
||||
})
|
||||
})
|
||||
|
||||
test('Should persist keybinding setting', async ({ comfyPage }) => {
|
||||
// Open the settings dialog
|
||||
await comfyPage.page.keyboard.press('Control+,')
|
||||
await comfyPage.page.waitForSelector('.settings-container')
|
||||
|
||||
// Open the keybinding tab
|
||||
await comfyPage.page.getByLabel('Keybinding').click()
|
||||
await comfyPage.page.waitForSelector(
|
||||
'[placeholder="Search Keybindings..."]'
|
||||
)
|
||||
|
||||
// Focus the 'New Blank Workflow' row
|
||||
const newBlankWorkflowRow = comfyPage.page.locator('tr', {
|
||||
has: comfyPage.page.getByRole('cell', { name: 'New Blank Workflow' })
|
||||
})
|
||||
await newBlankWorkflowRow.click()
|
||||
|
||||
// Click edit button
|
||||
const editKeybindingButton = newBlankWorkflowRow.locator('.pi-pencil')
|
||||
await editKeybindingButton.click()
|
||||
|
||||
// Set new keybinding
|
||||
const input = comfyPage.page.getByPlaceholder('Press keys for new binding')
|
||||
await input.press('Alt+n')
|
||||
|
||||
const requestPromise = comfyPage.page.waitForRequest(
|
||||
'**/api/settings/Comfy.Keybinding.NewBindings'
|
||||
)
|
||||
|
||||
// Save keybinding
|
||||
const saveButton = comfyPage.page
|
||||
.getByLabel('Comfy.NewBlankWorkflow')
|
||||
.getByLabel('Save')
|
||||
await saveButton.click()
|
||||
|
||||
const request = await requestPromise
|
||||
const expectedSetting: Keybinding = {
|
||||
commandId: 'Comfy.NewBlankWorkflow',
|
||||
combo: {
|
||||
key: 'n',
|
||||
ctrl: false,
|
||||
alt: true,
|
||||
shift: false
|
||||
}
|
||||
}
|
||||
expect(request.postData()).toContain(JSON.stringify(expectedSetting))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -27,6 +27,11 @@ dotenv.config()
|
||||
type WorkspaceStore = ReturnType<typeof useWorkspaceStore>
|
||||
|
||||
class ComfyMenu {
|
||||
private _nodeLibraryTab: NodeLibrarySidebarTab | null = null
|
||||
private _workflowsTab: WorkflowsSidebarTab | null = null
|
||||
private _queueTab: QueueSidebarTab | null = null
|
||||
private _topbar: Topbar | null = null
|
||||
|
||||
public readonly sideToolbar: Locator
|
||||
public readonly themeToggleButton: Locator
|
||||
public readonly saveButton: Locator
|
||||
@@ -40,19 +45,23 @@ class ComfyMenu {
|
||||
}
|
||||
|
||||
get nodeLibraryTab() {
|
||||
return new NodeLibrarySidebarTab(this.page)
|
||||
this._nodeLibraryTab ??= new NodeLibrarySidebarTab(this.page)
|
||||
return this._nodeLibraryTab
|
||||
}
|
||||
|
||||
get workflowsTab() {
|
||||
return new WorkflowsSidebarTab(this.page)
|
||||
this._workflowsTab ??= new WorkflowsSidebarTab(this.page)
|
||||
return this._workflowsTab
|
||||
}
|
||||
|
||||
get queueTab() {
|
||||
return new QueueSidebarTab(this.page)
|
||||
this._queueTab ??= new QueueSidebarTab(this.page)
|
||||
return this._queueTab
|
||||
}
|
||||
|
||||
get topbar() {
|
||||
return new Topbar(this.page)
|
||||
this._topbar ??= new Topbar(this.page)
|
||||
return this._topbar
|
||||
}
|
||||
|
||||
async toggleTheme() {
|
||||
@@ -360,11 +369,6 @@ export class ComfyPage {
|
||||
}, settingId)
|
||||
}
|
||||
|
||||
async reload({ clearStorage = true }: { clearStorage?: boolean } = {}) {
|
||||
await this.page.reload({ timeout: 15000 })
|
||||
await this.setup({ clearStorage })
|
||||
}
|
||||
|
||||
async goto() {
|
||||
await this.page.goto(this.url)
|
||||
}
|
||||
@@ -821,6 +825,11 @@ export class ComfyPage {
|
||||
async getNodeRefById(id: NodeId) {
|
||||
return new NodeReference(id, this)
|
||||
}
|
||||
async getNodes() {
|
||||
return await this.page.evaluate(() => {
|
||||
return window['app'].graph.nodes
|
||||
})
|
||||
}
|
||||
async getNodeRefsByType(type: string): Promise<NodeReference[]> {
|
||||
return Promise.all(
|
||||
(
|
||||
|
||||
@@ -3,6 +3,13 @@ import { Locator, Page } from '@playwright/test'
|
||||
export class ComfyNodeSearchFilterSelectionPanel {
|
||||
constructor(public readonly page: Page) {}
|
||||
|
||||
get header() {
|
||||
return this.page
|
||||
.getByRole('dialog')
|
||||
.locator('div')
|
||||
.filter({ hasText: 'Add node filter condition' })
|
||||
}
|
||||
|
||||
async selectFilterType(filterType: string) {
|
||||
await this.page
|
||||
.locator(
|
||||
|
||||
@@ -152,6 +152,13 @@ export class WorkflowsSidebarTab extends SidebarTab {
|
||||
await this.page.keyboard.press('Enter')
|
||||
await this.page.waitForTimeout(300)
|
||||
}
|
||||
|
||||
async insertWorkflow(locator: Locator) {
|
||||
await locator.click({ button: 'right' })
|
||||
await this.page
|
||||
.locator('.p-contextmenu-item-content', { hasText: 'Insert' })
|
||||
.click()
|
||||
}
|
||||
}
|
||||
|
||||
export class QueueSidebarTab extends SidebarTab {
|
||||
|
||||
@@ -9,6 +9,12 @@ export class Topbar {
|
||||
.allInnerTexts()
|
||||
}
|
||||
|
||||
async getActiveTabName(): Promise<string> {
|
||||
return this.page
|
||||
.locator('.workflow-tabs .p-togglebutton-checked')
|
||||
.innerText()
|
||||
}
|
||||
|
||||
async openSubmenuMobile() {
|
||||
await this.page.locator('.p-menubar-mobile .p-menubar-button').click()
|
||||
}
|
||||
|
||||
@@ -469,6 +469,43 @@ test.describe('Canvas Interaction', () => {
|
||||
expect(await getCursorStyle()).toBe('default')
|
||||
})
|
||||
|
||||
// https://github.com/Comfy-Org/litegraph.js/pull/424
|
||||
test('Properly resets dragging state after pan mode sequence', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const getCursorStyle = async () => {
|
||||
return await comfyPage.page.evaluate(() => {
|
||||
return (
|
||||
document.getElementById('graph-canvas')!.style.cursor || 'default'
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// Initial state check
|
||||
await comfyPage.page.mouse.move(10, 10)
|
||||
expect(await getCursorStyle()).toBe('default')
|
||||
|
||||
// Click and hold
|
||||
await comfyPage.page.mouse.down()
|
||||
expect(await getCursorStyle()).toBe('grabbing')
|
||||
|
||||
// Press space while holding click
|
||||
await comfyPage.page.keyboard.down('Space')
|
||||
expect(await getCursorStyle()).toBe('grabbing')
|
||||
|
||||
// Release click while space is still down
|
||||
await comfyPage.page.mouse.up()
|
||||
expect(await getCursorStyle()).toBe('grab')
|
||||
|
||||
// Release space
|
||||
await comfyPage.page.keyboard.up('Space')
|
||||
expect(await getCursorStyle()).toBe('default')
|
||||
|
||||
// Move mouse - cursor should remain default
|
||||
await comfyPage.page.mouse.move(20, 20)
|
||||
expect(await getCursorStyle()).toBe('default')
|
||||
})
|
||||
|
||||
test('Can pan when dragging a link', async ({ comfyPage }) => {
|
||||
const posSlot1 = comfyPage.clipTextEncodeNode1InputSlot
|
||||
await comfyPage.page.mouse.move(posSlot1.x, posSlot1.y)
|
||||
@@ -556,7 +593,7 @@ test.describe('Load workflow', () => {
|
||||
}) => {
|
||||
await comfyPage.loadWorkflow('single_ksampler')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('single_ksampler.png')
|
||||
await comfyPage.reload({ clearStorage: false })
|
||||
await comfyPage.setup({ clearStorage: false })
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('single_ksampler.png')
|
||||
})
|
||||
|
||||
@@ -573,11 +610,72 @@ test.describe('Load workflow', () => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'single_ksampler_modified.png'
|
||||
)
|
||||
await comfyPage.reload({ clearStorage: false })
|
||||
await comfyPage.setup({ clearStorage: false })
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'single_ksampler_modified.png'
|
||||
)
|
||||
})
|
||||
|
||||
test.describe('Restore all open workflows on reload', () => {
|
||||
let workflowA: string
|
||||
let workflowB: string
|
||||
|
||||
const generateUniqueFilename = (extension = '') =>
|
||||
`${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}${extension}`
|
||||
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
|
||||
workflowA = generateUniqueFilename()
|
||||
await comfyPage.menu.topbar.saveWorkflow(workflowA)
|
||||
workflowB = generateUniqueFilename()
|
||||
await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New'])
|
||||
await comfyPage.menu.topbar.saveWorkflow(workflowB)
|
||||
|
||||
// Wait for localStorage to persist the workflow paths before reloading
|
||||
await comfyPage.page.waitForFunction(
|
||||
() => !!window.localStorage.getItem('Comfy.OpenWorkflowsPaths')
|
||||
)
|
||||
await comfyPage.setup({ clearStorage: false })
|
||||
})
|
||||
|
||||
test('Restores topbar workflow tabs after reload', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setSetting(
|
||||
'Comfy.Workflow.WorkflowTabsPosition',
|
||||
'Topbar'
|
||||
)
|
||||
const tabs = await comfyPage.menu.topbar.getTabNames()
|
||||
const activeWorkflowName = await comfyPage.menu.topbar.getActiveTabName()
|
||||
|
||||
expect(tabs).toEqual(expect.arrayContaining([workflowA, workflowB]))
|
||||
expect(tabs.indexOf(workflowA)).toBeLessThan(tabs.indexOf(workflowB))
|
||||
expect(activeWorkflowName).toEqual(workflowB)
|
||||
})
|
||||
|
||||
test('Restores sidebar workflows after reload', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting(
|
||||
'Comfy.Workflow.WorkflowTabsPosition',
|
||||
'Sidebar'
|
||||
)
|
||||
await comfyPage.menu.workflowsTab.open()
|
||||
const openWorkflows =
|
||||
await comfyPage.menu.workflowsTab.getOpenedWorkflowNames()
|
||||
const activeWorkflowName =
|
||||
await comfyPage.menu.workflowsTab.getActiveWorkflowName()
|
||||
const workflowPathA = `${workflowA}.json`
|
||||
const workflowPathB = `${workflowB}.json`
|
||||
|
||||
expect(openWorkflows).toEqual(
|
||||
expect.arrayContaining([workflowPathA, workflowPathB])
|
||||
)
|
||||
expect(openWorkflows.indexOf(workflowPathA)).toBeLessThan(
|
||||
openWorkflows.indexOf(workflowPathB)
|
||||
)
|
||||
expect(activeWorkflowName).toEqual(workflowPathB)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Load duplicate workflow', () => {
|
||||
|
||||
@@ -21,7 +21,7 @@ test.describe('Menu', () => {
|
||||
expect(await comfyPage.menu.getThemeId()).toBe('light')
|
||||
|
||||
// Theme id should persist after reload.
|
||||
await comfyPage.reload()
|
||||
await comfyPage.setup()
|
||||
expect(await comfyPage.menu.getThemeId()).toBe('light')
|
||||
|
||||
await comfyPage.menu.toggleTheme()
|
||||
@@ -292,6 +292,47 @@ test.describe('Menu', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('Can customize bookmark color after interacting with color options', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Open customization dialog
|
||||
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks.V2', ['foo/'])
|
||||
const tab = comfyPage.menu.nodeLibraryTab
|
||||
await tab.getFolder('foo').click({ button: 'right' })
|
||||
await comfyPage.page.getByLabel('Customize').click()
|
||||
|
||||
// Click a color option multiple times
|
||||
const customColorOption = comfyPage.page.locator(
|
||||
'.p-togglebutton-content > .pi-palette'
|
||||
)
|
||||
await customColorOption.click()
|
||||
await customColorOption.click()
|
||||
|
||||
// Use the color picker
|
||||
await comfyPage.page
|
||||
.getByLabel('Customize Folder')
|
||||
.getByRole('textbox')
|
||||
.click()
|
||||
await comfyPage.page.locator('.p-colorpicker-color-background').click()
|
||||
|
||||
// Finalize the customization
|
||||
await comfyPage.page
|
||||
.locator('.icon-field .p-selectbutton > *:nth-child(2)')
|
||||
.click()
|
||||
await comfyPage.page.getByLabel('Confirm').click()
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
// Verify the color selection is saved
|
||||
const setting = await comfyPage.getSetting(
|
||||
'Comfy.NodeLibrary.BookmarksCustomization'
|
||||
)
|
||||
await expect(setting).toHaveProperty(['foo/', 'color'])
|
||||
await expect(setting['foo/'].color).not.toBeNull()
|
||||
await expect(setting['foo/'].color).not.toBeUndefined()
|
||||
await expect(setting['foo/'].color).not.toBe('')
|
||||
})
|
||||
|
||||
test('Can rename customized bookmark folder', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks.V2', ['foo/'])
|
||||
await comfyPage.setSetting('Comfy.NodeLibrary.BookmarksCustomization', {
|
||||
@@ -429,6 +470,26 @@ test.describe('Menu', () => {
|
||||
])
|
||||
})
|
||||
|
||||
test('Can open workflow after insert', async ({ comfyPage }) => {
|
||||
await comfyPage.setupWorkflowsDirectory({
|
||||
'workflow1.json': 'single_ksampler.json'
|
||||
})
|
||||
await comfyPage.setup()
|
||||
|
||||
const tab = comfyPage.menu.workflowsTab
|
||||
await tab.open()
|
||||
await comfyPage.executeCommand('Comfy.LoadDefaultWorkflow')
|
||||
const originalNodeCount = (await comfyPage.getNodes()).length
|
||||
|
||||
await tab.insertWorkflow(tab.getPersistedItem('workflow1.json'))
|
||||
await comfyPage.nextFrame()
|
||||
expect((await comfyPage.getNodes()).length).toEqual(originalNodeCount + 1)
|
||||
|
||||
await tab.getPersistedItem('workflow1.json').click()
|
||||
await comfyPage.nextFrame()
|
||||
expect((await comfyPage.getNodes()).length).toEqual(1)
|
||||
})
|
||||
|
||||
test('Can rename nested workflow from opened workflow item', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
@@ -508,7 +569,7 @@ test.describe('Menu', () => {
|
||||
})
|
||||
|
||||
await comfyPage.setSetting('Comfy.Locale', 'zh')
|
||||
await comfyPage.reload()
|
||||
await comfyPage.setup()
|
||||
|
||||
const downloadedContentZh = await comfyPage.getExportedWorkflow({
|
||||
api: false
|
||||
@@ -604,6 +665,15 @@ test.describe('Menu', () => {
|
||||
).toEqual(['*Unsaved Workflow.json'])
|
||||
})
|
||||
|
||||
test('Can close saved workflow with command', async ({ comfyPage }) => {
|
||||
const tab = comfyPage.menu.workflowsTab
|
||||
await comfyPage.menu.topbar.saveWorkflow('workflow1.json')
|
||||
await comfyPage.executeCommand('Workspace.CloseWorkflow')
|
||||
expect(await tab.getOpenedWorkflowNames()).toEqual([
|
||||
'*Unsaved Workflow.json'
|
||||
])
|
||||
})
|
||||
|
||||
test('Can delete workflows (confirm disabled)', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.Workflow.ConfirmDelete', false)
|
||||
|
||||
@@ -738,7 +808,7 @@ test.describe('Menu', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Queue sidebar', () => {
|
||||
test.describe.skip('Queue sidebar', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
|
||||
@@ -132,6 +132,48 @@ test.describe('Node search box', () => {
|
||||
await expectFilterChips(comfyPage, ['MODEL'])
|
||||
})
|
||||
|
||||
// Flaky test.
|
||||
// Sample test failure:
|
||||
// https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/12696912248/job/35391990861?pr=2210
|
||||
/*
|
||||
1) [chromium-2x] › nodeSearchBox.spec.ts:135:5 › Node search box › Filtering › Outer click dismisses filter panel but keeps search box visible
|
||||
|
||||
Error: expect(locator).not.toBeVisible()
|
||||
|
||||
Locator: getByRole('dialog').locator('div').filter({ hasText: 'Add node filter condition' })
|
||||
Expected: not visible
|
||||
Received: visible
|
||||
Call log:
|
||||
- expect.not.toBeVisible with timeout 5000ms
|
||||
- waiting for getByRole('dialog').locator('div').filter({ hasText: 'Add node filter condition' })
|
||||
|
||||
|
||||
143 |
|
||||
144 | // Verify the filter selection panel is hidden
|
||||
> 145 | expect(panel.header).not.toBeVisible()
|
||||
| ^
|
||||
146 |
|
||||
147 | // Verify the node search dialog is still visible
|
||||
148 | expect(comfyPage.searchBox.input).toBeVisible()
|
||||
|
||||
at /home/runner/work/ComfyUI_frontend/ComfyUI_frontend/ComfyUI_frontend/browser_tests/nodeSearchBox.spec.ts:145:32
|
||||
*/
|
||||
test.skip('Outer click dismisses filter panel but keeps search box visible', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.searchBox.filterButton.click()
|
||||
const panel = comfyPage.searchBox.filterSelectionPanel
|
||||
await panel.header.waitFor({ state: 'visible' })
|
||||
const panelBounds = await panel.header.boundingBox()
|
||||
await comfyPage.page.mouse.click(panelBounds!.x - 10, panelBounds!.y - 10)
|
||||
|
||||
// Verify the filter selection panel is hidden
|
||||
expect(panel.header).not.toBeVisible()
|
||||
|
||||
// Verify the node search dialog is still visible
|
||||
expect(comfyPage.searchBox.input).toBeVisible()
|
||||
})
|
||||
|
||||
test('Can add multiple filters', async ({ comfyPage }) => {
|
||||
await comfyPage.searchBox.addFilter('MODEL', 'Input Type')
|
||||
await comfyPage.searchBox.addFilter('CLIP', 'Output Type')
|
||||
|
||||
2
global.d.ts
vendored
2
global.d.ts
vendored
@@ -1 +1,3 @@
|
||||
declare const __COMFYUI_FRONTEND_VERSION__: string
|
||||
declare const __SENTRY_ENABLED__: boolean
|
||||
declare const __SENTRY_DSN__: string
|
||||
|
||||
129
package-lock.json
generated
129
package-lock.json
generated
@@ -1,18 +1,19 @@
|
||||
{
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"version": "1.6.14",
|
||||
"version": "1.7.10",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"version": "1.6.14",
|
||||
"version": "1.7.10",
|
||||
"license": "GPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
|
||||
"@comfyorg/comfyui-electron-types": "^0.4.3",
|
||||
"@comfyorg/litegraph": "^0.8.58",
|
||||
"@comfyorg/comfyui-electron-types": "^0.4.7",
|
||||
"@comfyorg/litegraph": "^0.8.60",
|
||||
"@primevue/themes": "^4.0.5",
|
||||
"@sentry/vue": "^8.48.0",
|
||||
"@tiptap/core": "^2.10.4",
|
||||
"@tiptap/extension-link": "^2.10.4",
|
||||
"@tiptap/extension-table": "^2.10.4",
|
||||
@@ -87,7 +88,8 @@
|
||||
"vite-plugin-static-copy": "^1.0.5",
|
||||
"vitest": "^2.0.5",
|
||||
"vue-tsc": "^2.1.10",
|
||||
"zip-dir": "^2.0.0"
|
||||
"zip-dir": "^2.0.0",
|
||||
"zod-to-json-schema": "^3.24.1"
|
||||
}
|
||||
},
|
||||
"../litegraph.js": {
|
||||
@@ -1934,15 +1936,15 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@comfyorg/comfyui-electron-types": {
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@comfyorg/comfyui-electron-types/-/comfyui-electron-types-0.4.3.tgz",
|
||||
"integrity": "sha512-hSM3mchpsYN0e7oZ7XLWjEvFDvE1rgzaB9YkCeqIiZYZgLL78T79ssM0n5ra17Zv7Mqwl6ErZblXvbQE/36RPw==",
|
||||
"version": "0.4.7",
|
||||
"resolved": "https://registry.npmjs.org/@comfyorg/comfyui-electron-types/-/comfyui-electron-types-0.4.7.tgz",
|
||||
"integrity": "sha512-APC3C4VZOo9W6h0xiAGxnsU9iNp3T8rN9w/5KmOCI0GUoKtKg5U2OaicTmnMwcDSQe5Jxflmej53GyJ1nH9oRw==",
|
||||
"license": "GPL-3.0-only"
|
||||
},
|
||||
"node_modules/@comfyorg/litegraph": {
|
||||
"version": "0.8.58",
|
||||
"resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.8.58.tgz",
|
||||
"integrity": "sha512-V/4yC8i5QOpDV20ZiEMiZP6KnmYD5d15El3V4tmH/MkhjOxjc6owAFMyAVgpxphYdcBF2qj1QTNTrZLgC6x2VQ==",
|
||||
"version": "0.8.60",
|
||||
"resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.8.60.tgz",
|
||||
"integrity": "sha512-LkZalBcka1xVxkL7JnkF/1EzyvspLyrSthzyN9ZumWJw7kAaZkO9omraXv2t/UiFsqwMr5M/AV5UY915Vq8cxQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@cspotcode/source-map-support": {
|
||||
@@ -4461,6 +4463,96 @@
|
||||
"string-argv": "~0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry-internal/browser-utils": {
|
||||
"version": "8.48.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.48.0.tgz",
|
||||
"integrity": "sha512-pLtu0Fa1Ou0v3M1OEO1MB1EONJVmXEGtoTwFRCO1RPQI2ulmkG6BikINClFG5IBpoYKZ33WkEXuM6U5xh+pdZg==",
|
||||
"dependencies": {
|
||||
"@sentry/core": "8.48.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry-internal/feedback": {
|
||||
"version": "8.48.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.48.0.tgz",
|
||||
"integrity": "sha512-6PwcJNHVPg0EfZxmN+XxVOClfQpv7MBAweV8t9i5l7VFr8sM/7wPNSeU/cG7iK19Ug9ZEkBpzMOe3G4GXJ5bpw==",
|
||||
"dependencies": {
|
||||
"@sentry/core": "8.48.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry-internal/replay": {
|
||||
"version": "8.48.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.48.0.tgz",
|
||||
"integrity": "sha512-csILVupc5RkrsTrncuUTGmlB56FQSFjXPYWG8I8yBTGlXEJ+o8oTuF6+55R4vbw3EIzBveXWi4kEBbnQlXW/eg==",
|
||||
"dependencies": {
|
||||
"@sentry-internal/browser-utils": "8.48.0",
|
||||
"@sentry/core": "8.48.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry-internal/replay-canvas": {
|
||||
"version": "8.48.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.48.0.tgz",
|
||||
"integrity": "sha512-LdivLfBXXB9us1aAc6XaL7/L2Ob4vi3C/fEOXElehg3qHjX6q6pewiv5wBvVXGX1NfZTRvu+X11k6TZoxKsezw==",
|
||||
"dependencies": {
|
||||
"@sentry-internal/replay": "8.48.0",
|
||||
"@sentry/core": "8.48.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/browser": {
|
||||
"version": "8.48.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.48.0.tgz",
|
||||
"integrity": "sha512-fuuVULB5/1vI8NoIwXwR3xwhJJqk+y4RdSdajExGF7nnUDBpwUJyXsmYJnOkBO+oLeEs58xaCpotCKiPUNnE3g==",
|
||||
"dependencies": {
|
||||
"@sentry-internal/browser-utils": "8.48.0",
|
||||
"@sentry-internal/feedback": "8.48.0",
|
||||
"@sentry-internal/replay": "8.48.0",
|
||||
"@sentry-internal/replay-canvas": "8.48.0",
|
||||
"@sentry/core": "8.48.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/core": {
|
||||
"version": "8.48.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.48.0.tgz",
|
||||
"integrity": "sha512-VGwYgTfLpvJ5LRO5A+qWo1gpo6SfqaGXL9TOzVgBucAdpzbrYHpZ87sEarDVq/4275uk1b0S293/mfsskFczyw==",
|
||||
"engines": {
|
||||
"node": ">=14.18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/vue": {
|
||||
"version": "8.48.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/vue/-/vue-8.48.0.tgz",
|
||||
"integrity": "sha512-hqm9X7hz1vMQQB1HBYezrDBQihZk6e/MxWIG1wMJoClcBnD1Sh7y+D36UwaQlR4Gr/Ftiz+Bb0DxuAYHoUS4ow==",
|
||||
"dependencies": {
|
||||
"@sentry/browser": "8.48.0",
|
||||
"@sentry/core": "8.48.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"pinia": "2.x",
|
||||
"vue": "2.x || 3.x"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"pinia": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@sinclair/typebox": {
|
||||
"version": "0.27.8",
|
||||
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
|
||||
@@ -19440,21 +19532,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "3.23.8",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
|
||||
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
|
||||
"version": "3.24.1",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz",
|
||||
"integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
},
|
||||
"node_modules/zod-to-json-schema": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.23.5.tgz",
|
||||
"integrity": "sha512-5wlSS0bXfF/BrL4jPAbz9da5hDlDptdEppYfe+x4eIJ7jioqKG9uUxOwPzqof09u/XeVdrgFu29lZi+8XNDJtA==",
|
||||
"version": "3.24.1",
|
||||
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.1.tgz",
|
||||
"integrity": "sha512-3h08nf3Vw3Wl3PK+q3ow/lIil81IT2Oa7YpQyUUDsEWbXveMesdfK1xBd2RhCkynwZndAxixji/7SYJJowr62w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"zod": "^3.23.3"
|
||||
"zod": "^3.24.1"
|
||||
}
|
||||
},
|
||||
"node_modules/zod-validation-error": {
|
||||
|
||||
17
package.json
17
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"private": true,
|
||||
"version": "1.6.14",
|
||||
"version": "1.7.10",
|
||||
"type": "module",
|
||||
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
|
||||
"homepage": "https://comfy.org",
|
||||
@@ -17,8 +17,8 @@
|
||||
"update-litegraph": "node scripts/update-litegraph.js",
|
||||
"zipdist": "node scripts/zipdist.js",
|
||||
"typecheck": "vue-tsc --noEmit && tsc --noEmit && tsc-strict",
|
||||
"format": "prettier --write './**/*.{js,ts,tsx,vue,mts}' --plugin @trivago/prettier-plugin-sort-imports",
|
||||
"format:check": "prettier --check './**/*.{js,ts,tsx,vue,mts}' --plugin @trivago/prettier-plugin-sort-imports",
|
||||
"format": "prettier --write './**/*.{js,ts,tsx,vue,mts}'",
|
||||
"format:check": "prettier --check './**/*.{js,ts,tsx,vue,mts}'",
|
||||
"test:jest": "jest --config jest.config.ts",
|
||||
"test:generate": "npx tsx tests-ui/setup",
|
||||
"test:browser": "npx playwright test",
|
||||
@@ -28,7 +28,8 @@
|
||||
"lint": "eslint src",
|
||||
"lint:fix": "eslint src --fix",
|
||||
"locale": "lobe-i18n locale",
|
||||
"collect-i18n": "playwright test --config=playwright.i18n.config.ts"
|
||||
"collect-i18n": "playwright test --config=playwright.i18n.config.ts",
|
||||
"json-schema": "tsx scripts/generate-json-schema.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.24.7",
|
||||
@@ -77,13 +78,15 @@
|
||||
"vite-plugin-static-copy": "^1.0.5",
|
||||
"vitest": "^2.0.5",
|
||||
"vue-tsc": "^2.1.10",
|
||||
"zip-dir": "^2.0.0"
|
||||
"zip-dir": "^2.0.0",
|
||||
"zod-to-json-schema": "^3.24.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
|
||||
"@comfyorg/comfyui-electron-types": "^0.4.3",
|
||||
"@comfyorg/litegraph": "^0.8.58",
|
||||
"@comfyorg/comfyui-electron-types": "^0.4.7",
|
||||
"@comfyorg/litegraph": "^0.8.60",
|
||||
"@primevue/themes": "^4.0.5",
|
||||
"@sentry/vue": "^8.48.0",
|
||||
"@tiptap/core": "^2.10.4",
|
||||
"@tiptap/extension-link": "^2.10.4",
|
||||
"@tiptap/extension-table": "^2.10.4",
|
||||
|
||||
@@ -8,7 +8,7 @@ const config: PlaywrightTestConfig = {
|
||||
},
|
||||
reporter: 'list',
|
||||
timeout: 10000,
|
||||
testMatch: /collect-i18n\.ts/
|
||||
testMatch: /collect-i18n-.*\.ts/
|
||||
}
|
||||
|
||||
export default config
|
||||
|
||||
@@ -3,14 +3,11 @@ import * as fs from 'fs'
|
||||
import { comfyPageFixture as test } from '../browser_tests/fixtures/ComfyPage'
|
||||
import { CORE_MENU_COMMANDS } from '../src/constants/coreMenuCommands'
|
||||
import { SERVER_CONFIG_ITEMS } from '../src/constants/serverConfig'
|
||||
import type { ComfyApi } from '../src/scripts/api'
|
||||
import type { ComfyCommandImpl } from '../src/stores/commandStore'
|
||||
import { ComfyNodeDefImpl } from '../src/stores/nodeDefStore'
|
||||
import type { FormItem, SettingParams } from '../src/types/settingTypes'
|
||||
import { formatCamelCase, normalizeI18nKey } from '../src/utils/formatUtil'
|
||||
|
||||
const localePath = './src/locales/en/main.json'
|
||||
const nodeDefsPath = './src/locales/en/nodeDefs.json'
|
||||
const commandsPath = './src/locales/en/commands.json'
|
||||
const settingsPath = './src/locales/en/settings.json'
|
||||
|
||||
@@ -22,7 +19,7 @@ const extractMenuCommandLocaleStrings = (): Set<string> => {
|
||||
return labels
|
||||
}
|
||||
|
||||
test('collect-i18n', async ({ comfyPage }) => {
|
||||
test('collect-i18n-general', async ({ comfyPage }) => {
|
||||
const commands = (
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const workspace = window['app'].extensionManager
|
||||
@@ -129,105 +126,6 @@ test('collect-i18n', async ({ comfyPage }) => {
|
||||
])
|
||||
)
|
||||
|
||||
// Node Definitions
|
||||
const nodeDefs: ComfyNodeDefImpl[] = Object.values(
|
||||
await comfyPage.page.evaluate(async () => {
|
||||
const api = window['app'].api as ComfyApi
|
||||
return await api.getNodeDefs()
|
||||
})
|
||||
).map((def) => new ComfyNodeDefImpl(def))
|
||||
|
||||
console.log(`Collected ${nodeDefs.length} node definitions`)
|
||||
|
||||
const allDataTypesLocale = Object.fromEntries(
|
||||
nodeDefs
|
||||
.flatMap((nodeDef) => {
|
||||
const inputDataTypes = Object.values(nodeDef.inputs.all).map(
|
||||
(inputSpec) => inputSpec.type
|
||||
)
|
||||
const outputDataTypes = nodeDef.outputs.all.map((output) => output.type)
|
||||
const allDataTypes = [...inputDataTypes, ...outputDataTypes].flatMap(
|
||||
(type: string) => type.split(',')
|
||||
)
|
||||
return allDataTypes.map((dataType) => [
|
||||
normalizeI18nKey(dataType),
|
||||
dataType
|
||||
])
|
||||
})
|
||||
.sort((a, b) => a[0].localeCompare(b[0]))
|
||||
)
|
||||
|
||||
function extractInputs(nodeDef: ComfyNodeDefImpl) {
|
||||
const inputs = Object.fromEntries(
|
||||
nodeDef.inputs.all.flatMap((input) => {
|
||||
const name = input.name
|
||||
const tooltip = input.tooltip
|
||||
|
||||
if (name === undefined && tooltip === undefined) {
|
||||
return []
|
||||
}
|
||||
|
||||
return [
|
||||
[
|
||||
normalizeI18nKey(input.name),
|
||||
{
|
||||
name,
|
||||
tooltip
|
||||
}
|
||||
]
|
||||
]
|
||||
})
|
||||
)
|
||||
return Object.keys(inputs).length > 0 ? inputs : undefined
|
||||
}
|
||||
|
||||
function extractOutputs(nodeDef: ComfyNodeDefImpl) {
|
||||
const outputs = Object.fromEntries(
|
||||
nodeDef.outputs.all.flatMap((output, i) => {
|
||||
// Ignore data types if they are already translated in allDataTypesLocale.
|
||||
const name = output.name in allDataTypesLocale ? undefined : output.name
|
||||
const tooltip = output.tooltip
|
||||
|
||||
if (name === undefined && tooltip === undefined) {
|
||||
return []
|
||||
}
|
||||
|
||||
return [
|
||||
[
|
||||
i.toString(),
|
||||
{
|
||||
name,
|
||||
tooltip
|
||||
}
|
||||
]
|
||||
]
|
||||
})
|
||||
)
|
||||
return Object.keys(outputs).length > 0 ? outputs : undefined
|
||||
}
|
||||
|
||||
const allNodeDefsLocale = Object.fromEntries(
|
||||
nodeDefs
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map((nodeDef) => [
|
||||
normalizeI18nKey(nodeDef.name),
|
||||
{
|
||||
display_name: nodeDef.display_name ?? nodeDef.name,
|
||||
description: nodeDef.description || undefined,
|
||||
inputs: extractInputs(nodeDef),
|
||||
outputs: extractOutputs(nodeDef)
|
||||
}
|
||||
])
|
||||
)
|
||||
|
||||
const allNodeCategoriesLocale = Object.fromEntries(
|
||||
nodeDefs.flatMap((nodeDef) =>
|
||||
nodeDef.category
|
||||
.split('/')
|
||||
.map((category) => [normalizeI18nKey(category), category])
|
||||
)
|
||||
)
|
||||
|
||||
fs.writeFileSync(
|
||||
localePath,
|
||||
JSON.stringify(
|
||||
@@ -241,16 +139,13 @@ test('collect-i18n', async ({ comfyPage }) => {
|
||||
...allSettingCategoriesLocale
|
||||
},
|
||||
serverConfigItems: allServerConfigsLocale,
|
||||
serverConfigCategories: allServerConfigCategoriesLocale,
|
||||
dataTypes: allDataTypesLocale,
|
||||
nodeCategories: allNodeCategoriesLocale
|
||||
serverConfigCategories: allServerConfigCategoriesLocale
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
)
|
||||
|
||||
fs.writeFileSync(nodeDefsPath, JSON.stringify(allNodeDefsLocale, null, 2))
|
||||
fs.writeFileSync(commandsPath, JSON.stringify(allCommandsLocale, null, 2))
|
||||
fs.writeFileSync(settingsPath, JSON.stringify(allSettingsLocale, null, 2))
|
||||
})
|
||||
125
scripts/collect-i18n-node-defs.ts
Normal file
125
scripts/collect-i18n-node-defs.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import * as fs from 'fs'
|
||||
|
||||
import { comfyPageFixture as test } from '../browser_tests/fixtures/ComfyPage'
|
||||
import type { ComfyApi } from '../src/scripts/api'
|
||||
import { ComfyNodeDefImpl } from '../src/stores/nodeDefStore'
|
||||
import { normalizeI18nKey } from '../src/utils/formatUtil'
|
||||
|
||||
const localePath = './src/locales/en/main.json'
|
||||
const nodeDefsPath = './src/locales/en/nodeDefs.json'
|
||||
|
||||
test('collect-i18n-node-defs', async ({ comfyPage }) => {
|
||||
const nodeDefs: ComfyNodeDefImpl[] = Object.values(
|
||||
await comfyPage.page.evaluate(async () => {
|
||||
const api = window['app'].api as ComfyApi
|
||||
return await api.getNodeDefs()
|
||||
})
|
||||
).map((def) => new ComfyNodeDefImpl(def))
|
||||
|
||||
console.log(`Collected ${nodeDefs.length} node definitions`)
|
||||
|
||||
const allDataTypesLocale = Object.fromEntries(
|
||||
nodeDefs
|
||||
.flatMap((nodeDef) => {
|
||||
const inputDataTypes = Object.values(nodeDef.inputs.all).map(
|
||||
(inputSpec) => inputSpec.type
|
||||
)
|
||||
const outputDataTypes = nodeDef.outputs.all.map((output) => output.type)
|
||||
const allDataTypes = [...inputDataTypes, ...outputDataTypes].flatMap(
|
||||
(type: string) => type.split(',')
|
||||
)
|
||||
return allDataTypes.map((dataType) => [
|
||||
normalizeI18nKey(dataType),
|
||||
dataType
|
||||
])
|
||||
})
|
||||
.sort((a, b) => a[0].localeCompare(b[0]))
|
||||
)
|
||||
|
||||
function extractInputs(nodeDef: ComfyNodeDefImpl) {
|
||||
const inputs = Object.fromEntries(
|
||||
nodeDef.inputs.all.flatMap((input) => {
|
||||
const name = input.name
|
||||
const tooltip = input.tooltip
|
||||
|
||||
if (name === undefined && tooltip === undefined) {
|
||||
return []
|
||||
}
|
||||
|
||||
return [
|
||||
[
|
||||
normalizeI18nKey(input.name),
|
||||
{
|
||||
name,
|
||||
tooltip
|
||||
}
|
||||
]
|
||||
]
|
||||
})
|
||||
)
|
||||
return Object.keys(inputs).length > 0 ? inputs : undefined
|
||||
}
|
||||
|
||||
function extractOutputs(nodeDef: ComfyNodeDefImpl) {
|
||||
const outputs = Object.fromEntries(
|
||||
nodeDef.outputs.all.flatMap((output, i) => {
|
||||
// Ignore data types if they are already translated in allDataTypesLocale.
|
||||
const name = output.name in allDataTypesLocale ? undefined : output.name
|
||||
const tooltip = output.tooltip
|
||||
|
||||
if (name === undefined && tooltip === undefined) {
|
||||
return []
|
||||
}
|
||||
|
||||
return [
|
||||
[
|
||||
i.toString(),
|
||||
{
|
||||
name,
|
||||
tooltip
|
||||
}
|
||||
]
|
||||
]
|
||||
})
|
||||
)
|
||||
return Object.keys(outputs).length > 0 ? outputs : undefined
|
||||
}
|
||||
|
||||
const allNodeDefsLocale = Object.fromEntries(
|
||||
nodeDefs
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map((nodeDef) => [
|
||||
normalizeI18nKey(nodeDef.name),
|
||||
{
|
||||
display_name: nodeDef.display_name ?? nodeDef.name,
|
||||
description: nodeDef.description || undefined,
|
||||
inputs: extractInputs(nodeDef),
|
||||
outputs: extractOutputs(nodeDef)
|
||||
}
|
||||
])
|
||||
)
|
||||
|
||||
const allNodeCategoriesLocale = Object.fromEntries(
|
||||
nodeDefs.flatMap((nodeDef) =>
|
||||
nodeDef.category
|
||||
.split('/')
|
||||
.map((category) => [normalizeI18nKey(category), category])
|
||||
)
|
||||
)
|
||||
|
||||
const locale = JSON.parse(fs.readFileSync(localePath, 'utf-8'))
|
||||
fs.writeFileSync(
|
||||
localePath,
|
||||
JSON.stringify(
|
||||
{
|
||||
...locale,
|
||||
dataTypes: allDataTypesLocale,
|
||||
nodeCategories: allNodeCategoriesLocale
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
)
|
||||
|
||||
fs.writeFileSync(nodeDefsPath, JSON.stringify(allNodeDefsLocale, null, 2))
|
||||
})
|
||||
35
scripts/generate-json-schema.ts
Normal file
35
scripts/generate-json-schema.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { zodToJsonSchema } from 'zod-to-json-schema'
|
||||
|
||||
import { zComfyWorkflow, zComfyWorkflow1 } from '../src/types/comfyWorkflow'
|
||||
|
||||
// Convert both workflow schemas to JSON Schema
|
||||
const workflow04Schema = zodToJsonSchema(zComfyWorkflow, {
|
||||
name: 'ComfyWorkflow0_4',
|
||||
$refStrategy: 'none'
|
||||
})
|
||||
|
||||
const workflow1Schema = zodToJsonSchema(zComfyWorkflow1, {
|
||||
name: 'ComfyWorkflow1_0',
|
||||
$refStrategy: 'none'
|
||||
})
|
||||
|
||||
// Create output directory if it doesn't exist
|
||||
const outputDir = './schemas'
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir, { recursive: true })
|
||||
}
|
||||
|
||||
// Write schemas to files
|
||||
fs.writeFileSync(
|
||||
path.join(outputDir, 'workflow-0_4.json'),
|
||||
JSON.stringify(workflow04Schema, null, 2)
|
||||
)
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(outputDir, 'workflow-1_0.json'),
|
||||
JSON.stringify(workflow1Schema, null, 2)
|
||||
)
|
||||
|
||||
console.log('JSON Schemas generated successfully!')
|
||||
@@ -1,71 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
import requests
|
||||
import zipfile
|
||||
import shutil
|
||||
import git
|
||||
import tempfile
|
||||
|
||||
|
||||
def download_and_extract(version, temp_dir):
|
||||
url = f"https://github.com/Comfy-Org/ComfyUI_frontend/releases/download/v{version}/dist.zip"
|
||||
response = requests.get(url)
|
||||
|
||||
if response.status_code == 200:
|
||||
zip_path = os.path.join(temp_dir, "dist.zip")
|
||||
with open(zip_path, "wb") as file:
|
||||
file.write(response.content)
|
||||
|
||||
with zipfile.ZipFile(zip_path, "r") as zip_ref:
|
||||
zip_ref.extractall(temp_dir)
|
||||
|
||||
# Clean up the zip file after extraction
|
||||
os.remove(zip_path)
|
||||
else:
|
||||
raise Exception(
|
||||
f"Failed to download release asset. Status code: {response.status_code}"
|
||||
)
|
||||
|
||||
|
||||
def update_repo(repo_path, version, temp_dir):
|
||||
repo = git.Repo(repo_path)
|
||||
|
||||
# Stash any changes
|
||||
repo.git.stash()
|
||||
|
||||
# Create and checkout new branch
|
||||
new_branch = f"release-{version}"
|
||||
repo.git.checkout("-b", new_branch, "-t", "origin/master")
|
||||
|
||||
# Remove all files under web/ directory
|
||||
web_dir = os.path.join(repo_path, "web")
|
||||
if os.path.exists(web_dir):
|
||||
shutil.rmtree(web_dir)
|
||||
|
||||
# Move content from temp_dir to web/
|
||||
shutil.move(temp_dir, web_dir)
|
||||
|
||||
# Add changes and commit
|
||||
repo.git.add(all=True)
|
||||
commit_message = f"Update web content to release v{version}"
|
||||
repo.git.commit("-m", commit_message)
|
||||
|
||||
|
||||
def main(repo_path: str, version: str):
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
try:
|
||||
download_and_extract(version, temp_dir)
|
||||
update_repo(repo_path, version, temp_dir)
|
||||
print(f"Successfully updated repo to release v{version}")
|
||||
except Exception as e:
|
||||
print(f"An error occurred: {str(e)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 3:
|
||||
print("Usage: python script.py <repo_path> <version>")
|
||||
sys.exit(1)
|
||||
|
||||
repo_path = sys.argv[1]
|
||||
version = sys.argv[2]
|
||||
main(repo_path, version)
|
||||
@@ -10,6 +10,7 @@
|
||||
--bg-color: #fff;
|
||||
--comfy-menu-bg: #353535;
|
||||
--comfy-menu-secondary-bg: #292929;
|
||||
--comfy-topbar-height: 2.5rem;
|
||||
--comfy-input-bg: #222;
|
||||
--input-text: #ddd;
|
||||
--descrip-text: #999;
|
||||
@@ -763,3 +764,17 @@ audio.comfy-audio.empty-audio-widget {
|
||||
.p-tree-node-content {
|
||||
padding: var(--comfy-tree-explorer-item-padding) !important;
|
||||
}
|
||||
|
||||
/* [Desktop] Electron window specific styles */
|
||||
.app-drag {
|
||||
app-region: drag;
|
||||
}
|
||||
|
||||
.no-drag {
|
||||
app-region: no-drag;
|
||||
}
|
||||
|
||||
.window-actions-spacer {
|
||||
width: calc(100vw - env(titlebar-area-width, 100vw));
|
||||
}
|
||||
/* End of [Desktop] Electron window specific styles */
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"NODE_SELECTED_TITLE_COLOR": "#FFF",
|
||||
"NODE_TEXT_SIZE": 14,
|
||||
"NODE_TEXT_COLOR": "#AAA",
|
||||
"NODE_TEXT_HIGHLIGHT_COLOR": "#FFF",
|
||||
"NODE_SUBTEXT_SIZE": 12,
|
||||
"NODE_DEFAULT_COLOR": "#333",
|
||||
"NODE_DEFAULT_BGCOLOR": "#353535",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"id": "light",
|
||||
"name": "Light",
|
||||
"light_theme": true,
|
||||
"colors": {
|
||||
"node_slot": {
|
||||
"CLIP": "#FFA726",
|
||||
@@ -13,7 +14,12 @@
|
||||
"MASK": "#9CCC65",
|
||||
"MODEL": "#7E57C2",
|
||||
"STYLE_MODEL": "#D4E157",
|
||||
"VAE": "#FF7043"
|
||||
"VAE": "#FF7043",
|
||||
"NOISE": "#B0B0B0",
|
||||
"GUIDER": "#66FFFF",
|
||||
"SAMPLER": "#ECB4B4",
|
||||
"SIGMAS": "#CDFFCD",
|
||||
"TAESD": "#DCC274"
|
||||
},
|
||||
"litegraph_base": {
|
||||
"BACKGROUND_IMAGE": "data:image/gif;base64,R0lGODlhZABkALMAAAAAAP///+vr6+rq6ujo6Ofn5+bm5uXl5d3d3f///wAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAkALAAAAABkAGQAAAT/UMhJq7046827HkcoHkYxjgZhnGG6si5LqnIM0/fL4qwwIMAg0CAsEovBIxKhRDaNy2GUOX0KfVFrssrNdpdaqTeKBX+dZ+jYvEaTf+y4W66mC8PUdrE879f9d2mBeoNLfH+IhYBbhIx2jkiHiomQlGKPl4uZe3CaeZifnnijgkESBqipqqusra6vsLGys62SlZO4t7qbuby7CLa+wqGWxL3Gv3jByMOkjc2lw8vOoNSi0czAncXW3Njdx9Pf48/Z4Kbbx+fQ5evZ4u3k1fKR6cn03vHlp7T9/v8A/8Gbp4+gwXoFryXMB2qgwoMMHyKEqA5fxX322FG8tzBcRnMW/zlulPbRncmQGidKjMjyYsOSKEF2FBlJQMCbOHP6c9iSZs+UnGYCdbnSo1CZI5F64kn0p1KnTH02nSoV3dGTV7FFHVqVq1dtWcMmVQZTbNGu72zqXMuW7danVL+6e4t1bEy6MeueBYLXrNO5Ze36jQtWsOG97wIj1vt3St/DjTEORss4nNq2mDP3e7w4r1bFkSET5hy6s2TRlD2/mSxXtSHQhCunXo26NevCpmvD/UU6tuullzULH76q92zdZG/Ltv1a+W+osI/nRmyc+fRi1Xdbh+68+0vv10dH3+77KD/i6IdnX669/frn5Zsjh4/2PXju8+8bzc9/6fj27LFnX11/+IUnXWl7BJfegm79FyB9JOl3oHgSklefgxAC+FmFGpqHIYcCfkhgfCohSKKJVo044YUMttggiBkmp6KFXw1oII24oYhjiDByaKOOHcp3Y5BD/njikSkO+eBREQAAOw==",
|
||||
@@ -22,6 +28,7 @@
|
||||
"NODE_SELECTED_TITLE_COLOR": "#000",
|
||||
"NODE_TEXT_SIZE": 14,
|
||||
"NODE_TEXT_COLOR": "#444",
|
||||
"NODE_TEXT_HIGHLIGHT_COLOR": "#1e293b",
|
||||
"NODE_SUBTEXT_SIZE": 12,
|
||||
"NODE_DEFAULT_COLOR": "#F7F7F7",
|
||||
"NODE_DEFAULT_BGCOLOR": "#F5F5F5",
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
<template>
|
||||
<Button
|
||||
<div
|
||||
v-show="workspaceState.focusMode"
|
||||
class="comfy-menu-hamburger"
|
||||
class="comfy-menu-hamburger no-drag"
|
||||
:style="positionCSS"
|
||||
icon="pi pi-bars"
|
||||
severity="secondary"
|
||||
text
|
||||
size="large"
|
||||
v-tooltip="{ value: $t('menu.showMenu'), showDelay: 300 }"
|
||||
@click="exitFocusMode"
|
||||
@contextmenu="showNativeMenu"
|
||||
/>
|
||||
>
|
||||
<Button
|
||||
icon="pi pi-bars"
|
||||
severity="secondary"
|
||||
text
|
||||
size="large"
|
||||
v-tooltip="{ value: $t('menu.showMenu'), showDelay: 300 }"
|
||||
:aria-label="$t('menu.showMenu')"
|
||||
aria-live="assertive"
|
||||
@click="exitFocusMode"
|
||||
@contextmenu="showNativeMenu"
|
||||
/>
|
||||
<div v-show="menuSetting !== 'Bottom'" class="window-actions-spacer" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -51,8 +57,6 @@ const positionCSS = computed<CSSProperties>(() =>
|
||||
|
||||
<style scoped>
|
||||
.comfy-menu-hamburger {
|
||||
pointer-events: auto;
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
@apply pointer-events-auto fixed z-[9999] flex flex-row;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
class="batch-count"
|
||||
:class="props.class"
|
||||
v-tooltip.bottom="$t('menu.batchCount')"
|
||||
:aria-label="$t('menu.batchCount')"
|
||||
>
|
||||
<InputNumber
|
||||
class="w-14"
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
:severity="executingPrompt ? 'danger' : 'secondary'"
|
||||
:disabled="!executingPrompt"
|
||||
text
|
||||
:aria-label="$t('menu.interrupt')"
|
||||
@click="() => commandStore.execute('Comfy.Interrupt')"
|
||||
>
|
||||
</Button>
|
||||
@@ -48,6 +49,7 @@
|
||||
:severity="hasPendingTasks ? 'danger' : 'secondary'"
|
||||
:disabled="!hasPendingTasks"
|
||||
text
|
||||
:aria-label="$t('sideToolbar.queueTab.clearPendingTasks')"
|
||||
@click="() => commandStore.execute('Comfy.ClearPendingTasks')"
|
||||
/>
|
||||
</ButtonGroup>
|
||||
|
||||
40
src/components/common/CheckboxGroup.vue
Normal file
40
src/components/common/CheckboxGroup.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<div :class="['flex flex-wrap', $attrs.class]">
|
||||
<div
|
||||
v-for="checkbox in checkboxes"
|
||||
:key="checkbox.value"
|
||||
class="flex items-center gap-2"
|
||||
>
|
||||
<Checkbox
|
||||
v-model="internalSelection"
|
||||
:inputId="checkbox.value"
|
||||
:value="checkbox.value"
|
||||
/>
|
||||
<label :for="checkbox.value" class="ml-2">{{ checkbox.label }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Checkbox from 'primevue/checkbox'
|
||||
import { computed } from 'vue'
|
||||
|
||||
interface CheckboxItem {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
checkboxes: CheckboxItem[]
|
||||
modelValue: string[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: string[]): void
|
||||
}>()
|
||||
|
||||
const internalSelection = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value: string[]) => emit('update:modelValue', value)
|
||||
})
|
||||
</script>
|
||||
@@ -12,12 +12,14 @@
|
||||
:data-test="src"
|
||||
class="comfy-image-blur"
|
||||
:style="{ 'background-image': `url(${src})` }"
|
||||
:alt="alt"
|
||||
/>
|
||||
<img
|
||||
:src="src"
|
||||
@error="handleImageError"
|
||||
class="comfy-image-main"
|
||||
:class="[...classArray]"
|
||||
:alt="alt"
|
||||
/>
|
||||
</span>
|
||||
<div v-if="imageBroken" class="broken-image-placeholder">
|
||||
@@ -34,9 +36,11 @@ const props = withDefaults(
|
||||
src: string
|
||||
class?: string | string[] | object
|
||||
contain: boolean
|
||||
alt?: string
|
||||
}>(),
|
||||
{
|
||||
contain: false
|
||||
contain: false,
|
||||
alt: 'Image content'
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
:options="colorOptions"
|
||||
optionLabel="name"
|
||||
dataKey="value"
|
||||
:allow-empty="false"
|
||||
>
|
||||
<template #option="slotProps">
|
||||
<div
|
||||
|
||||
@@ -5,12 +5,20 @@
|
||||
:message="props.error.exception_message"
|
||||
/>
|
||||
<div class="comfy-error-report">
|
||||
<Button
|
||||
v-show="!reportOpen"
|
||||
:label="$t('g.showReport')"
|
||||
@click="showReport"
|
||||
text
|
||||
/>
|
||||
<div class="flex gap-2 justify-center">
|
||||
<Button
|
||||
v-show="!reportOpen"
|
||||
text
|
||||
:label="$t('g.showReport')"
|
||||
@click="showReport"
|
||||
/>
|
||||
<Button
|
||||
v-show="!sendReportOpen"
|
||||
text
|
||||
:label="$t('issueReport.helpFix')"
|
||||
@click="showSendReport"
|
||||
/>
|
||||
</div>
|
||||
<template v-if="reportOpen">
|
||||
<Divider />
|
||||
<ScrollPanel style="width: 100%; height: 400px; max-width: 80vw">
|
||||
@@ -18,9 +26,12 @@
|
||||
</ScrollPanel>
|
||||
<Divider />
|
||||
</template>
|
||||
|
||||
<ReportIssuePanel
|
||||
v-if="sendReportOpen"
|
||||
error-type="graphExecutionError"
|
||||
:extra-fields="[stackTraceField]"
|
||||
/>
|
||||
<div class="action-container">
|
||||
<ReportIssueButton v-if="showSendError" :error="props.error" />
|
||||
<FindIssueButton
|
||||
:errorMessage="props.error.exception_message"
|
||||
:repoOwner="repoOwner"
|
||||
@@ -41,16 +52,18 @@ import Button from 'primevue/button'
|
||||
import Divider from 'primevue/divider'
|
||||
import ScrollPanel from 'primevue/scrollpanel'
|
||||
import { useToast } from 'primevue/usetoast'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
|
||||
import FindIssueButton from '@/components/dialog/content/error/FindIssueButton.vue'
|
||||
import ReportIssueButton from '@/components/dialog/content/error/ReportIssueButton.vue'
|
||||
import { useCopyToClipboard } from '@/hooks/clipboardHooks'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app } from '@/scripts/app'
|
||||
import type { ExecutionErrorWsMessage, SystemStats } from '@/types/apiTypes'
|
||||
import { isElectron } from '@/utils/envUtil'
|
||||
import type { ReportField } from '@/types/issueReportTypes'
|
||||
|
||||
import ReportIssuePanel from './error/ReportIssuePanel.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
error: ExecutionErrorWsMessage
|
||||
@@ -63,9 +76,24 @@ const reportOpen = ref(false)
|
||||
const showReport = () => {
|
||||
reportOpen.value = true
|
||||
}
|
||||
const showSendError = isElectron()
|
||||
|
||||
const sendReportOpen = ref(false)
|
||||
const showSendReport = () => {
|
||||
sendReportOpen.value = true
|
||||
}
|
||||
const toast = useToast()
|
||||
const { t } = useI18n()
|
||||
|
||||
const stackTraceField = computed<ReportField>(() => {
|
||||
return {
|
||||
label: t('issueReport.stackTrace'),
|
||||
value: 'StackTrace',
|
||||
optIn: true,
|
||||
data: {
|
||||
nodeType: props.error.node_type,
|
||||
stackTrace: props.error.traceback?.join('\n')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
v-model:modelValue="searchQuery"
|
||||
@search="handleSearch"
|
||||
:placeholder="$t('g.searchSettings') + '...'"
|
||||
:debounce-time="128"
|
||||
/>
|
||||
<Listbox
|
||||
v-model="activeCategory"
|
||||
@@ -14,7 +15,7 @@
|
||||
scrollHeight="100%"
|
||||
:optionDisabled="
|
||||
(option: SettingTreeNode) =>
|
||||
inSearch && !searchResultsCategories.has(option.label)
|
||||
!queryIsEmpty && !searchResultsCategories.has(option.label)
|
||||
"
|
||||
class="border-none w-full"
|
||||
/>
|
||||
@@ -263,9 +264,8 @@ const handleSearch = (query: string) => {
|
||||
activeCategory.value = null
|
||||
}
|
||||
|
||||
const inSearch = computed(
|
||||
() => searchQuery.value.length > 0 && !searchInProgress.value
|
||||
)
|
||||
const queryIsEmpty = computed(() => searchQuery.value.length === 0)
|
||||
const inSearch = computed(() => !queryIsEmpty.value && !searchInProgress.value)
|
||||
const tabValue = computed(() =>
|
||||
inSearch.value ? 'Search Results' : activeCategory.value?.label
|
||||
)
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
<template>
|
||||
<Button
|
||||
@click="reportIssue"
|
||||
:label="$t('g.reportIssue')"
|
||||
:severity="submitted ? 'success' : 'secondary'"
|
||||
:icon="icon"
|
||||
:disabled="submitted"
|
||||
v-tooltip="$t('g.reportIssueTooltip')"
|
||||
>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Button from 'primevue/button'
|
||||
import { useToast } from 'primevue/usetoast'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { ExecutionErrorWsMessage } from '@/types/apiTypes'
|
||||
import { electronAPI } from '@/utils/envUtil'
|
||||
|
||||
const { error } = defineProps<{
|
||||
error: ExecutionErrorWsMessage
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const toast = useToast()
|
||||
const submitting = ref(false)
|
||||
const submitted = ref(false)
|
||||
const icon = computed(
|
||||
() => `pi ${submitting.value ? 'pi-spin pi-spinner' : 'pi-send'}`
|
||||
)
|
||||
|
||||
const reportIssue = async () => {
|
||||
if (submitting.value) return
|
||||
submitting.value = true
|
||||
try {
|
||||
await electronAPI().sendErrorToSentry(error.exception_message, {
|
||||
stackTrace: error.traceback?.join('\n'),
|
||||
nodeType: error.node_type
|
||||
})
|
||||
submitted.value = true
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: t('g.reportSent'),
|
||||
life: 3000
|
||||
})
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
197
src/components/dialog/content/error/ReportIssuePanel.vue
Normal file
197
src/components/dialog/content/error/ReportIssuePanel.vue
Normal file
@@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<Panel>
|
||||
<template #header>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-bold">{{ $t('issueReport.submitErrorReport') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="flex justify-end">
|
||||
<Button
|
||||
v-tooltip="$t('g.reportIssueTooltip')"
|
||||
:label="submitted ? $t('g.reportSent') : $t('g.reportIssue')"
|
||||
:severity="isButtonDisabled ? 'secondary' : 'primary'"
|
||||
:icon="icon"
|
||||
:disabled="isButtonDisabled"
|
||||
@click="reportIssue"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<div class="p-4 mt-4 border border-round surface-border shadow-1">
|
||||
<CheckboxGroup
|
||||
v-model="selection"
|
||||
class="gap-4 mb-4"
|
||||
:checkboxes="reportCheckboxes"
|
||||
/>
|
||||
<div class="mb-4">
|
||||
<InputText
|
||||
v-model="contactInfo"
|
||||
class="w-full"
|
||||
:placeholder="$t('issueReport.provideEmail')"
|
||||
:maxlength="CONTACT_MAX_LEN"
|
||||
/>
|
||||
<CheckboxGroup
|
||||
v-model="contactPrefs"
|
||||
class="gap-3 mt-2"
|
||||
:checkboxes="contactCheckboxes"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<Textarea
|
||||
v-model="details"
|
||||
class="w-full"
|
||||
rows="4"
|
||||
:maxlength="DETAILS_MAX_LEN"
|
||||
:placeholder="$t('issueReport.provideAdditionalDetails')"
|
||||
:aria-label="$t('issueReport.provideAdditionalDetails')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { CaptureContext, User } from '@sentry/core'
|
||||
import { captureMessage } from '@sentry/core'
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
import Button from 'primevue/button'
|
||||
import InputText from 'primevue/inputtext'
|
||||
import Panel from 'primevue/panel'
|
||||
import Textarea from 'primevue/textarea'
|
||||
import { useToast } from 'primevue/usetoast'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import CheckboxGroup from '@/components/common/CheckboxGroup.vue'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app } from '@/scripts/app'
|
||||
import type { DefaultField, ReportField } from '@/types/issueReportTypes'
|
||||
|
||||
const ISSUE_NAME = 'User reported issue'
|
||||
const DETAILS_MAX_LEN = 5_000
|
||||
const CONTACT_MAX_LEN = 320
|
||||
|
||||
const props = defineProps<{
|
||||
errorType: string
|
||||
defaultFields?: DefaultField[]
|
||||
extraFields?: ReportField[]
|
||||
}>()
|
||||
const { defaultFields = ['Workflow', 'Logs', 'SystemStats', 'Settings'] } =
|
||||
props
|
||||
|
||||
const { t } = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
const selection = ref<string[]>([])
|
||||
const contactPrefs = ref<string[]>([])
|
||||
const contactInfo = ref('')
|
||||
const details = ref('')
|
||||
const submitting = ref(false)
|
||||
const submitted = ref(false)
|
||||
|
||||
const followUp = computed(() => contactPrefs.value.includes('FollowUp'))
|
||||
const notifyResolve = computed(() => contactPrefs.value.includes('Resolution'))
|
||||
|
||||
const icon = computed(() => {
|
||||
if (submitting.value) return 'pi pi-spin pi-spinner'
|
||||
if (submitted.value) return 'pi pi-check'
|
||||
return 'pi pi-send'
|
||||
})
|
||||
const isFormEmpty = computed(() => !selection.value.length && !details.value)
|
||||
const isButtonDisabled = computed(
|
||||
() => submitted.value || submitting.value || isFormEmpty.value
|
||||
)
|
||||
|
||||
const contactCheckboxes = [
|
||||
{ label: t('issueReport.contactFollowUp'), value: 'FollowUp' },
|
||||
{ label: t('issueReport.notifyResolve'), value: 'Resolution' }
|
||||
]
|
||||
const defaultReportCheckboxes = [
|
||||
{ label: t('g.workflow'), value: 'Workflow' },
|
||||
{ label: t('g.logs'), value: 'Logs' },
|
||||
{ label: t('issueReport.systemStats'), value: 'SystemStats' },
|
||||
{ label: t('g.settings'), value: 'Settings' }
|
||||
]
|
||||
const reportCheckboxes = computed(() => [
|
||||
...(props.extraFields?.map(({ label, value }) => ({ label, value })) ?? []),
|
||||
...defaultReportCheckboxes.filter(({ value }) =>
|
||||
defaultFields.includes(value as DefaultField)
|
||||
)
|
||||
])
|
||||
|
||||
const getUserInfo = (): User => ({ email: contactInfo.value })
|
||||
|
||||
const getLogs = async () =>
|
||||
selection.value.includes('Logs') ? api.getLogs() : null
|
||||
|
||||
const getSystemStats = async () =>
|
||||
selection.value.includes('SystemStats') ? api.getSystemStats() : null
|
||||
|
||||
const getSettings = async () =>
|
||||
selection.value.includes('Settings') ? api.getSettings() : null
|
||||
|
||||
const getWorkflow = () =>
|
||||
selection.value.includes('Workflow')
|
||||
? cloneDeep(app.graph.asSerialisable())
|
||||
: null
|
||||
|
||||
const createDefaultFields = async () => {
|
||||
const [settings, systemStats, logs, workflow] = await Promise.all([
|
||||
getSettings(),
|
||||
getSystemStats(),
|
||||
getLogs(),
|
||||
getWorkflow()
|
||||
])
|
||||
return { settings, systemStats, logs, workflow }
|
||||
}
|
||||
|
||||
const createExtraFields = (): Record<string, unknown> | undefined => {
|
||||
if (!props.extraFields) return undefined
|
||||
|
||||
return props.extraFields
|
||||
.filter((field) => !field.optIn || selection.value.includes(field.value))
|
||||
.reduce((acc, field) => ({ ...acc, ...cloneDeep(field.data) }), {})
|
||||
}
|
||||
|
||||
const createFeedback = () => {
|
||||
return {
|
||||
details: details.value,
|
||||
contactPreferences: {
|
||||
followUp: followUp.value,
|
||||
notifyOnResolution: notifyResolve.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const createCaptureContext = async (): Promise<CaptureContext> => {
|
||||
return {
|
||||
user: getUserInfo(),
|
||||
level: 'error',
|
||||
tags: {
|
||||
errorType: props.errorType
|
||||
},
|
||||
extra: {
|
||||
...createFeedback(),
|
||||
...(await createDefaultFields()),
|
||||
...createExtraFields()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const reportIssue = async () => {
|
||||
if (isButtonDisabled.value) return
|
||||
|
||||
submitting.value = true
|
||||
try {
|
||||
captureMessage(ISSUE_NAME, await createCaptureContext())
|
||||
submitted.value = true
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: t('g.reportSent'),
|
||||
life: 3000
|
||||
})
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,230 @@
|
||||
// @ts-strict-ignore
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import Button from 'primevue/button'
|
||||
import PrimeVue from 'primevue/config'
|
||||
import InputText from 'primevue/inputtext'
|
||||
import Panel from 'primevue/panel'
|
||||
import Textarea from 'primevue/textarea'
|
||||
import Tooltip from 'primevue/tooltip'
|
||||
import { beforeAll, describe, expect, it, vi } from 'vitest'
|
||||
import { createApp } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import CheckboxGroup from '@/components/common/CheckboxGroup.vue'
|
||||
import enMesages from '@/locales/en/main.json'
|
||||
import { DefaultField, ReportField } from '@/types/issueReportTypes'
|
||||
|
||||
import ReportIssuePanel from '../ReportIssuePanel.vue'
|
||||
|
||||
type ReportIssuePanelProps = {
|
||||
errorType: string
|
||||
defaultFields?: DefaultField[]
|
||||
extraFields?: ReportField[]
|
||||
}
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages: {
|
||||
en: enMesages
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('primevue/usetoast', () => ({
|
||||
useToast: vi.fn(() => ({
|
||||
add: vi.fn()
|
||||
}))
|
||||
}))
|
||||
|
||||
vi.mock('@/scripts/api', () => ({
|
||||
api: {
|
||||
getLogs: vi.fn().mockResolvedValue('mock logs'),
|
||||
getSystemStats: vi.fn().mockResolvedValue('mock stats'),
|
||||
getSettings: vi.fn().mockResolvedValue('mock settings')
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/scripts/app', () => ({
|
||||
app: {
|
||||
graph: {
|
||||
asSerialisable: vi.fn().mockReturnValue({})
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@sentry/core', () => ({
|
||||
captureMessage: vi.fn()
|
||||
}))
|
||||
|
||||
describe('ReportIssuePanel', () => {
|
||||
beforeAll(() => {
|
||||
const app = createApp({})
|
||||
app.use(PrimeVue)
|
||||
})
|
||||
|
||||
const mountComponent = (props: ReportIssuePanelProps, options = {}): any => {
|
||||
return mount(ReportIssuePanel, {
|
||||
global: {
|
||||
plugins: [PrimeVue, createTestingPinia(), i18n],
|
||||
directives: { tooltip: Tooltip },
|
||||
components: { InputText, Button, Panel, Textarea, CheckboxGroup }
|
||||
},
|
||||
props,
|
||||
...options
|
||||
})
|
||||
}
|
||||
|
||||
it('renders the panel with all required components', () => {
|
||||
const wrapper = mountComponent({ errorType: 'Test Error' })
|
||||
expect(wrapper.find('.p-panel').exists()).toBe(true)
|
||||
expect(wrapper.findAllComponents(CheckboxGroup).length).toBe(2)
|
||||
expect(wrapper.findComponent(InputText).exists()).toBe(true)
|
||||
expect(wrapper.findComponent(Textarea).exists()).toBe(true)
|
||||
expect(wrapper.findComponent(Button).exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('updates selection when checkboxes are selected', async () => {
|
||||
const wrapper = mountComponent({ errorType: 'Test Error' })
|
||||
const checkboxes = wrapper.findAllComponents(CheckboxGroup).at(0)
|
||||
await checkboxes?.setValue(['Workflow', 'Logs'])
|
||||
expect(wrapper.vm.selection).toEqual(['Workflow', 'Logs'])
|
||||
})
|
||||
|
||||
it('updates contactInfo when input is changed', async () => {
|
||||
const wrapper = mountComponent({ errorType: 'Test Error' })
|
||||
const input = wrapper.findComponent(InputText)
|
||||
await input.setValue('test@example.com')
|
||||
expect(wrapper.vm.contactInfo).toBe('test@example.com')
|
||||
})
|
||||
|
||||
it('updates additional details when textarea is changed', async () => {
|
||||
const wrapper = mountComponent({ errorType: 'Test Error' })
|
||||
const textarea = wrapper.findComponent(Textarea)
|
||||
await textarea.setValue('This is a test detail.')
|
||||
expect(wrapper.vm.details).toBe('This is a test detail.')
|
||||
})
|
||||
|
||||
it('updates contactPrefs when preferences are selected', async () => {
|
||||
const wrapper = mountComponent({ errorType: 'Test Error' })
|
||||
const preferences = wrapper.findAllComponents(CheckboxGroup).at(1)
|
||||
await preferences?.setValue(['FollowUp'])
|
||||
expect(wrapper.vm.contactPrefs).toEqual(['FollowUp'])
|
||||
})
|
||||
|
||||
it('does not allow submission if the form is empty', async () => {
|
||||
const wrapper = mountComponent({ errorType: 'Test Error' })
|
||||
await wrapper.vm.reportIssue()
|
||||
expect(wrapper.vm.submitted).toBe(false)
|
||||
})
|
||||
|
||||
it('renders with overridden default fields', () => {
|
||||
const wrapper = mountComponent({
|
||||
errorType: 'Test Error',
|
||||
defaultFields: ['Settings']
|
||||
})
|
||||
const checkboxes = wrapper.findAllComponents(CheckboxGroup).at(0)
|
||||
expect(checkboxes?.props('checkboxes')).toEqual([
|
||||
{ label: 'Settings', value: 'Settings' }
|
||||
])
|
||||
})
|
||||
|
||||
it('renders additional fields when extraFields prop is provided', () => {
|
||||
const extraFields = [
|
||||
{ label: 'Custom Field', value: 'CustomField', optIn: true, data: {} }
|
||||
]
|
||||
const wrapper = mountComponent({ errorType: 'Test Error', extraFields })
|
||||
const checkboxes = wrapper.findAllComponents(CheckboxGroup).at(0)
|
||||
expect(checkboxes?.props('checkboxes')).toContainEqual({
|
||||
label: 'Custom Field',
|
||||
value: 'CustomField'
|
||||
})
|
||||
})
|
||||
|
||||
it('does not submit unchecked fields', async () => {
|
||||
const wrapper = mountComponent({ errorType: 'Test Error' })
|
||||
const textarea = wrapper.findComponent(Textarea)
|
||||
|
||||
await textarea.setValue('Report with only text but no fields selected')
|
||||
await wrapper.vm.reportIssue()
|
||||
|
||||
const { captureMessage } = (await import('@sentry/core')) as any
|
||||
const captureContext = captureMessage.mock.calls[0][1]
|
||||
|
||||
expect(captureContext.extra.logs).toBeNull()
|
||||
expect(captureContext.extra.systemStats).toBeNull()
|
||||
expect(captureContext.extra.settings).toBeNull()
|
||||
expect(captureContext.extra.workflow).toBeNull()
|
||||
})
|
||||
|
||||
it.each([
|
||||
{
|
||||
checkbox: 'Logs',
|
||||
apiMethod: 'getLogs',
|
||||
expectedKey: 'logs',
|
||||
mockValue: 'mock logs'
|
||||
},
|
||||
{
|
||||
checkbox: 'SystemStats',
|
||||
apiMethod: 'getSystemStats',
|
||||
expectedKey: 'systemStats',
|
||||
mockValue: 'mock stats'
|
||||
},
|
||||
{
|
||||
checkbox: 'Settings',
|
||||
apiMethod: 'getSettings',
|
||||
expectedKey: 'settings',
|
||||
mockValue: 'mock settings'
|
||||
}
|
||||
])(
|
||||
'submits (%s) when the (%s) checkbox is selected',
|
||||
async ({ checkbox, apiMethod, expectedKey, mockValue }) => {
|
||||
const wrapper = mountComponent({ errorType: 'Test Error' })
|
||||
|
||||
const { api } = (await import('@/scripts/api')) as any
|
||||
vi.spyOn(api, apiMethod).mockResolvedValue(mockValue)
|
||||
|
||||
const { captureMessage } = await import('@sentry/core')
|
||||
|
||||
// Select the checkbox
|
||||
const checkboxes = wrapper.findAllComponents(CheckboxGroup).at(0)
|
||||
await checkboxes?.vm.$emit('update:modelValue', [checkbox])
|
||||
|
||||
await wrapper.vm.reportIssue()
|
||||
expect(api[apiMethod]).toHaveBeenCalled()
|
||||
|
||||
// Verify the message includes the associated data
|
||||
expect(captureMessage).toHaveBeenCalledWith(
|
||||
'User reported issue',
|
||||
expect.objectContaining({
|
||||
extra: expect.objectContaining({ [expectedKey]: mockValue })
|
||||
})
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
it('submits workflow when the Workflow checkbox is selected', async () => {
|
||||
const wrapper = mountComponent({ errorType: 'Test Error' })
|
||||
|
||||
const { app } = (await import('@/scripts/app')) as any
|
||||
const { captureMessage } = await import('@sentry/core')
|
||||
|
||||
const mockWorkflow = { nodes: [], edges: [] }
|
||||
vi.spyOn(app.graph, 'asSerialisable').mockReturnValue(mockWorkflow)
|
||||
|
||||
// Select the "Workflow" checkbox
|
||||
const checkboxes = wrapper.findAllComponents(CheckboxGroup).at(0)
|
||||
await checkboxes?.vm.$emit('update:modelValue', ['Workflow'])
|
||||
|
||||
await wrapper.vm.reportIssue()
|
||||
expect(app.graph.asSerialisable).toHaveBeenCalled()
|
||||
|
||||
// Verify the message includes the workflow
|
||||
expect(captureMessage).toHaveBeenCalledWith(
|
||||
'User reported issue',
|
||||
expect.objectContaining({
|
||||
extra: expect.objectContaining({ workflow: mockWorkflow })
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -13,13 +13,13 @@
|
||||
optionValue="id"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-download"
|
||||
icon="pi pi-upload"
|
||||
text
|
||||
:title="$t('g.download')"
|
||||
:title="$t('g.export')"
|
||||
@click="colorPaletteService.exportColorPalette(activePaletteId)"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-upload"
|
||||
icon="pi pi-download"
|
||||
text
|
||||
:title="$t('g.import')"
|
||||
@click="importCustomPalette"
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
<BottomPanel />
|
||||
</template>
|
||||
<template #graph-canvas-panel>
|
||||
<SecondRowWorkflowTabs
|
||||
v-if="workflowTabsPosition === 'Topbar (2nd-row)'"
|
||||
/>
|
||||
<GraphCanvasMenu v-if="canvasMenuEnabled" />
|
||||
</template>
|
||||
</LiteGraphCanvasSplitterOverlay>
|
||||
@@ -48,12 +51,13 @@ import NodeTooltip from '@/components/graph/NodeTooltip.vue'
|
||||
import TitleEditor from '@/components/graph/TitleEditor.vue'
|
||||
import NodeSearchboxPopover from '@/components/searchbox/NodeSearchBoxPopover.vue'
|
||||
import SideToolbar from '@/components/sidebar/SideToolbar.vue'
|
||||
import SecondRowWorkflowTabs from '@/components/topbar/SecondRowWorkflowTabs.vue'
|
||||
import { CORE_SETTINGS } from '@/constants/coreSettings'
|
||||
import { usePragmaticDroppable } from '@/hooks/dndHooks'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app as comfyApp } from '@/scripts/app'
|
||||
import { ChangeTracker } from '@/scripts/changeTracker'
|
||||
import { setStorageValue } from '@/scripts/utils'
|
||||
import { getStorageValue, setStorageValue } from '@/scripts/utils'
|
||||
import { IS_CONTROL_WIDGET, updateControlWidgetLabel } from '@/scripts/widgets'
|
||||
import { useColorPaletteService } from '@/services/colorPaletteService'
|
||||
import { useLitegraphService } from '@/services/litegraphService'
|
||||
@@ -83,11 +87,37 @@ const modelToNodeStore = useModelToNodeStore()
|
||||
const betaMenuEnabled = computed(
|
||||
() => settingStore.get('Comfy.UseNewMenu') !== 'Disabled'
|
||||
)
|
||||
const workflowTabsPosition = computed(() =>
|
||||
settingStore.get('Comfy.Workflow.WorkflowTabsPosition')
|
||||
)
|
||||
const canvasMenuEnabled = computed(() =>
|
||||
settingStore.get('Comfy.Graph.CanvasMenu')
|
||||
)
|
||||
const tooltipEnabled = computed(() => settingStore.get('Comfy.EnableTooltips'))
|
||||
|
||||
const storedWorkflows = JSON.parse(
|
||||
getStorageValue('Comfy.OpenWorkflowsPaths') || '[]'
|
||||
)
|
||||
const storedActiveIndex = JSON.parse(
|
||||
getStorageValue('Comfy.ActiveWorkflowIndex') || '-1'
|
||||
)
|
||||
const openWorkflows = computed(() => workspaceStore?.workflow?.openWorkflows)
|
||||
const activeWorkflow = computed(() => workspaceStore?.workflow?.activeWorkflow)
|
||||
const restoreState = computed<{ paths: string[]; activeIndex: number }>(() => {
|
||||
if (!openWorkflows.value || !activeWorkflow.value) {
|
||||
return { paths: [], activeIndex: -1 }
|
||||
}
|
||||
|
||||
const paths = openWorkflows.value
|
||||
.filter((workflow) => workflow?.isPersisted && !workflow.isModified)
|
||||
.map((workflow) => workflow.path)
|
||||
const activeIndex = openWorkflows.value.findIndex(
|
||||
(workflow) => workflow.path === activeWorkflow.value?.path
|
||||
)
|
||||
|
||||
return { paths, activeIndex }
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
const canvasInfoEnabled = settingStore.get('Comfy.Graph.CanvasInfo')
|
||||
if (canvasStore.canvas) {
|
||||
@@ -357,6 +387,18 @@ onMounted(async () => {
|
||||
'Comfy.CustomColorPalettes'
|
||||
)
|
||||
|
||||
const isRestorable = storedWorkflows?.length > 0 && storedActiveIndex >= 0
|
||||
if (isRestorable)
|
||||
workflowStore.openWorkflowsInBackground({
|
||||
left: storedWorkflows.slice(0, storedActiveIndex),
|
||||
right: storedWorkflows.slice(storedActiveIndex)
|
||||
})
|
||||
|
||||
watch(restoreState, ({ paths, activeIndex }) => {
|
||||
setStorageValue('Comfy.OpenWorkflowsPaths', JSON.stringify(paths))
|
||||
setStorageValue('Comfy.ActiveWorkflowIndex', JSON.stringify(activeIndex))
|
||||
})
|
||||
|
||||
// Start watching for locale change after the initial value is loaded.
|
||||
watch(
|
||||
() => settingStore.get('Comfy.Locale'),
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
severity="secondary"
|
||||
icon="pi pi-plus"
|
||||
v-tooltip.left="t('graphCanvasMenu.zoomIn')"
|
||||
:aria-label="$t('graphCanvasMenu.zoomIn')"
|
||||
@mousedown="repeat('Comfy.Canvas.ZoomIn')"
|
||||
@mouseup="stopRepeat"
|
||||
/>
|
||||
@@ -13,6 +14,7 @@
|
||||
severity="secondary"
|
||||
icon="pi pi-minus"
|
||||
v-tooltip.left="t('graphCanvasMenu.zoomOut')"
|
||||
:aria-label="$t('graphCanvasMenu.zoomOut')"
|
||||
@mousedown="repeat('Comfy.Canvas.ZoomOut')"
|
||||
@mouseup="stopRepeat"
|
||||
/>
|
||||
@@ -20,6 +22,7 @@
|
||||
severity="secondary"
|
||||
icon="pi pi-expand"
|
||||
v-tooltip.left="t('graphCanvasMenu.fitView')"
|
||||
:aria-label="$t('graphCanvasMenu.fitView')"
|
||||
@click="() => commandStore.execute('Comfy.Canvas.FitView')"
|
||||
/>
|
||||
<Button
|
||||
@@ -30,6 +33,12 @@
|
||||
(canvasStore.canvas?.read_only ? 'panMode' : 'selectMode')
|
||||
) + ' (Space)'
|
||||
"
|
||||
:aria-label="
|
||||
t(
|
||||
'graphCanvasMenu.' +
|
||||
(canvasStore.canvas?.read_only ? 'panMode' : 'selectMode')
|
||||
)
|
||||
"
|
||||
@click="() => commandStore.execute('Comfy.Canvas.ToggleLock')"
|
||||
>
|
||||
<template #icon>
|
||||
@@ -43,6 +52,7 @@
|
||||
severity="secondary"
|
||||
:icon="linkHidden ? 'pi pi-eye-slash' : 'pi pi-eye'"
|
||||
v-tooltip.left="t('graphCanvasMenu.toggleLinkVisibility')"
|
||||
:aria-label="$t('graphCanvasMenu.toggleLinkVisibility')"
|
||||
@click="() => commandStore.execute('Comfy.Canvas.ToggleLinkVisibility')"
|
||||
data-testid="toggle-link-visibility-button"
|
||||
/>
|
||||
|
||||
@@ -59,10 +59,27 @@
|
||||
</h4>
|
||||
<ul class="list-disc pl-6 space-y-1">
|
||||
<li>
|
||||
{{ $t('install.settings.dataCollectionDialog.errorReports') }}
|
||||
{{
|
||||
$t('install.settings.dataCollectionDialog.collect.errorReports')
|
||||
}}
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('install.settings.dataCollectionDialog.systemInfo') }}
|
||||
{{ $t('install.settings.dataCollectionDialog.collect.systemInfo') }}
|
||||
</li>
|
||||
<li>
|
||||
{{
|
||||
$t(
|
||||
'install.settings.dataCollectionDialog.collect.userJourneyEvents'
|
||||
)
|
||||
}}
|
||||
<span
|
||||
class="pi pi-info-circle text-neutral-400"
|
||||
v-tooltip="
|
||||
$t(
|
||||
'install.settings.dataCollectionDialog.collect.userJourneyTooltip'
|
||||
)
|
||||
"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -72,21 +89,29 @@
|
||||
<ul class="list-disc pl-6 space-y-1">
|
||||
<li>
|
||||
{{
|
||||
$t('install.settings.dataCollectionDialog.personalInformation')
|
||||
}}
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('install.settings.dataCollectionDialog.workflowContents') }}
|
||||
</li>
|
||||
<li>
|
||||
{{
|
||||
$t('install.settings.dataCollectionDialog.fileSystemInformation')
|
||||
$t(
|
||||
'install.settings.dataCollectionDialog.doNotCollect.personalInformation'
|
||||
)
|
||||
}}
|
||||
</li>
|
||||
<li>
|
||||
{{
|
||||
$t(
|
||||
'install.settings.dataCollectionDialog.customNodeConfigurations'
|
||||
'install.settings.dataCollectionDialog.doNotCollect.workflowContents'
|
||||
)
|
||||
}}
|
||||
</li>
|
||||
<li>
|
||||
{{
|
||||
$t(
|
||||
'install.settings.dataCollectionDialog.doNotCollect.fileSystemInformation'
|
||||
)
|
||||
}}
|
||||
</li>
|
||||
<li>
|
||||
{{
|
||||
$t(
|
||||
'install.settings.dataCollectionDialog.doNotCollect.customNodeConfigurations'
|
||||
)
|
||||
}}
|
||||
</li>
|
||||
|
||||
@@ -130,12 +130,13 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { TorchDeviceType } from '@comfyorg/comfyui-electron-types'
|
||||
import Tag from 'primevue/tag'
|
||||
import ToggleSwitch from 'primevue/toggleswitch'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { TorchDeviceType, electronAPI } from '@/utils/envUtil'
|
||||
import { electronAPI } from '@/utils/envUtil'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
|
||||
@@ -19,7 +19,13 @@
|
||||
class="filter-button z-10"
|
||||
@click="nodeSearchFilterVisible = true"
|
||||
/>
|
||||
<Dialog v-model:visible="nodeSearchFilterVisible" class="min-w-96">
|
||||
<Dialog
|
||||
v-model:visible="nodeSearchFilterVisible"
|
||||
class="min-w-96"
|
||||
dismissable-mask
|
||||
modal
|
||||
@hide="reFocusInput"
|
||||
>
|
||||
<template #header>
|
||||
<h3>Add node filter condition</h3>
|
||||
</template>
|
||||
@@ -140,7 +146,6 @@ onMounted(reFocusInput)
|
||||
const onAddFilter = (filterAndValue: FilterAndValue) => {
|
||||
nodeSearchFilterVisible.value = false
|
||||
emit('addFilter', filterAndValue)
|
||||
reFocusInput()
|
||||
}
|
||||
const onRemoveFilter = (event: Event, filterAndValue: FilterAndValue) => {
|
||||
event.stopPropagation()
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="showCategory"
|
||||
class="option-category font-light text-sm text-gray-400 overflow-hidden text-ellipsis whitespace-nowrap"
|
||||
class="option-category font-light text-sm text-muted overflow-hidden text-ellipsis whitespace-nowrap"
|
||||
>
|
||||
{{ nodeDef.category.replaceAll('/', ' > ') }}
|
||||
</div>
|
||||
|
||||
@@ -71,11 +71,11 @@ const getTabTooltipSuffix = (tab: SidebarTabExtension) => {
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--sidebar-width: 64px;
|
||||
--sidebar-width: 4rem;
|
||||
--sidebar-icon-size: 1.5rem;
|
||||
}
|
||||
:root .small-sidebar {
|
||||
--sidebar-width: 40px;
|
||||
--sidebar-width: 2.5rem;
|
||||
--sidebar-icon-size: 1rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -11,14 +11,15 @@
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
|
||||
|
||||
import SidebarIcon from './SidebarIcon.vue'
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
const currentTheme = computed(() => settingStore.get('Comfy.ColorPalette'))
|
||||
const colorPaletteStore = useColorPaletteStore()
|
||||
const icon = computed(() =>
|
||||
currentTheme.value !== 'light' ? 'pi pi-moon' : 'pi pi-sun'
|
||||
colorPaletteStore.completedActivePalette.light_theme
|
||||
? 'pi pi-sun'
|
||||
: 'pi pi-moon'
|
||||
)
|
||||
|
||||
const commandStore = useCommandStore()
|
||||
|
||||
@@ -20,6 +20,14 @@
|
||||
@click="alphabeticalSort = !alphabeticalSort"
|
||||
v-tooltip.bottom="$t('sideToolbar.nodeLibraryTab.sortOrder')"
|
||||
/>
|
||||
<Button
|
||||
class="grouping-button"
|
||||
:icon="groupBySource ? 'pi pi-list' : 'pi pi-list-check'"
|
||||
text
|
||||
severity="secondary"
|
||||
@click="groupBySource = !groupBySource"
|
||||
v-tooltip.bottom="$t('sideToolbar.nodeLibraryTab.groupingType')"
|
||||
/>
|
||||
</template>
|
||||
<template #header>
|
||||
<SearchBox
|
||||
@@ -66,7 +74,7 @@ import Button from 'primevue/button'
|
||||
import Divider from 'primevue/divider'
|
||||
import Popover from 'primevue/popover'
|
||||
import type { TreeNode } from 'primevue/treenode'
|
||||
import { Ref, computed, nextTick, ref } from 'vue'
|
||||
import { Ref, computed, nextTick, ref, watch } from 'vue'
|
||||
|
||||
import SearchBox from '@/components/common/SearchBox.vue'
|
||||
import { SearchFilter } from '@/components/common/SearchFilterChip.vue'
|
||||
@@ -101,6 +109,21 @@ const nodeBookmarkTreeExplorerRef = ref<InstanceType<
|
||||
> | null>(null)
|
||||
const searchFilter = ref(null)
|
||||
const alphabeticalSort = ref(false)
|
||||
const groupBySource = ref(false)
|
||||
|
||||
const createSourceKey = (nodeDef: ComfyNodeDefImpl) => {
|
||||
const sourcePath = nodeDef.python_module.split('.')
|
||||
const pathWithoutCategory = nodeDef.nodePath.split('/').slice(1)
|
||||
return [...sourcePath, ...pathWithoutCategory]
|
||||
}
|
||||
|
||||
watch(groupBySource, (newValue) => {
|
||||
if (newValue) {
|
||||
nodeDefStore.setKeyFunction(createSourceKey)
|
||||
} else {
|
||||
nodeDefStore.setKeyFunction(null)
|
||||
}
|
||||
})
|
||||
|
||||
const searchQuery = ref<string>('')
|
||||
|
||||
@@ -194,4 +217,9 @@ const onRemoveFilter = (filterAndValue) => {
|
||||
}
|
||||
handleSearch(searchQuery.value)
|
||||
}
|
||||
|
||||
// This can be added if the persistent state is not desirable:
|
||||
// onBeforeUnmount(() => {
|
||||
// nodeDefStore.setKeyFunction(null)
|
||||
// })
|
||||
</script>
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
:class="props.class"
|
||||
>
|
||||
<div class="comfy-vue-side-bar-header">
|
||||
<Toolbar
|
||||
class="flex-shrink-0 border-x-0 border-t-0 rounded-none px-2 py-1 min-h-8"
|
||||
>
|
||||
<Toolbar class="border-x-0 border-t-0 rounded-none px-2 py-1 min-h-8">
|
||||
<template #start>
|
||||
<span class="text-sm">{{ props.title.toUpperCase() }}</span>
|
||||
<span class="text-xs 2xl:text-sm truncate" :title="props.title">
|
||||
{{ props.title.toUpperCase() }}
|
||||
</span>
|
||||
</template>
|
||||
<template #end>
|
||||
<slot name="tool-buttons"></slot>
|
||||
@@ -37,4 +37,8 @@ const props = defineProps<{
|
||||
:deep(.p-toolbar-end) .p-button {
|
||||
@apply py-1 2xl:py-2;
|
||||
}
|
||||
|
||||
:deep(.p-toolbar-start) {
|
||||
@apply min-w-0 flex-1 overflow-hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
:key="item.url"
|
||||
:src="item.url"
|
||||
:contain="false"
|
||||
:alt="item.filename"
|
||||
class="galleria-image"
|
||||
v-if="item.isImage"
|
||||
/>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
:src="result.url"
|
||||
class="task-output-image"
|
||||
:contain="imageFit === 'contain'"
|
||||
:alt="result.filename"
|
||||
/>
|
||||
<ResultVideo v-else-if="result.isVideo" :result="result" />
|
||||
<div v-else class="task-result-preview">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<template #header>
|
||||
<div class="flex items-center justify-center">
|
||||
<div
|
||||
class="relative overflow-hidden rounded-lg cursor-pointer w-64 h-64"
|
||||
class="relative overflow-hidden rounded-t-lg cursor-pointer w-64 h-64"
|
||||
>
|
||||
<img
|
||||
v-if="!imageError"
|
||||
@@ -13,7 +13,7 @@
|
||||
: `api/workflow_templates/${props.moduleName}/${props.workflowName}.jpg`
|
||||
"
|
||||
@error="imageError = true"
|
||||
class="w-64 h-64 rounded-lg object-cover thumbnail"
|
||||
class="w-64 h-64 rounded-t-lg object-cover thumbnail"
|
||||
/>
|
||||
<div v-else class="w-64 h-64 content-center text-center">
|
||||
<i class="pi pi-file" style="font-size: 4rem"></i>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
:key="selectedTab.moduleName"
|
||||
>
|
||||
<template #item="slotProps">
|
||||
<div @click="loadWorkflow(slotProps.data)">
|
||||
<div @click="loadWorkflow(slotProps.data)" class="p-2">
|
||||
<TemplateWorkflowCard
|
||||
:moduleName="selectedTab.moduleName"
|
||||
:workflowName="slotProps.data"
|
||||
@@ -112,7 +112,7 @@ const loadWorkflow = async (id: string) => {
|
||||
let json
|
||||
if (selectedTab.value.moduleName === 'default') {
|
||||
// Default templates provided by frontend are served on this separate endpoint
|
||||
json = await fetch(api.fileURL(`templates/${id}.json`)).then((r) =>
|
||||
json = await fetch(api.fileURL(`/templates/${id}.json`)).then((r) =>
|
||||
r.json()
|
||||
)
|
||||
} else {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
v-show="bottomPanelStore.bottomPanelTabs.length > 0"
|
||||
severity="secondary"
|
||||
text
|
||||
:aria-label="$t('menu.toggleBottomPanel')"
|
||||
@click="bottomPanelStore.toggleBottomPanel"
|
||||
v-tooltip="{ value: $t('menu.toggleBottomPanel'), showDelay: 300 }"
|
||||
>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<span class="p-menubar-item-label">{{ item.label }}</span>
|
||||
<span
|
||||
v-if="item?.comfyCommand?.keybinding"
|
||||
class="ml-auto border border-surface rounded text-muted text-xs p-1 keybinding-tag"
|
||||
class="ml-auto border border-surface rounded text-muted text-xs text-nowrap p-1 keybinding-tag"
|
||||
>
|
||||
{{ item.comfyCommand.keybinding.combo.toString() }}
|
||||
</span>
|
||||
|
||||
15
src/components/topbar/SecondRowWorkflowTabs.vue
Normal file
15
src/components/topbar/SecondRowWorkflowTabs.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<div class="absolute top-0 left-0 w-auto max-w-full pointer-events-auto">
|
||||
<WorkflowTabs />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import WorkflowTabs from '@/components/topbar/WorkflowTabs.vue'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.workflow-tabs) {
|
||||
background-color: var(--comfy-menu-bg);
|
||||
}
|
||||
</style>
|
||||
@@ -3,34 +3,44 @@
|
||||
<div
|
||||
ref="topMenuRef"
|
||||
class="comfyui-menu flex items-center"
|
||||
v-show="betaMenuEnabled && !workspaceState.focusMode"
|
||||
v-show="showTopMenu"
|
||||
:class="{ dropzone: isDropZone, 'dropzone-active': isDroppable }"
|
||||
>
|
||||
<h1 class="comfyui-logo mx-2">ComfyUI</h1>
|
||||
<h1 class="comfyui-logo mx-2 app-drag">ComfyUI</h1>
|
||||
<CommandMenubar />
|
||||
<Divider layout="vertical" class="mx-2" />
|
||||
<div class="flex-grow">
|
||||
<div class="flex-grow min-w-0 app-drag h-full">
|
||||
<WorkflowTabs v-if="workflowTabsPosition === 'Topbar'" />
|
||||
</div>
|
||||
<div class="comfyui-menu-right" ref="menuRight"></div>
|
||||
<Actionbar />
|
||||
<BottomPanelToggleButton />
|
||||
<BottomPanelToggleButton class="flex-shrink-0" />
|
||||
<Button
|
||||
class="flex-shrink-0"
|
||||
icon="pi pi-bars"
|
||||
severity="secondary"
|
||||
text
|
||||
v-tooltip="{ value: $t('menu.hideMenu'), showDelay: 300 }"
|
||||
:aria-label="$t('menu.hideMenu')"
|
||||
@click="workspaceState.focusMode = true"
|
||||
@contextmenu="showNativeMenu"
|
||||
/>
|
||||
<div
|
||||
v-show="menuSetting !== 'Bottom'"
|
||||
class="window-actions-spacer flex-shrink-0"
|
||||
/>
|
||||
</div>
|
||||
</teleport>
|
||||
|
||||
<!-- Virtual top menu for native window (drag handle) -->
|
||||
<div
|
||||
v-show="isNativeWindow && !showTopMenu"
|
||||
class="fixed top-0 left-0 app-drag w-full h-[var(--comfy-topbar-height)]"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useEventBus } from '@vueuse/core'
|
||||
import Button from 'primevue/button'
|
||||
import Divider from 'primevue/divider'
|
||||
import { computed, onMounted, provide, ref } from 'vue'
|
||||
|
||||
import Actionbar from '@/components/actionbar/ComfyActionbar.vue'
|
||||
@@ -40,21 +50,27 @@ import WorkflowTabs from '@/components/topbar/WorkflowTabs.vue'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
import { showNativeMenu } from '@/utils/envUtil'
|
||||
import { electronAPI, isElectron, showNativeMenu } from '@/utils/envUtil'
|
||||
|
||||
const workspaceState = useWorkspaceStore()
|
||||
const settingStore = useSettingStore()
|
||||
const workflowTabsPosition = computed(() =>
|
||||
settingStore.get('Comfy.Workflow.WorkflowTabsPosition')
|
||||
)
|
||||
const betaMenuEnabled = computed(
|
||||
() => settingStore.get('Comfy.UseNewMenu') !== 'Disabled'
|
||||
)
|
||||
const menuSetting = computed(() => settingStore.get('Comfy.UseNewMenu'))
|
||||
const betaMenuEnabled = computed(() => menuSetting.value !== 'Disabled')
|
||||
const teleportTarget = computed(() =>
|
||||
settingStore.get('Comfy.UseNewMenu') === 'Top'
|
||||
? '.comfyui-body-top'
|
||||
: '.comfyui-body-bottom'
|
||||
)
|
||||
const isNativeWindow = computed(
|
||||
() =>
|
||||
isElectron() && settingStore.get('Comfy-Desktop.WindowStyle') === 'custom'
|
||||
)
|
||||
const showTopMenu = computed(
|
||||
() => betaMenuEnabled.value && !workspaceState.focusMode
|
||||
)
|
||||
|
||||
const menuRight = ref<HTMLDivElement | null>(null)
|
||||
// Menu-right holds legacy topbar elements attached by custom scripts
|
||||
@@ -75,11 +91,20 @@ eventBus.on((event: string, payload: any) => {
|
||||
isDroppable.value = payload.isOverlapping && payload.isDragging
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (isElectron()) {
|
||||
electronAPI().changeTheme({
|
||||
height: topMenuRef.value.getBoundingClientRect().height
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.comfyui-menu {
|
||||
width: 100vw;
|
||||
height: var(--comfy-topbar-height);
|
||||
background: var(--comfy-menu-bg);
|
||||
color: var(--fg-color);
|
||||
box-shadow: var(--bar-shadow);
|
||||
@@ -89,7 +114,6 @@ eventBus.on((event: string, payload: any) => {
|
||||
z-index: 1000;
|
||||
order: 0;
|
||||
grid-column: 1/-1;
|
||||
max-height: 90vh;
|
||||
}
|
||||
|
||||
.comfyui-menu.dropzone {
|
||||
|
||||
@@ -1,34 +1,48 @@
|
||||
<template>
|
||||
<SelectButton
|
||||
class="workflow-tabs bg-transparent inline"
|
||||
:class="props.class"
|
||||
:modelValue="selectedWorkflow"
|
||||
@update:modelValue="onWorkflowChange"
|
||||
:options="options"
|
||||
optionLabel="label"
|
||||
dataKey="value"
|
||||
>
|
||||
<template #option="{ option }">
|
||||
<WorkflowTab
|
||||
@contextmenu="showContextMenu($event, option)"
|
||||
@click.middle="onCloseWorkflow(option)"
|
||||
:workflow-option="option"
|
||||
/>
|
||||
</template>
|
||||
</SelectButton>
|
||||
<Button
|
||||
class="new-blank-workflow-button"
|
||||
icon="pi pi-plus"
|
||||
text
|
||||
severity="secondary"
|
||||
@click="() => commandStore.execute('Comfy.NewBlankWorkflow')"
|
||||
/>
|
||||
<ContextMenu ref="menu" :model="contextMenuItems" />
|
||||
<div class="workflow-tabs-container flex flex-row max-w-full h-full">
|
||||
<ScrollPanel
|
||||
class="overflow-hidden no-drag"
|
||||
:pt:content="{
|
||||
class: 'p-0 w-full',
|
||||
onwheel: handleWheel
|
||||
}"
|
||||
pt:barX="h-1"
|
||||
>
|
||||
<SelectButton
|
||||
class="workflow-tabs bg-transparent"
|
||||
:class="props.class"
|
||||
:modelValue="selectedWorkflow"
|
||||
@update:modelValue="onWorkflowChange"
|
||||
:options="options"
|
||||
optionLabel="label"
|
||||
dataKey="value"
|
||||
>
|
||||
<template #option="{ option }">
|
||||
<WorkflowTab
|
||||
@contextmenu="showContextMenu($event, option)"
|
||||
@click.middle="onCloseWorkflow(option)"
|
||||
:workflow-option="option"
|
||||
/>
|
||||
</template>
|
||||
</SelectButton>
|
||||
</ScrollPanel>
|
||||
<Button
|
||||
v-tooltip="{ value: $t('sideToolbar.newBlankWorkflow'), showDelay: 300 }"
|
||||
class="new-blank-workflow-button flex-shrink-0 no-drag"
|
||||
icon="pi pi-plus"
|
||||
text
|
||||
severity="secondary"
|
||||
:aria-label="$t('sideToolbar.newBlankWorkflow')"
|
||||
@click="() => commandStore.execute('Comfy.NewBlankWorkflow')"
|
||||
/>
|
||||
<ContextMenu ref="menu" :model="contextMenuItems" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Button from 'primevue/button'
|
||||
import ContextMenu from 'primevue/contextmenu'
|
||||
import ScrollPanel from 'primevue/scrollpanel'
|
||||
import SelectButton from 'primevue/selectbutton'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
@@ -36,7 +50,7 @@ import { useI18n } from 'vue-i18n'
|
||||
import WorkflowTab from '@/components/topbar/WorkflowTab.vue'
|
||||
import { useWorkflowService } from '@/services/workflowService'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { ComfyWorkflow } from '@/stores/workflowStore'
|
||||
import { ComfyWorkflow, useWorkflowBookmarkStore } from '@/stores/workflowStore'
|
||||
import { useWorkflowStore } from '@/stores/workflowStore'
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
|
||||
@@ -53,6 +67,7 @@ const { t } = useI18n()
|
||||
const workspaceStore = useWorkspaceStore()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const workflowService = useWorkflowService()
|
||||
const workflowBookmarkStore = useWorkflowBookmarkStore()
|
||||
const rightClickedTab = ref<WorkflowOption>(null)
|
||||
const menu = ref()
|
||||
|
||||
@@ -140,26 +155,56 @@ const contextMenuItems = computed(() => {
|
||||
...options.value.slice(0, index)
|
||||
]),
|
||||
disabled: options.value.length <= 1
|
||||
},
|
||||
{
|
||||
label: workflowBookmarkStore.isBookmarked(tab.workflow.path)
|
||||
? t('tabMenu.removeFromBookmarks')
|
||||
: t('tabMenu.addToBookmarks'),
|
||||
command: () => workflowBookmarkStore.toggleBookmarked(tab.workflow.path),
|
||||
disabled: tab.workflow.isTemporary
|
||||
}
|
||||
]
|
||||
})
|
||||
const commandStore = useCommandStore()
|
||||
|
||||
// Horizontal scroll on wheel
|
||||
const handleWheel = (event: WheelEvent) => {
|
||||
const scrollElement = event.currentTarget as HTMLElement
|
||||
const scrollAmount = event.deltaX || event.deltaY
|
||||
scrollElement.scroll({
|
||||
left: scrollElement.scrollLeft + scrollAmount
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.p-togglebutton) {
|
||||
@apply p-0 bg-transparent rounded-none flex-shrink-0 relative border-0 border-r border-solid;
|
||||
border-right-color: var(--border-color);
|
||||
}
|
||||
|
||||
:deep(.p-togglebutton::before) {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
:deep(.p-togglebutton) {
|
||||
@apply p-0 bg-transparent rounded-none flex-shrink-0 relative;
|
||||
:deep(.p-togglebutton:first-child) {
|
||||
@apply border-l border-solid;
|
||||
border-left-color: var(--border-color);
|
||||
}
|
||||
|
||||
:deep(.p-togglebutton:not(:first-child)) {
|
||||
@apply border-l-0;
|
||||
}
|
||||
|
||||
:deep(.p-togglebutton.p-togglebutton-checked) {
|
||||
@apply border-b-2;
|
||||
@apply border-b border-solid h-full;
|
||||
border-bottom-color: var(--p-button-text-primary-color);
|
||||
}
|
||||
|
||||
:deep(.p-togglebutton:not(.p-togglebutton-checked)) {
|
||||
@apply opacity-75;
|
||||
}
|
||||
|
||||
:deep(.p-togglebutton-checked) .close-button,
|
||||
:deep(.p-togglebutton:hover) .close-button {
|
||||
@apply visible;
|
||||
@@ -172,4 +217,18 @@ const commandStore = useCommandStore()
|
||||
:deep(.p-togglebutton) .close-button {
|
||||
@apply invisible;
|
||||
}
|
||||
|
||||
:deep(.p-scrollpanel-content) {
|
||||
@apply h-full;
|
||||
}
|
||||
|
||||
/* Scrollbar half opacity to avoid blocking the active tab bottom border */
|
||||
:deep(.p-scrollpanel:hover .p-scrollpanel-bar),
|
||||
:deep(.p-scrollpanel:active .p-scrollpanel-bar) {
|
||||
@apply opacity-50;
|
||||
}
|
||||
|
||||
:deep(.p-selectbutton) {
|
||||
@apply rounded-none h-full;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,7 +4,7 @@ import github from '@/assets/palettes/github.json'
|
||||
import light from '@/assets/palettes/light.json'
|
||||
import nord from '@/assets/palettes/nord.json'
|
||||
import solarized from '@/assets/palettes/solarized.json'
|
||||
import type { ColorPalettes } from '@/types/colorPaletteTypes'
|
||||
import type { ColorPalettes, CompletedPalette } from '@/types/colorPaletteTypes'
|
||||
|
||||
export const CORE_COLOR_PALETTES: ColorPalettes = {
|
||||
dark,
|
||||
@@ -15,4 +15,6 @@ export const CORE_COLOR_PALETTES: ColorPalettes = {
|
||||
github
|
||||
} as const
|
||||
|
||||
export const DEFAULT_COLOR_PALETTE = dark
|
||||
export const DEFAULT_COLOR_PALETTE: CompletedPalette = dark
|
||||
export const DEFAULT_DARK_COLOR_PALETTE: CompletedPalette = dark
|
||||
export const DEFAULT_LIGHT_COLOR_PALETTE: CompletedPalette = light
|
||||
|
||||
@@ -95,7 +95,7 @@ export const CORE_KEYBINDINGS: Keybinding[] = [
|
||||
alt: true
|
||||
},
|
||||
commandId: 'Comfy.Canvas.ZoomIn',
|
||||
targetSelector: '#graph-canvas'
|
||||
targetElementId: 'graph-canvas'
|
||||
},
|
||||
{
|
||||
combo: {
|
||||
@@ -104,7 +104,7 @@ export const CORE_KEYBINDINGS: Keybinding[] = [
|
||||
shift: true
|
||||
},
|
||||
commandId: 'Comfy.Canvas.ZoomIn',
|
||||
targetSelector: '#graph-canvas'
|
||||
targetElementId: 'graph-canvas'
|
||||
},
|
||||
// For number pad '+'
|
||||
{
|
||||
@@ -113,7 +113,7 @@ export const CORE_KEYBINDINGS: Keybinding[] = [
|
||||
alt: true
|
||||
},
|
||||
commandId: 'Comfy.Canvas.ZoomIn',
|
||||
targetSelector: '#graph-canvas'
|
||||
targetElementId: 'graph-canvas'
|
||||
},
|
||||
{
|
||||
combo: {
|
||||
@@ -121,21 +121,21 @@ export const CORE_KEYBINDINGS: Keybinding[] = [
|
||||
alt: true
|
||||
},
|
||||
commandId: 'Comfy.Canvas.ZoomOut',
|
||||
targetSelector: '#graph-canvas'
|
||||
targetElementId: 'graph-canvas'
|
||||
},
|
||||
{
|
||||
combo: {
|
||||
key: '.'
|
||||
},
|
||||
commandId: 'Comfy.Canvas.FitView',
|
||||
targetSelector: '#graph-canvas'
|
||||
targetElementId: 'graph-canvas'
|
||||
},
|
||||
{
|
||||
combo: {
|
||||
key: 'p'
|
||||
},
|
||||
commandId: 'Comfy.Canvas.ToggleSelected.Pin',
|
||||
targetSelector: '#graph-canvas'
|
||||
targetElementId: 'graph-canvas'
|
||||
},
|
||||
{
|
||||
combo: {
|
||||
@@ -143,7 +143,7 @@ export const CORE_KEYBINDINGS: Keybinding[] = [
|
||||
alt: true
|
||||
},
|
||||
commandId: 'Comfy.Canvas.ToggleSelectedNodes.Collapse',
|
||||
targetSelector: '#graph-canvas'
|
||||
targetElementId: 'graph-canvas'
|
||||
},
|
||||
{
|
||||
combo: {
|
||||
@@ -151,7 +151,7 @@ export const CORE_KEYBINDINGS: Keybinding[] = [
|
||||
ctrl: true
|
||||
},
|
||||
commandId: 'Comfy.Canvas.ToggleSelectedNodes.Bypass',
|
||||
targetSelector: '#graph-canvas'
|
||||
targetElementId: 'graph-canvas'
|
||||
},
|
||||
{
|
||||
combo: {
|
||||
@@ -159,7 +159,7 @@ export const CORE_KEYBINDINGS: Keybinding[] = [
|
||||
ctrl: true
|
||||
},
|
||||
commandId: 'Comfy.Canvas.ToggleSelectedNodes.Mute',
|
||||
targetSelector: '#graph-canvas'
|
||||
targetElementId: 'graph-canvas'
|
||||
},
|
||||
{
|
||||
combo: {
|
||||
|
||||
@@ -85,7 +85,8 @@ export const CORE_SETTINGS: SettingParams[] = [
|
||||
name: 'Sidebar size',
|
||||
type: 'combo',
|
||||
options: ['normal', 'small'],
|
||||
defaultValue: () => (window.innerWidth < 1600 ? 'small' : 'normal')
|
||||
// Default to small if the window is less than 1536px(2xl) wide.
|
||||
defaultValue: () => (window.innerWidth < 1536 ? 'small' : 'normal')
|
||||
},
|
||||
{
|
||||
id: 'Comfy.TextareaWidget.FontSize',
|
||||
@@ -399,8 +400,10 @@ export const CORE_SETTINGS: SettingParams[] = [
|
||||
id: 'Comfy.Workflow.WorkflowTabsPosition',
|
||||
name: 'Opened workflows position',
|
||||
type: 'combo',
|
||||
options: ['Sidebar', 'Topbar'],
|
||||
defaultValue: 'Topbar'
|
||||
options: ['Sidebar', 'Topbar', 'Topbar (2nd-row)'],
|
||||
// Default to topbar (2nd-row) if the window is less than 1536px(2xl) wide.
|
||||
defaultValue: () =>
|
||||
window.innerWidth < 1536 ? 'Topbar (2nd-row)' : 'Topbar'
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Graph.CanvasMenu',
|
||||
@@ -423,7 +426,16 @@ export const CORE_SETTINGS: SettingParams[] = [
|
||||
name: 'Keybindings unset by the user',
|
||||
type: 'hidden',
|
||||
defaultValue: [] as Keybinding[],
|
||||
versionAdded: '1.3.7'
|
||||
versionAdded: '1.3.7',
|
||||
versionModified: '1.7.3',
|
||||
migrateDeprecatedValue: (value: any[]) => {
|
||||
return value.map((keybinding) => {
|
||||
if (keybinding['targetSelector'] === '#graph-canvas') {
|
||||
keybinding['targetElementId'] = 'graph-canvas'
|
||||
}
|
||||
return keybinding
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Keybinding.NewBindings',
|
||||
|
||||
@@ -30,10 +30,28 @@ import { electronAPI as getElectronAPI, isElectron } from '@/utils/envUtil'
|
||||
{
|
||||
id: 'Comfy-Desktop.SendStatistics',
|
||||
category: ['Comfy-Desktop', 'General', 'Send Statistics'],
|
||||
name: 'Send anonymous crash reports',
|
||||
name: 'Send anonymous usage metrics',
|
||||
type: 'boolean',
|
||||
defaultValue: true,
|
||||
onChange: onChangeRestartApp
|
||||
},
|
||||
{
|
||||
id: 'Comfy-Desktop.WindowStyle',
|
||||
category: ['Comfy-Desktop', 'General', 'Window Style'],
|
||||
name: 'Window Style',
|
||||
tooltip: 'Choose custom option to hide the system title bar',
|
||||
type: 'combo',
|
||||
experimental: true,
|
||||
defaultValue: 'default',
|
||||
options: ['default', 'custom'],
|
||||
onChange: (
|
||||
newValue: 'default' | 'custom',
|
||||
oldValue: 'default' | 'custom'
|
||||
) => {
|
||||
electronAPI.Config.setWindowStyle(newValue)
|
||||
|
||||
onChangeRestartApp(newValue, oldValue)
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import './electronAdapter'
|
||||
import './groupNode'
|
||||
import './groupNodeManage'
|
||||
import './groupOptions'
|
||||
import './invertMenuScrolling'
|
||||
import './load3d'
|
||||
import './maskeditor'
|
||||
import './nodeTemplates'
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
// @ts-strict-ignore
|
||||
import { LiteGraph } from '@comfyorg/litegraph'
|
||||
|
||||
import { app } from '../../scripts/app'
|
||||
|
||||
// Inverts the scrolling of context menus
|
||||
|
||||
const id = 'Comfy.InvertMenuScrolling'
|
||||
app.registerExtension({
|
||||
name: id,
|
||||
init() {
|
||||
const ctxMenu = LiteGraph.ContextMenu
|
||||
const replace = () => {
|
||||
// @ts-expect-error
|
||||
LiteGraph.ContextMenu = function (values, options) {
|
||||
options = options || {}
|
||||
if (options.scroll_speed) {
|
||||
options.scroll_speed *= -1
|
||||
} else {
|
||||
options.scroll_speed = -0.1
|
||||
}
|
||||
return ctxMenu.call(this, values, options)
|
||||
}
|
||||
LiteGraph.ContextMenu.prototype = ctxMenu.prototype
|
||||
}
|
||||
app.ui.settings.addSetting({
|
||||
id,
|
||||
category: ['LiteGraph', 'Menu', 'InvertMenuScrolling'],
|
||||
name: 'Invert Context Menu Scrolling',
|
||||
type: 'boolean',
|
||||
defaultValue: false,
|
||||
onChange(value) {
|
||||
if (value) {
|
||||
replace()
|
||||
} else {
|
||||
LiteGraph.ContextMenu = ctxMenu
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -2,6 +2,7 @@
|
||||
import { IWidget } from '@comfyorg/litegraph'
|
||||
import * as THREE from 'three'
|
||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
|
||||
import { ViewHelper } from 'three/examples/jsm/helpers/ViewHelper'
|
||||
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader'
|
||||
import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
|
||||
import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader'
|
||||
@@ -115,8 +116,7 @@ class Load3d {
|
||||
stlLoader: STLLoader
|
||||
currentModel: THREE.Object3D | null = null
|
||||
originalModel: THREE.Object3D | THREE.BufferGeometry | GLTF | null = null
|
||||
node: any
|
||||
private animationFrameId: number | null = null
|
||||
animationFrameId: number | null = null
|
||||
gridHelper: THREE.GridHelper
|
||||
lights: THREE.Light[] = []
|
||||
clock: THREE.Clock
|
||||
@@ -131,6 +131,10 @@ class Load3d {
|
||||
currentUpDirection: 'original' | '-x' | '+x' | '-y' | '+y' | '-z' | '+z' =
|
||||
'original'
|
||||
originalRotation: THREE.Euler | null = null
|
||||
viewHelper: ViewHelper
|
||||
viewHelperContainer: HTMLDivElement
|
||||
cameraSwitcherContainer: HTMLDivElement
|
||||
gridSwitcherContainer: HTMLDivElement
|
||||
|
||||
constructor(container: Element | HTMLElement) {
|
||||
this.scene = new THREE.Scene()
|
||||
@@ -157,6 +161,7 @@ class Load3d {
|
||||
this.renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true })
|
||||
this.renderer.setSize(300, 300)
|
||||
this.renderer.setClearColor(0x282828)
|
||||
this.renderer.autoClear = false
|
||||
|
||||
const rendererDomElement: HTMLCanvasElement = this.renderer.domElement
|
||||
|
||||
@@ -203,13 +208,143 @@ class Load3d {
|
||||
|
||||
this.standardMaterial = this.createSTLMaterial()
|
||||
|
||||
this.animate()
|
||||
this.createViewHelper(container)
|
||||
|
||||
this.createGridSwitcher(container)
|
||||
|
||||
this.createCameraSwitcher(container)
|
||||
|
||||
this.handleResize()
|
||||
|
||||
this.startAnimation()
|
||||
}
|
||||
|
||||
createViewHelper(container: Element | HTMLElement) {
|
||||
this.viewHelperContainer = document.createElement('div')
|
||||
|
||||
this.viewHelperContainer.style.position = 'absolute'
|
||||
this.viewHelperContainer.style.bottom = '0'
|
||||
this.viewHelperContainer.style.left = '0'
|
||||
this.viewHelperContainer.style.width = '128px'
|
||||
this.viewHelperContainer.style.height = '128px'
|
||||
this.viewHelperContainer.addEventListener('pointerup', (event) => {
|
||||
event.stopPropagation()
|
||||
|
||||
this.viewHelper.handleClick(event)
|
||||
})
|
||||
|
||||
this.viewHelperContainer.addEventListener('pointerdown', (event) => {
|
||||
event.stopPropagation()
|
||||
})
|
||||
|
||||
container.appendChild(this.viewHelperContainer)
|
||||
|
||||
this.viewHelper = new ViewHelper(
|
||||
this.activeCamera,
|
||||
this.viewHelperContainer
|
||||
)
|
||||
|
||||
this.viewHelper.center = this.controls.target
|
||||
}
|
||||
|
||||
createGridSwitcher(container: Element | HTMLElement) {
|
||||
this.gridSwitcherContainer = document.createElement('div')
|
||||
this.gridSwitcherContainer.style.position = 'absolute'
|
||||
this.gridSwitcherContainer.style.top = '28px' // 修改这里,让按钮在相机按钮下方
|
||||
this.gridSwitcherContainer.style.left = '3px' // 与相机按钮左对齐
|
||||
this.gridSwitcherContainer.style.width = '20px'
|
||||
this.gridSwitcherContainer.style.height = '20px'
|
||||
this.gridSwitcherContainer.style.cursor = 'pointer'
|
||||
this.gridSwitcherContainer.style.alignItems = 'center'
|
||||
this.gridSwitcherContainer.style.justifyContent = 'center'
|
||||
this.gridSwitcherContainer.style.transition = 'background-color 0.2s'
|
||||
|
||||
const gridIcon = document.createElement('div')
|
||||
gridIcon.innerHTML = `
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2">
|
||||
<path d="M3 3h18v18H3z"/>
|
||||
<path d="M3 9h18"/>
|
||||
<path d="M3 15h18"/>
|
||||
<path d="M9 3v18"/>
|
||||
<path d="M15 3v18"/>
|
||||
</svg>
|
||||
`
|
||||
|
||||
const updateButtonState = () => {
|
||||
if (this.gridHelper.visible) {
|
||||
this.gridSwitcherContainer.style.backgroundColor =
|
||||
'rgba(255, 255, 255, 0.2)'
|
||||
} else {
|
||||
this.gridSwitcherContainer.style.backgroundColor = 'transparent'
|
||||
}
|
||||
}
|
||||
|
||||
updateButtonState()
|
||||
|
||||
this.gridSwitcherContainer.addEventListener('mouseenter', () => {
|
||||
if (!this.gridHelper.visible) {
|
||||
this.gridSwitcherContainer.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
})
|
||||
|
||||
this.gridSwitcherContainer.addEventListener('mouseleave', () => {
|
||||
if (!this.gridHelper.visible) {
|
||||
this.gridSwitcherContainer.style.backgroundColor = 'transparent'
|
||||
}
|
||||
})
|
||||
|
||||
this.gridSwitcherContainer.title = 'Toggle Grid'
|
||||
|
||||
this.gridSwitcherContainer.addEventListener('click', (event) => {
|
||||
event.stopPropagation()
|
||||
this.toggleGrid(!this.gridHelper.visible)
|
||||
updateButtonState()
|
||||
})
|
||||
|
||||
this.gridSwitcherContainer.appendChild(gridIcon)
|
||||
container.appendChild(this.gridSwitcherContainer)
|
||||
}
|
||||
|
||||
createCameraSwitcher(container: Element | HTMLElement) {
|
||||
this.cameraSwitcherContainer = document.createElement('div')
|
||||
this.cameraSwitcherContainer.style.position = 'absolute'
|
||||
this.cameraSwitcherContainer.style.top = '3px'
|
||||
this.cameraSwitcherContainer.style.left = '3px'
|
||||
this.cameraSwitcherContainer.style.width = '20px'
|
||||
this.cameraSwitcherContainer.style.height = '20px'
|
||||
this.cameraSwitcherContainer.style.cursor = 'pointer'
|
||||
this.cameraSwitcherContainer.style.alignItems = 'center'
|
||||
this.cameraSwitcherContainer.style.justifyContent = 'center'
|
||||
|
||||
const cameraIcon = document.createElement('div')
|
||||
cameraIcon.innerHTML = `
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2">
|
||||
<path d="M18 4H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2Z"/>
|
||||
<path d="m12 12 4-2.4"/>
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
</svg>
|
||||
`
|
||||
this.cameraSwitcherContainer.addEventListener('mouseenter', () => {
|
||||
this.cameraSwitcherContainer.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'
|
||||
})
|
||||
|
||||
this.cameraSwitcherContainer.addEventListener('mouseleave', () => {
|
||||
this.cameraSwitcherContainer.style.backgroundColor = 'rgba(0, 0, 0, 0.3)'
|
||||
})
|
||||
|
||||
this.cameraSwitcherContainer.title =
|
||||
'Switch Camera (Perspective/Orthographic)'
|
||||
|
||||
this.cameraSwitcherContainer.addEventListener('click', (event) => {
|
||||
event.stopPropagation()
|
||||
this.toggleCamera()
|
||||
})
|
||||
|
||||
this.cameraSwitcherContainer.appendChild(cameraIcon)
|
||||
|
||||
container.appendChild(this.cameraSwitcherContainer)
|
||||
}
|
||||
|
||||
setFOV(fov: number) {
|
||||
if (this.activeCamera === this.perspectiveCamera) {
|
||||
this.perspectiveCamera.fov = fov
|
||||
@@ -465,6 +600,13 @@ class Load3d {
|
||||
this.controls.target.copy(target)
|
||||
this.controls.update()
|
||||
|
||||
this.viewHelper.dispose()
|
||||
this.viewHelper = new ViewHelper(
|
||||
this.activeCamera,
|
||||
this.viewHelperContainer
|
||||
)
|
||||
this.viewHelper.center = this.controls.target
|
||||
|
||||
this.handleResize()
|
||||
}
|
||||
|
||||
@@ -501,8 +643,16 @@ class Load3d {
|
||||
startAnimation() {
|
||||
const animate = () => {
|
||||
this.animationFrameId = requestAnimationFrame(animate)
|
||||
const delta = this.clock.getDelta()
|
||||
|
||||
if (this.viewHelper.animating) {
|
||||
this.viewHelper.update(delta)
|
||||
}
|
||||
|
||||
this.renderer.clear()
|
||||
this.controls.update()
|
||||
this.renderer.render(this.scene, this.activeCamera)
|
||||
this.viewHelper.render(this.renderer)
|
||||
}
|
||||
animate()
|
||||
}
|
||||
@@ -588,6 +738,7 @@ class Load3d {
|
||||
}
|
||||
|
||||
this.controls.dispose()
|
||||
this.viewHelper.dispose()
|
||||
this.renderer.dispose()
|
||||
this.renderer.domElement.remove()
|
||||
this.scene.clear()
|
||||
@@ -818,10 +969,12 @@ class Load3d {
|
||||
this.orthographicCamera.updateProjectionMatrix()
|
||||
}
|
||||
|
||||
this.renderer.clear()
|
||||
this.renderer.render(this.scene, this.activeCamera)
|
||||
const sceneData = this.renderer.domElement.toDataURL('image/png')
|
||||
|
||||
this.renderer.setClearColor(0x000000, 0)
|
||||
this.renderer.clear()
|
||||
this.renderer.render(this.scene, this.activeCamera)
|
||||
const maskData = this.renderer.domElement.toDataURL('image/png')
|
||||
|
||||
@@ -846,44 +999,6 @@ class Load3d {
|
||||
})
|
||||
}
|
||||
|
||||
setViewPosition(position: 'front' | 'top' | 'right' | 'isometric') {
|
||||
if (!this.currentModel) {
|
||||
return
|
||||
}
|
||||
|
||||
const box = new THREE.Box3()
|
||||
let center = new THREE.Vector3()
|
||||
let size = new THREE.Vector3()
|
||||
|
||||
if (this.currentModel) {
|
||||
box.setFromObject(this.currentModel)
|
||||
box.getCenter(center)
|
||||
box.getSize(size)
|
||||
}
|
||||
|
||||
const maxDim = Math.max(size.x, size.y, size.z)
|
||||
const distance = maxDim * 2
|
||||
|
||||
switch (position) {
|
||||
case 'front':
|
||||
this.activeCamera.position.set(0, 0, distance)
|
||||
break
|
||||
case 'top':
|
||||
this.activeCamera.position.set(0, distance, 0)
|
||||
break
|
||||
case 'right':
|
||||
this.activeCamera.position.set(distance, 0, 0)
|
||||
break
|
||||
case 'isometric':
|
||||
this.activeCamera.position.set(distance, distance, distance)
|
||||
break
|
||||
}
|
||||
|
||||
this.activeCamera.lookAt(center)
|
||||
this.controls.target.copy(center)
|
||||
this.controls.update()
|
||||
}
|
||||
|
||||
setBackgroundColor(color: string) {
|
||||
this.renderer.setClearColor(new THREE.Color(color))
|
||||
this.renderer.render(this.scene, this.activeCamera)
|
||||
@@ -1020,16 +1135,28 @@ class Load3dAnimation extends Load3d {
|
||||
})
|
||||
}
|
||||
|
||||
animate = () => {
|
||||
requestAnimationFrame(this.animate)
|
||||
|
||||
if (this.currentAnimation && this.isAnimationPlaying) {
|
||||
startAnimation() {
|
||||
const animate = () => {
|
||||
this.animationFrameId = requestAnimationFrame(animate)
|
||||
const delta = this.clock.getDelta()
|
||||
this.currentAnimation.update(delta)
|
||||
}
|
||||
|
||||
this.controls.update()
|
||||
this.renderer.render(this.scene, this.activeCamera)
|
||||
if (this.currentAnimation && this.isAnimationPlaying) {
|
||||
this.currentAnimation.update(delta)
|
||||
}
|
||||
|
||||
this.controls.update()
|
||||
|
||||
this.renderer.clear()
|
||||
|
||||
this.renderer.render(this.scene, this.activeCamera)
|
||||
|
||||
if (this.viewHelper.animating) {
|
||||
this.viewHelper.update(delta)
|
||||
}
|
||||
|
||||
this.viewHelper.render(this.renderer)
|
||||
}
|
||||
animate()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1076,9 +1203,6 @@ function configureLoad3D(
|
||||
load3d: Load3d,
|
||||
loadFolder: 'input' | 'output',
|
||||
modelWidget: IWidget,
|
||||
showGrid: IWidget,
|
||||
cameraType: IWidget,
|
||||
view: IWidget,
|
||||
material: IWidget,
|
||||
bgColor: IWidget,
|
||||
lightIntensity: IWidget,
|
||||
@@ -1138,22 +1262,6 @@ function configureLoad3D(
|
||||
|
||||
modelWidget.callback = onModelWidgetUpdate
|
||||
|
||||
load3d.toggleGrid(showGrid.value as boolean)
|
||||
|
||||
showGrid.callback = (value: boolean) => {
|
||||
load3d.toggleGrid(value)
|
||||
}
|
||||
|
||||
load3d.toggleCamera(cameraType.value as 'perspective' | 'orthographic')
|
||||
|
||||
cameraType.callback = (value: 'perspective' | 'orthographic') => {
|
||||
load3d.toggleCamera(value)
|
||||
}
|
||||
|
||||
view.callback = (value: 'front' | 'top' | 'right' | 'isometric') => {
|
||||
load3d.setViewPosition(value)
|
||||
}
|
||||
|
||||
material.callback = (value: 'original' | 'normal' | 'wireframe') => {
|
||||
load3d.setMaterialMode(value)
|
||||
}
|
||||
@@ -1312,14 +1420,6 @@ app.registerExtension({
|
||||
(w: IWidget) => w.name === 'model_file'
|
||||
)
|
||||
|
||||
const showGrid = node.widgets.find((w: IWidget) => w.name === 'show_grid')
|
||||
|
||||
const cameraType = node.widgets.find(
|
||||
(w: IWidget) => w.name === 'camera_type'
|
||||
)
|
||||
|
||||
const view = node.widgets.find((w: IWidget) => w.name === 'view')
|
||||
|
||||
const material = node.widgets.find((w: IWidget) => w.name === 'material')
|
||||
|
||||
const bgColor = node.widgets.find((w: IWidget) => w.name === 'bg_color')
|
||||
@@ -1353,9 +1453,6 @@ app.registerExtension({
|
||||
load3d,
|
||||
'input',
|
||||
modelWidget,
|
||||
showGrid,
|
||||
cameraType,
|
||||
view,
|
||||
material,
|
||||
bgColor,
|
||||
lightIntensity,
|
||||
@@ -1569,14 +1666,6 @@ app.registerExtension({
|
||||
(w: IWidget) => w.name === 'model_file'
|
||||
)
|
||||
|
||||
const showGrid = node.widgets.find((w: IWidget) => w.name === 'show_grid')
|
||||
|
||||
const cameraType = node.widgets.find(
|
||||
(w: IWidget) => w.name === 'camera_type'
|
||||
)
|
||||
|
||||
const view = node.widgets.find((w: IWidget) => w.name === 'view')
|
||||
|
||||
const material = node.widgets.find((w: IWidget) => w.name === 'material')
|
||||
|
||||
const bgColor = node.widgets.find((w: IWidget) => w.name === 'bg_color')
|
||||
@@ -1621,9 +1710,6 @@ app.registerExtension({
|
||||
load3d,
|
||||
'input',
|
||||
modelWidget,
|
||||
showGrid,
|
||||
cameraType,
|
||||
view,
|
||||
material,
|
||||
bgColor,
|
||||
lightIntensity,
|
||||
@@ -1652,6 +1738,8 @@ app.registerExtension({
|
||||
sceneWidget.serializeValue = async () => {
|
||||
node.properties['Camera Info'] = JSON.stringify(load3d.getCameraState())
|
||||
|
||||
load3d.toggleAnimation(false)
|
||||
|
||||
const { scene: imageData, mask: maskData } = await load3d.captureScene(
|
||||
w.value,
|
||||
h.value
|
||||
@@ -1758,14 +1846,6 @@ app.registerExtension({
|
||||
(w: IWidget) => w.name === 'model_file'
|
||||
)
|
||||
|
||||
const showGrid = node.widgets.find((w: IWidget) => w.name === 'show_grid')
|
||||
|
||||
const cameraType = node.widgets.find(
|
||||
(w: IWidget) => w.name === 'camera_type'
|
||||
)
|
||||
|
||||
const view = node.widgets.find((w: IWidget) => w.name === 'view')
|
||||
|
||||
const material = node.widgets.find((w: IWidget) => w.name === 'material')
|
||||
|
||||
const bgColor = node.widgets.find((w: IWidget) => w.name === 'bg_color')
|
||||
@@ -1801,9 +1881,6 @@ app.registerExtension({
|
||||
load3d,
|
||||
'output',
|
||||
modelWidget,
|
||||
showGrid,
|
||||
cameraType,
|
||||
view,
|
||||
material,
|
||||
bgColor,
|
||||
lightIntensity,
|
||||
|
||||
@@ -1125,7 +1125,7 @@ class MaskEditorDialog extends ComfyDialog {
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
console.log('Failed to upload mask:', response)
|
||||
this.uploadMask(filepath, formData, 2)
|
||||
this.uploadMask(filepath, formData, retries - 1)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
|
||||
@@ -25,11 +25,11 @@ app.registerExtension({
|
||||
if (!this.properties) {
|
||||
this.properties = { text: '' }
|
||||
}
|
||||
ComfyWidgets.MARKDOWN(
|
||||
ComfyWidgets.STRING(
|
||||
// Should we extends LGraphNode? Yesss
|
||||
this,
|
||||
'',
|
||||
['', { default: this.properties.text }],
|
||||
['', { default: this.properties.text, multiline: true }],
|
||||
app
|
||||
)
|
||||
|
||||
@@ -50,5 +50,33 @@ app.registerExtension({
|
||||
)
|
||||
|
||||
NoteNode.category = 'utils'
|
||||
|
||||
/** Markdown variant of NoteNode */
|
||||
class MarkdownNoteNode extends LGraphNode {
|
||||
static title = 'Markdown Note'
|
||||
|
||||
color = LGraphCanvas.node_colors.yellow.color
|
||||
bgcolor = LGraphCanvas.node_colors.yellow.bgcolor
|
||||
groupcolor = LGraphCanvas.node_colors.yellow.groupcolor
|
||||
|
||||
constructor(title?: string) {
|
||||
super(title)
|
||||
if (!this.properties) {
|
||||
this.properties = { text: '' }
|
||||
}
|
||||
ComfyWidgets.MARKDOWN(
|
||||
this,
|
||||
'',
|
||||
['', { default: this.properties.text }],
|
||||
app
|
||||
)
|
||||
|
||||
this.serialize_widgets = true
|
||||
this.isVirtualNode = true
|
||||
}
|
||||
}
|
||||
|
||||
LiteGraph.registerNodeType('MarkdownNote', MarkdownNoteNode)
|
||||
MarkdownNoteNode.category = 'utils'
|
||||
}
|
||||
})
|
||||
|
||||
@@ -14,11 +14,14 @@ export function useTerminal(element: Ref<HTMLElement>) {
|
||||
terminal.loadAddon(fitAddon)
|
||||
|
||||
terminal.attachCustomKeyEventHandler((event) => {
|
||||
if (event.type === 'keydown' && (event.ctrlKey || event.metaKey)) {
|
||||
if (event.key === 'c' || event.key === 'v') {
|
||||
// Allow default browser copy/paste handling
|
||||
return false
|
||||
}
|
||||
// Allow default browser copy/paste handling
|
||||
if (
|
||||
event.type === 'keydown' &&
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
((event.key === 'c' && terminal.hasSelection()) || event.key === 'v')
|
||||
) {
|
||||
// TODO: Deselect text after copy/paste; use IPC.
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
@@ -5,6 +5,10 @@ import {
|
||||
LiteGraph
|
||||
} from '@comfyorg/litegraph'
|
||||
|
||||
import {
|
||||
DEFAULT_DARK_COLOR_PALETTE,
|
||||
DEFAULT_LIGHT_COLOR_PALETTE
|
||||
} from '@/constants/coreColorPalettes'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
@@ -16,6 +20,7 @@ import { useSettingStore } from '@/stores/settingStore'
|
||||
import { useToastStore } from '@/stores/toastStore'
|
||||
import { type ComfyWorkflow, useWorkflowStore } from '@/stores/workflowStore'
|
||||
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
|
||||
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
|
||||
import { useSearchBoxStore } from '@/stores/workspace/searchBoxStore'
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
|
||||
@@ -23,6 +28,7 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
const workflowService = useWorkflowService()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const dialogService = useDialogService()
|
||||
const colorPaletteStore = useColorPaletteStore()
|
||||
const getTracker = () => workflowStore.activeWorkflow?.changeTracker
|
||||
|
||||
const getSelectedNodes = (): LGraphNode[] => {
|
||||
@@ -410,18 +416,18 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
label: 'Toggle Theme (Dark/Light)',
|
||||
versionAdded: '1.3.12',
|
||||
function: (() => {
|
||||
let previousDarkTheme: string = 'dark'
|
||||
let previousDarkTheme: string = DEFAULT_DARK_COLOR_PALETTE.id
|
||||
let previousLightTheme: string = DEFAULT_LIGHT_COLOR_PALETTE.id
|
||||
|
||||
// Official light theme is the only light theme supported now.
|
||||
const isDarkMode = (themeId: string) => themeId !== 'light'
|
||||
return () => {
|
||||
const settingStore = useSettingStore()
|
||||
const currentTheme = settingStore.get('Comfy.ColorPalette')
|
||||
if (isDarkMode(currentTheme)) {
|
||||
previousDarkTheme = currentTheme
|
||||
settingStore.set('Comfy.ColorPalette', 'light')
|
||||
} else {
|
||||
const theme = colorPaletteStore.completedActivePalette
|
||||
if (theme.light_theme) {
|
||||
previousLightTheme = theme.id
|
||||
settingStore.set('Comfy.ColorPalette', previousDarkTheme)
|
||||
} else {
|
||||
previousDarkTheme = theme.id
|
||||
settingStore.set('Comfy.ColorPalette', previousLightTheme)
|
||||
}
|
||||
}
|
||||
})()
|
||||
@@ -522,6 +528,16 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
function: () => {
|
||||
workflowService.duplicateWorkflow(workflowStore.activeWorkflow!)
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'Workspace.CloseWorkflow',
|
||||
icon: 'pi pi-times',
|
||||
label: 'Close Current Workflow',
|
||||
versionAdded: '1.7.3',
|
||||
function: () => {
|
||||
if (workflowStore.activeWorkflow)
|
||||
workflowService.closeWorkflow(workflowStore.activeWorkflow)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -152,6 +152,9 @@
|
||||
"Comfy_Undo": {
|
||||
"label": "Undo"
|
||||
},
|
||||
"Workspace_CloseWorkflow": {
|
||||
"label": "Close Current Workflow"
|
||||
},
|
||||
"Workspace_NextOpenedWorkflow": {
|
||||
"label": "Next Opened Workflow"
|
||||
},
|
||||
|
||||
@@ -68,7 +68,19 @@
|
||||
"disableAll": "Disable All",
|
||||
"command": "Command",
|
||||
"keybinding": "Keybinding",
|
||||
"upload": "Upload"
|
||||
"upload": "Upload",
|
||||
"export": "Export",
|
||||
"workflow": "Workflow"
|
||||
},
|
||||
"issueReport": {
|
||||
"submitErrorReport": "Submit Error Report (Optional)",
|
||||
"provideEmail": "Give us your email (Optional)",
|
||||
"provideAdditionalDetails": "Provide additional details (optional)",
|
||||
"stackTrace": "Stack Trace",
|
||||
"systemStats": "System Stats",
|
||||
"contactFollowUp": "Contact me for follow up",
|
||||
"notifyResolve": "Notify me when resolved",
|
||||
"helpFix": "Help Fix This"
|
||||
},
|
||||
"color": {
|
||||
"default": "Default",
|
||||
@@ -169,21 +181,26 @@
|
||||
},
|
||||
"settings": {
|
||||
"autoUpdate": "Automatic Updates",
|
||||
"allowMetrics": "Crash Reports",
|
||||
"autoUpdateDescription": "Automatically download and install updates when they become available. You'll always be notified before updates are installed.",
|
||||
"allowMetricsDescription": "Help improve ComfyUI by sending anonymous crash reports. No personal information or workflow content will be collected. This can be disabled at any time in the settings menu.",
|
||||
"allowMetrics": "Usage Metrics",
|
||||
"autoUpdateDescription": "Automatically download updates when they become available. You will be notified before updates are installed.",
|
||||
"allowMetricsDescription": "Help improve ComfyUI by sending anonymous usage metrics. No personal information or workflow content will be collected.",
|
||||
"learnMoreAboutData": "Learn more about data collection",
|
||||
"dataCollectionDialog": {
|
||||
"title": "About Data Collection",
|
||||
"whatWeCollect": "What we collect:",
|
||||
"whatWeDoNotCollect": "What we don't collect:",
|
||||
"errorReports": "Error message and stack trace",
|
||||
"systemInfo": "Hardware, OS type, and app version",
|
||||
"personalInformation": "Personal information",
|
||||
"workflowContent": "Workflow content",
|
||||
"fileSystemInformation": "File system information",
|
||||
"workflowContents": "Workflow contents",
|
||||
"customNodeConfigurations": "Custom node configurations"
|
||||
"collect": {
|
||||
"errorReports": "Error message and stack trace",
|
||||
"systemInfo": "Hardware, OS type, and app version",
|
||||
"userJourneyEvents": "User journey events",
|
||||
"userJourneyTooltip": "User journey events are used to track the user's journey through the app installation process. The event collection ends on the first successful ComfyUI workflow run."
|
||||
},
|
||||
"doNotCollect": {
|
||||
"personalInformation": "Personal information",
|
||||
"fileSystemInformation": "File system information",
|
||||
"workflowContents": "Workflow contents",
|
||||
"customNodeConfigurations": "Custom node configurations"
|
||||
}
|
||||
}
|
||||
},
|
||||
"customNodes": "Custom Nodes",
|
||||
@@ -217,7 +234,8 @@
|
||||
"openWorkflow": "Open workflow in local file system",
|
||||
"newBlankWorkflow": "Create a new blank workflow",
|
||||
"nodeLibraryTab": {
|
||||
"sortOrder": "Sort Order"
|
||||
"sortOrder": "Sort Order",
|
||||
"groupingType": "Grouping Type"
|
||||
},
|
||||
"modelLibrary": "Model Library",
|
||||
"downloads": "Downloads",
|
||||
@@ -278,7 +296,9 @@
|
||||
"closeTab": "Close Tab",
|
||||
"closeTabsToLeft": "Close Tabs to Left",
|
||||
"closeTabsToRight": "Close Tabs to Right",
|
||||
"closeOtherTabs": "Close Other Tabs"
|
||||
"closeOtherTabs": "Close Other Tabs",
|
||||
"addToBookmarks": "Add to Bookmarks",
|
||||
"removeFromBookmarks": "Remove from Bookmarks"
|
||||
},
|
||||
"templateWorkflows": {
|
||||
"title": "Get Started with a Template",
|
||||
@@ -374,6 +394,7 @@
|
||||
"Show Settings Dialog": "Show Settings Dialog",
|
||||
"Toggle Theme (Dark/Light)": "Toggle Theme (Dark/Light)",
|
||||
"Undo": "Undo",
|
||||
"Close Current Workflow": "Close Current Workflow",
|
||||
"Next Opened Workflow": "Next Opened Workflow",
|
||||
"Previous Opened Workflow": "Previous Opened Workflow",
|
||||
"Toggle Search Box": "Toggle Search Box",
|
||||
|
||||
@@ -184,6 +184,9 @@
|
||||
},
|
||||
"type": {
|
||||
"name": "type"
|
||||
},
|
||||
"device": {
|
||||
"name": "device"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1100,6 +1103,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"DevToolsNodeWithSeedInput": {
|
||||
"display_name": "Node With Seed Input",
|
||||
"description": "A node with a seed input",
|
||||
"inputs": {
|
||||
"seed": {
|
||||
"name": "seed"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DevToolsNodeWithStringInput": {
|
||||
"display_name": "Node With String Input",
|
||||
"description": "A node with a string input",
|
||||
@@ -1124,6 +1136,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"DevToolsObjectPatchNode": {
|
||||
"display_name": "Object Patch Node",
|
||||
"description": "A node that applies an object patch",
|
||||
"inputs": {
|
||||
"model": {
|
||||
"name": "model"
|
||||
},
|
||||
"target_module": {
|
||||
"name": "target_module"
|
||||
},
|
||||
"dummy_float": {
|
||||
"name": "dummy_float"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DevToolsSimpleSlider": {
|
||||
"display_name": "Simple Slider",
|
||||
"inputs": {
|
||||
@@ -1197,6 +1224,9 @@
|
||||
},
|
||||
"type": {
|
||||
"name": "type"
|
||||
},
|
||||
"device": {
|
||||
"name": "device"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2293,6 +2323,9 @@
|
||||
},
|
||||
"up_direction": {
|
||||
"name": "up_direction"
|
||||
},
|
||||
"fov": {
|
||||
"name": "fov"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
@@ -2345,6 +2378,9 @@
|
||||
},
|
||||
"animation_speed": {
|
||||
"name": "animation_speed"
|
||||
},
|
||||
"fov": {
|
||||
"name": "fov"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
@@ -4249,6 +4285,9 @@
|
||||
},
|
||||
"up_direction": {
|
||||
"name": "up_direction"
|
||||
},
|
||||
"fov": {
|
||||
"name": "fov"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -3,7 +3,15 @@
|
||||
"name": "Automatically check for updates"
|
||||
},
|
||||
"Comfy-Desktop_SendStatistics": {
|
||||
"name": "Send anonymous crash reports"
|
||||
"name": "Send anonymous usage metrics"
|
||||
},
|
||||
"Comfy-Desktop_WindowStyle": {
|
||||
"name": "Window Style",
|
||||
"tooltip": "Choose custom option to hide the system title bar",
|
||||
"options": {
|
||||
"default": "default",
|
||||
"custom": "custom"
|
||||
}
|
||||
},
|
||||
"Comfy_ConfirmClear": {
|
||||
"name": "Require confirmation when clearing workflow"
|
||||
@@ -60,9 +68,6 @@
|
||||
"Comfy_GroupSelectedNodes_Padding": {
|
||||
"name": "Group selected nodes padding"
|
||||
},
|
||||
"Comfy_InvertMenuScrolling": {
|
||||
"name": "Invert Context Menu Scrolling"
|
||||
},
|
||||
"Comfy_LinkRelease_Action": {
|
||||
"name": "Action on link release (No modifier)",
|
||||
"options": {
|
||||
@@ -296,7 +301,8 @@
|
||||
"name": "Opened workflows position",
|
||||
"options": {
|
||||
"Sidebar": "Sidebar",
|
||||
"Topbar": "Topbar"
|
||||
"Topbar": "Topbar",
|
||||
"Topbar (2nd-row)": "Topbar (2nd-row)"
|
||||
}
|
||||
},
|
||||
"LiteGraph_Canvas_MaximumFps": {
|
||||
|
||||
@@ -152,6 +152,9 @@
|
||||
"Comfy_Undo": {
|
||||
"label": "Annuler"
|
||||
},
|
||||
"Workspace_CloseWorkflow": {
|
||||
"label": "Fermer le flux de travail actuel"
|
||||
},
|
||||
"Workspace_NextOpenedWorkflow": {
|
||||
"label": "Flux de travail ouvert suivant"
|
||||
},
|
||||
|
||||
@@ -85,6 +85,7 @@
|
||||
"enableAll": "Activer tout",
|
||||
"error": "Erreur",
|
||||
"experimental": "BETA",
|
||||
"export": "Exportation",
|
||||
"extensionName": "Nom de l'extension",
|
||||
"findIssues": "Trouver des problèmes",
|
||||
"firstTimeUIMessage": "C'est la première fois que vous utilisez la nouvelle interface utilisateur. Choisissez \"Menu > Utiliser le nouveau menu > Désactivé\" pour restaurer l'ancienne interface utilisateur.",
|
||||
@@ -131,7 +132,8 @@
|
||||
"systemInfo": "Informations système",
|
||||
"terminal": "Terminal",
|
||||
"upload": "Téléverser",
|
||||
"videoFailedToLoad": "Échec du chargement de la vidéo"
|
||||
"videoFailedToLoad": "Échec du chargement de la vidéo",
|
||||
"workflow": "Flux de travail"
|
||||
},
|
||||
"graphCanvasMenu": {
|
||||
"fitView": "Adapter la vue",
|
||||
@@ -204,27 +206,42 @@
|
||||
"pathValidationFailed": "Échec de la validation du chemin",
|
||||
"selectItemsToMigrate": "Sélectionnez les éléments à migrer",
|
||||
"settings": {
|
||||
"allowMetrics": "Rapports de plantage",
|
||||
"allowMetricsDescription": "Aidez à améliorer ComfyUI en envoyant des rapports de plantage anonymes. Aucune information personnelle ou contenu de flux de travail ne sera collecté. Cela peut être désactivé à tout moment dans le menu des paramètres.",
|
||||
"allowMetrics": "Métriques d'utilisation",
|
||||
"allowMetricsDescription": "Aidez à améliorer ComfyUI en envoyant des métriques d'utilisation anonymes. Aucune information personnelle ou contenu de flux de travail ne sera collecté.",
|
||||
"autoUpdate": "Mises à jour automatiques",
|
||||
"autoUpdateDescription": "Téléchargez et installez automatiquement les mises à jour lorsqu'elles deviennent disponibles. Vous serez toujours informé avant l'installation des mises à jour.",
|
||||
"dataCollectionDialog": {
|
||||
"customNodeConfigurations": "Configurations de nœuds personnalisés",
|
||||
"errorReports": "Message d'erreur et trace de la pile",
|
||||
"fileSystemInformation": "Informations sur le système de fichiers",
|
||||
"personalInformation": "Informations personnelles",
|
||||
"systemInfo": "Matériel, type d'OS et version de l'application",
|
||||
"collect": {
|
||||
"errorReports": "Message d'erreur et trace de la pile",
|
||||
"systemInfo": "Matériel, type de système d'exploitation et version de l'application",
|
||||
"userJourneyEvents": "Événements du parcours utilisateur",
|
||||
"userJourneyTooltip": "Les événements du parcours utilisateur sont utilisés pour suivre le parcours de l'utilisateur lors du processus d'installation de l'application. La collecte d'événements se termine lors de la première exécution réussie du flux de travail ComfyUI."
|
||||
},
|
||||
"doNotCollect": {
|
||||
"customNodeConfigurations": "Configurations de nœud personnalisées",
|
||||
"fileSystemInformation": "Informations sur le système de fichiers",
|
||||
"personalInformation": "Informations personnelles",
|
||||
"workflowContents": "Contenus du flux de travail"
|
||||
},
|
||||
"title": "À propos de la collecte de données",
|
||||
"whatWeCollect": "Ce que nous collectons :",
|
||||
"whatWeDoNotCollect": "Ce que nous ne collectons pas :",
|
||||
"workflowContent": "Contenu du flux de travail",
|
||||
"workflowContents": "Contenus du flux de travail"
|
||||
"whatWeDoNotCollect": "Ce que nous ne collectons pas :"
|
||||
},
|
||||
"learnMoreAboutData": "En savoir plus sur la collecte de données"
|
||||
},
|
||||
"systemLocations": "Emplacements système",
|
||||
"unhandledError": "Erreur inconnue"
|
||||
},
|
||||
"issueReport": {
|
||||
"contactFollowUp": "Contactez-moi pour un suivi",
|
||||
"helpFix": "Aidez à résoudre cela",
|
||||
"notifyResolve": "Prévenez-moi lorsque résolu",
|
||||
"provideAdditionalDetails": "Fournir des détails supplémentaires (facultatif)",
|
||||
"provideEmail": "Donnez-nous votre email (Facultatif)",
|
||||
"stackTrace": "Trace de la pile",
|
||||
"submitErrorReport": "Soumettre un rapport d'erreur (Facultatif)",
|
||||
"systemStats": "Statistiques du système"
|
||||
},
|
||||
"menu": {
|
||||
"autoQueue": "File d'attente automatique",
|
||||
"batchCount": "Nombre de lots",
|
||||
@@ -257,6 +274,7 @@
|
||||
"Clear Pending Tasks": "Effacer les tâches en attente",
|
||||
"Clear Workflow": "Effacer le flux de travail",
|
||||
"Clipspace": "Espace de clip",
|
||||
"Close Current Workflow": "Fermer le flux de travail actuel",
|
||||
"Collapse/Expand Selected Nodes": "Réduire/Étendre les nœuds sélectionnés",
|
||||
"Comfy-Org Discord": "Discord de Comfy-Org",
|
||||
"ComfyUI Docs": "Docs de ComfyUI",
|
||||
@@ -621,11 +639,13 @@
|
||||
"workflows": "Flux de travail"
|
||||
},
|
||||
"tabMenu": {
|
||||
"addToBookmarks": "Ajouter aux Favoris",
|
||||
"closeOtherTabs": "Fermer les autres onglets",
|
||||
"closeTab": "Fermer l'onglet",
|
||||
"closeTabsToLeft": "Fermer les onglets à gauche",
|
||||
"closeTabsToRight": "Fermer les onglets à droite",
|
||||
"duplicateTab": "Dupliquer l'onglet"
|
||||
"duplicateTab": "Dupliquer l'onglet",
|
||||
"removeFromBookmarks": "Retirer des Favoris"
|
||||
},
|
||||
"templateWorkflows": {
|
||||
"template": {
|
||||
|
||||
@@ -119,6 +119,9 @@
|
||||
"clip_name": {
|
||||
"name": "clip_name"
|
||||
},
|
||||
"device": {
|
||||
"name": "appareil"
|
||||
},
|
||||
"type": {
|
||||
"name": "type"
|
||||
}
|
||||
@@ -1100,6 +1103,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"DevToolsNodeWithSeedInput": {
|
||||
"description": "Un nœud avec une entrée de graine",
|
||||
"display_name": "Nœud Avec Entrée de Graine",
|
||||
"inputs": {
|
||||
"seed": {
|
||||
"name": "graine"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DevToolsNodeWithStringInput": {
|
||||
"description": "Un nœud avec une entrée de chaîne",
|
||||
"display_name": "Nœud avec entrée de chaîne",
|
||||
@@ -1124,6 +1136,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"DevToolsObjectPatchNode": {
|
||||
"description": "Un nœud qui applique un patch d'objet",
|
||||
"display_name": "Nœud de Patch d'Objet",
|
||||
"inputs": {
|
||||
"dummy_float": {
|
||||
"name": "flottant_factice"
|
||||
},
|
||||
"model": {
|
||||
"name": "modèle"
|
||||
},
|
||||
"target_module": {
|
||||
"name": "module_cible"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DevToolsSimpleSlider": {
|
||||
"display_name": "Curseur simple",
|
||||
"inputs": {
|
||||
@@ -1195,6 +1222,9 @@
|
||||
"clip_name2": {
|
||||
"name": "nom_clip2"
|
||||
},
|
||||
"device": {
|
||||
"name": "appareil"
|
||||
},
|
||||
"type": {
|
||||
"name": "type"
|
||||
}
|
||||
@@ -2358,6 +2388,9 @@
|
||||
"camera_type": {
|
||||
"name": "type_de_caméra"
|
||||
},
|
||||
"fov": {
|
||||
"name": "fov"
|
||||
},
|
||||
"height": {
|
||||
"name": "hauteur"
|
||||
},
|
||||
@@ -2410,6 +2443,9 @@
|
||||
"camera_type": {
|
||||
"name": "type_de_caméra"
|
||||
},
|
||||
"fov": {
|
||||
"name": "fov"
|
||||
},
|
||||
"height": {
|
||||
"name": "hauteur"
|
||||
},
|
||||
@@ -4232,6 +4268,9 @@
|
||||
"camera_type": {
|
||||
"name": "type_de_camera"
|
||||
},
|
||||
"fov": {
|
||||
"name": "fov"
|
||||
},
|
||||
"light_intensity": {
|
||||
"name": "intensité_de_lumière"
|
||||
},
|
||||
|
||||
@@ -3,7 +3,15 @@
|
||||
"name": "Vérifier automatiquement les mises à jour"
|
||||
},
|
||||
"Comfy-Desktop_SendStatistics": {
|
||||
"name": "Envoyer des rapports de plantage anonymes"
|
||||
"name": "Envoyer des métriques d'utilisation anonymes"
|
||||
},
|
||||
"Comfy-Desktop_WindowStyle": {
|
||||
"name": "Style de fenêtre",
|
||||
"options": {
|
||||
"custom": "personnalisé",
|
||||
"default": "défaut"
|
||||
},
|
||||
"tooltip": "Choisissez l'option personnalisée pour masquer la barre de titre du système"
|
||||
},
|
||||
"Comfy_ConfirmClear": {
|
||||
"name": "Demander une confirmation lors de l'effacement du flux de travail"
|
||||
@@ -60,9 +68,6 @@
|
||||
"Comfy_Group_DoubleClickTitleToEdit": {
|
||||
"name": "Double-cliquer sur le titre du groupe pour le modifier"
|
||||
},
|
||||
"Comfy_InvertMenuScrolling": {
|
||||
"name": "Inverser le défilement du menu contextuel"
|
||||
},
|
||||
"Comfy_LinkRelease_Action": {
|
||||
"name": "Action lors du relâchement du lien (sans modificateur)",
|
||||
"options": {
|
||||
@@ -296,7 +301,8 @@
|
||||
"name": "Position des flux de travail ouverts",
|
||||
"options": {
|
||||
"Sidebar": "Barre latérale",
|
||||
"Topbar": "Barre supérieure"
|
||||
"Topbar": "Barre supérieure",
|
||||
"Topbar (2nd-row)": "Barre supérieure (2ème rangée)"
|
||||
}
|
||||
},
|
||||
"LiteGraph_Canvas_MaximumFps": {
|
||||
|
||||
@@ -152,6 +152,9 @@
|
||||
"Comfy_Undo": {
|
||||
"label": "元に戻す"
|
||||
},
|
||||
"Workspace_CloseWorkflow": {
|
||||
"label": "現在のワークフローを閉じる"
|
||||
},
|
||||
"Workspace_NextOpenedWorkflow": {
|
||||
"label": "次の開いたワークフロー"
|
||||
},
|
||||
|
||||
@@ -85,6 +85,7 @@
|
||||
"enableAll": "すべて有効にする",
|
||||
"error": "エラー",
|
||||
"experimental": "ベータ",
|
||||
"export": "エクスポート",
|
||||
"extensionName": "拡張機能名",
|
||||
"findIssues": "問題を見つける",
|
||||
"firstTimeUIMessage": "新しいUIを初めて使用しています。「メニュー > 新しいメニューを使用 > 無効」を選択して古いUIに戻してください。",
|
||||
@@ -131,7 +132,8 @@
|
||||
"systemInfo": "システム情報",
|
||||
"terminal": "ターミナル",
|
||||
"upload": "アップロード",
|
||||
"videoFailedToLoad": "ビデオの読み込みに失敗しました"
|
||||
"videoFailedToLoad": "ビデオの読み込みに失敗しました",
|
||||
"workflow": "ワークフロー"
|
||||
},
|
||||
"graphCanvasMenu": {
|
||||
"fitView": "ビューに合わせる",
|
||||
@@ -204,27 +206,42 @@
|
||||
"pathValidationFailed": "パスの検証に失敗しました",
|
||||
"selectItemsToMigrate": "移行する項目を選択",
|
||||
"settings": {
|
||||
"allowMetrics": "クラッシュレポート",
|
||||
"allowMetricsDescription": "ComfyUIの改善に協力してください。匿名のクラッシュレポートを送信します。個人情報やワークフロー内容は収集されません。この設定はいつでも無効にできます。",
|
||||
"allowMetrics": "使用状況のメトリクス",
|
||||
"allowMetricsDescription": "匿名の使用状況メトリクスを送信してComfyUIを改善します。個人情報やワークフローの内容は収集されません。",
|
||||
"autoUpdate": "自動更新",
|
||||
"autoUpdateDescription": "更新が利用可能になると、自動的にダウンロードおよびインストールを行います。インストール前に通知が表示されます。",
|
||||
"dataCollectionDialog": {
|
||||
"customNodeConfigurations": "カスタムノード設定",
|
||||
"errorReports": "エラーメッセージとスタックトレース",
|
||||
"fileSystemInformation": "ファイルシステム情報",
|
||||
"personalInformation": "個人情報",
|
||||
"systemInfo": "ハードウェア、OSの種類、アプリのバージョン",
|
||||
"collect": {
|
||||
"errorReports": "エラーメッセージとスタックトレース",
|
||||
"systemInfo": "ハードウェア、OSタイプ、アプリバージョン",
|
||||
"userJourneyEvents": "ユーザージャーニーイベント",
|
||||
"userJourneyTooltip": "ユーザージャーニーイベントは、アプリのインストールプロセスを通じてユーザーの旅を追跡するために使用されます。イベントの収集は、最初の成功したComfyUIワークフローの実行で終了します。"
|
||||
},
|
||||
"doNotCollect": {
|
||||
"customNodeConfigurations": "カスタムノードの設定",
|
||||
"fileSystemInformation": "ファイルシステム情報",
|
||||
"personalInformation": "個人情報",
|
||||
"workflowContents": "ワークフローの内容"
|
||||
},
|
||||
"title": "データ収集について",
|
||||
"whatWeCollect": "収集内容:",
|
||||
"whatWeDoNotCollect": "収集しない内容:",
|
||||
"workflowContent": "ワークフロー内容",
|
||||
"workflowContents": "ワークフロー内容"
|
||||
"whatWeDoNotCollect": "収集しない内容:"
|
||||
},
|
||||
"learnMoreAboutData": "データ収集の詳細を見る"
|
||||
},
|
||||
"systemLocations": "システムの場所",
|
||||
"unhandledError": "未知のエラー"
|
||||
},
|
||||
"issueReport": {
|
||||
"contactFollowUp": "フォローアップのために私に連絡する",
|
||||
"helpFix": "これを修正するのを助ける",
|
||||
"notifyResolve": "解決したときに通知する",
|
||||
"provideAdditionalDetails": "追加の詳細を提供する(オプション)",
|
||||
"provideEmail": "あなたのメールアドレスを教えてください(オプション)",
|
||||
"stackTrace": "スタックトレース",
|
||||
"submitErrorReport": "エラーレポートを提出する(オプション)",
|
||||
"systemStats": "システム統計"
|
||||
},
|
||||
"menu": {
|
||||
"autoQueue": "自動キュー",
|
||||
"batchCount": "バッチ数",
|
||||
@@ -257,6 +274,7 @@
|
||||
"Clear Pending Tasks": "保留中のタスクをクリア",
|
||||
"Clear Workflow": "ワークフローをクリア",
|
||||
"Clipspace": "クリップスペース",
|
||||
"Close Current Workflow": "現在のワークフローを閉じる",
|
||||
"Collapse/Expand Selected Nodes": "選択したノードの折りたたみ/展開",
|
||||
"Comfy-Org Discord": "Comfy-Org Discord",
|
||||
"ComfyUI Docs": "ComfyUIのドキュメント",
|
||||
@@ -621,11 +639,13 @@
|
||||
"workflows": "ワークフロー"
|
||||
},
|
||||
"tabMenu": {
|
||||
"addToBookmarks": "ブックマークに追加",
|
||||
"closeOtherTabs": "他のタブを閉じる",
|
||||
"closeTab": "タブを閉じる",
|
||||
"closeTabsToLeft": "左のタブを閉じる",
|
||||
"closeTabsToRight": "右のタブを閉じる",
|
||||
"duplicateTab": "タブを複製"
|
||||
"duplicateTab": "タブを複製",
|
||||
"removeFromBookmarks": "ブックマークから削除"
|
||||
},
|
||||
"templateWorkflows": {
|
||||
"template": {
|
||||
|
||||
@@ -119,6 +119,9 @@
|
||||
"clip_name": {
|
||||
"name": "clip名"
|
||||
},
|
||||
"device": {
|
||||
"name": "デバイス"
|
||||
},
|
||||
"type": {
|
||||
"name": "タイプ"
|
||||
}
|
||||
@@ -1100,6 +1103,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"DevToolsNodeWithSeedInput": {
|
||||
"description": "シード入力付きのノード",
|
||||
"display_name": "シード入力付きノード",
|
||||
"inputs": {
|
||||
"seed": {
|
||||
"name": "シード"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DevToolsNodeWithStringInput": {
|
||||
"description": "文字列入力を持つノード",
|
||||
"display_name": "文字列入力ノード",
|
||||
@@ -1124,6 +1136,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"DevToolsObjectPatchNode": {
|
||||
"description": "オブジェクトパッチを適用するノード",
|
||||
"display_name": "オブジェクトパッチノード",
|
||||
"inputs": {
|
||||
"dummy_float": {
|
||||
"name": "ダミーフロート"
|
||||
},
|
||||
"model": {
|
||||
"name": "モデル"
|
||||
},
|
||||
"target_module": {
|
||||
"name": "ターゲットモジュール"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DevToolsSimpleSlider": {
|
||||
"display_name": "シンプルスライダー",
|
||||
"inputs": {
|
||||
@@ -1195,6 +1222,9 @@
|
||||
"clip_name2": {
|
||||
"name": "clip_name2"
|
||||
},
|
||||
"device": {
|
||||
"name": "デバイス"
|
||||
},
|
||||
"type": {
|
||||
"name": "タイプ"
|
||||
}
|
||||
@@ -2358,6 +2388,9 @@
|
||||
"camera_type": {
|
||||
"name": "カメラタイプ"
|
||||
},
|
||||
"fov": {
|
||||
"name": "fov"
|
||||
},
|
||||
"height": {
|
||||
"name": "高さ"
|
||||
},
|
||||
@@ -2410,6 +2443,9 @@
|
||||
"camera_type": {
|
||||
"name": "カメラタイプ"
|
||||
},
|
||||
"fov": {
|
||||
"name": "fov"
|
||||
},
|
||||
"height": {
|
||||
"name": "高さ"
|
||||
},
|
||||
@@ -4232,6 +4268,9 @@
|
||||
"camera_type": {
|
||||
"name": "カメラタイプ"
|
||||
},
|
||||
"fov": {
|
||||
"name": "fov"
|
||||
},
|
||||
"light_intensity": {
|
||||
"name": "光の強度"
|
||||
},
|
||||
|
||||
@@ -3,7 +3,15 @@
|
||||
"name": "自動的に更新を確認する"
|
||||
},
|
||||
"Comfy-Desktop_SendStatistics": {
|
||||
"name": "匿名のクラッシュレポートを送信する"
|
||||
"name": "匿名の使用統計を送信する"
|
||||
},
|
||||
"Comfy-Desktop_WindowStyle": {
|
||||
"name": "ウィンドウスタイル",
|
||||
"options": {
|
||||
"custom": "カスタム",
|
||||
"default": "デフォルト"
|
||||
},
|
||||
"tooltip": "システムタイトルバーを非表示にするにはカスタムオプションを選択してください"
|
||||
},
|
||||
"Comfy_ConfirmClear": {
|
||||
"name": "ワークフローをクリアする際に確認を要求する"
|
||||
@@ -60,9 +68,6 @@
|
||||
"Comfy_Group_DoubleClickTitleToEdit": {
|
||||
"name": "グループタイトルをダブルクリックして編集"
|
||||
},
|
||||
"Comfy_InvertMenuScrolling": {
|
||||
"name": "コンテキストメニューのスクロールを反転する"
|
||||
},
|
||||
"Comfy_LinkRelease_Action": {
|
||||
"name": "リンクリリース時のアクション(修飾子なし)",
|
||||
"options": {
|
||||
@@ -296,7 +301,8 @@
|
||||
"name": "開いているワークフローの位置",
|
||||
"options": {
|
||||
"Sidebar": "サイドバー",
|
||||
"Topbar": "トップバー"
|
||||
"Topbar": "トップバー",
|
||||
"Topbar (2nd-row)": "トップバー(2行目)"
|
||||
}
|
||||
},
|
||||
"LiteGraph_Canvas_MaximumFps": {
|
||||
|
||||
@@ -152,6 +152,9 @@
|
||||
"Comfy_Undo": {
|
||||
"label": "실행 취소"
|
||||
},
|
||||
"Workspace_CloseWorkflow": {
|
||||
"label": "현재 워크플로우 닫기"
|
||||
},
|
||||
"Workspace_NextOpenedWorkflow": {
|
||||
"label": "다음 열린 워크플로"
|
||||
},
|
||||
|
||||
@@ -85,6 +85,7 @@
|
||||
"enableAll": "모두 활성화",
|
||||
"error": "오류",
|
||||
"experimental": "베타",
|
||||
"export": "내보내기",
|
||||
"extensionName": "확장 이름",
|
||||
"findIssues": "문제 찾기",
|
||||
"firstTimeUIMessage": "새 UI를 처음 사용합니다. \"메뉴 > 새 메뉴 사용 > 비활성화\"를 선택하여 이전 UI로 복원하세요.",
|
||||
@@ -131,7 +132,8 @@
|
||||
"systemInfo": "시스템 정보",
|
||||
"terminal": "터미널",
|
||||
"upload": "업로드",
|
||||
"videoFailedToLoad": "비디오를 로드하지 못했습니다."
|
||||
"videoFailedToLoad": "비디오를 로드하지 못했습니다.",
|
||||
"workflow": "워크플로우"
|
||||
},
|
||||
"graphCanvasMenu": {
|
||||
"fitView": "보기 맞춤",
|
||||
@@ -204,27 +206,42 @@
|
||||
"pathValidationFailed": "경로 유효성 검사 실패",
|
||||
"selectItemsToMigrate": "마이그레이션 항목 선택",
|
||||
"settings": {
|
||||
"allowMetrics": "충돌 보고서",
|
||||
"allowMetricsDescription": "익명의 충돌 보고서를 보내 ComfyUI 개선에 도움을 줍니다. 개인 정보나 워크플로 내용은 수집되지 않습니다. 이는 설정 메뉴에서 언제든지 비활성화할 수 있습니다.",
|
||||
"allowMetrics": "사용 통계",
|
||||
"allowMetricsDescription": "익명의 사용 통계를 보내 ComfyUI를 개선하는 데 도움을 줍니다. 개인 정보나 워크플로우 내용은 수집되지 않습니다.",
|
||||
"autoUpdate": "자동 업데이트",
|
||||
"autoUpdateDescription": "업데이트가 가능해지면 자동으로 다운로드하고 설치합니다. 업데이트가 설치되기 전에 항상 알림을 받습니다.",
|
||||
"dataCollectionDialog": {
|
||||
"customNodeConfigurations": "사용자 정의 노드 설정",
|
||||
"errorReports": "오류 메시지 및 스택 추적",
|
||||
"fileSystemInformation": "파일 시스템 정보",
|
||||
"personalInformation": "개인 정보",
|
||||
"systemInfo": "하드웨어, OS 유형 및 앱 버전",
|
||||
"collect": {
|
||||
"errorReports": "오류 메시지 및 스택 추적",
|
||||
"systemInfo": "하드웨어, OS 유형, 앱 버전",
|
||||
"userJourneyEvents": "사용자 여정 이벤트",
|
||||
"userJourneyTooltip": "사용자 여정 이벤트는 앱 설치 과정을 통한 사용자의 여정을 추적하는 데 사용됩니다. 이벤트 수집은 첫 번째 성공적인 ComfyUI 워크플로우 실행에서 종료됩니다."
|
||||
},
|
||||
"doNotCollect": {
|
||||
"customNodeConfigurations": "사용자 정의 노드 구성",
|
||||
"fileSystemInformation": "파일 시스템 정보",
|
||||
"personalInformation": "개인 정보",
|
||||
"workflowContents": "워크플로우 내용"
|
||||
},
|
||||
"title": "데이터 수집 안내",
|
||||
"whatWeCollect": "수집하는 정보:",
|
||||
"whatWeDoNotCollect": "수집하지 않는 정보:",
|
||||
"workflowContent": "워크플로 내용",
|
||||
"workflowContents": "워크플로 내용"
|
||||
"whatWeDoNotCollect": "수집하지 않는 정보:"
|
||||
},
|
||||
"learnMoreAboutData": "데이터 수집에 대해 더 알아보기"
|
||||
},
|
||||
"systemLocations": "시스템 위치",
|
||||
"unhandledError": "알 수 없는 오류"
|
||||
},
|
||||
"issueReport": {
|
||||
"contactFollowUp": "추적 조사를 위해 연락해 주세요",
|
||||
"helpFix": "이 문제 해결에 도움을 주세요",
|
||||
"notifyResolve": "해결되었을 때 알려주세요",
|
||||
"provideAdditionalDetails": "추가 세부 사항 제공 (선택 사항)",
|
||||
"provideEmail": "이메일을 알려주세요 (선택 사항)",
|
||||
"stackTrace": "스택 추적",
|
||||
"submitErrorReport": "오류 보고서 제출 (선택 사항)",
|
||||
"systemStats": "시스템 통계"
|
||||
},
|
||||
"menu": {
|
||||
"autoQueue": "자동 실행 큐",
|
||||
"batchCount": "배치 수",
|
||||
@@ -257,6 +274,7 @@
|
||||
"Clear Pending Tasks": "보류 중인 작업 제거하기",
|
||||
"Clear Workflow": "워크플로 지우기",
|
||||
"Clipspace": "클립스페이스",
|
||||
"Close Current Workflow": "현재 워크플로우 닫기",
|
||||
"Collapse/Expand Selected Nodes": "선택한 노드 축소/확장",
|
||||
"Comfy-Org Discord": "Comfy-Org 디스코드",
|
||||
"ComfyUI Docs": "ComfyUI 문서",
|
||||
@@ -621,11 +639,13 @@
|
||||
"workflows": "워크플로"
|
||||
},
|
||||
"tabMenu": {
|
||||
"addToBookmarks": "북마크에 추가",
|
||||
"closeOtherTabs": "다른 탭 닫기",
|
||||
"closeTab": "탭 닫기",
|
||||
"closeTabsToLeft": "왼쪽 탭 닫기",
|
||||
"closeTabsToRight": "오른쪽 탭 닫기",
|
||||
"duplicateTab": "탭 복제"
|
||||
"duplicateTab": "탭 복제",
|
||||
"removeFromBookmarks": "북마크에서 제거"
|
||||
},
|
||||
"templateWorkflows": {
|
||||
"template": {
|
||||
|
||||
@@ -119,6 +119,9 @@
|
||||
"clip_name": {
|
||||
"name": "CLIP 파일명"
|
||||
},
|
||||
"device": {
|
||||
"name": "장치"
|
||||
},
|
||||
"type": {
|
||||
"name": "유형"
|
||||
}
|
||||
@@ -1100,6 +1103,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"DevToolsNodeWithSeedInput": {
|
||||
"description": "시드 입력이 있는 노드",
|
||||
"display_name": "시드 입력이 있는 노드",
|
||||
"inputs": {
|
||||
"seed": {
|
||||
"name": "시드"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DevToolsNodeWithStringInput": {
|
||||
"description": "문자열 입력이 있는 노드",
|
||||
"display_name": "문자열 입력이 있는 노드",
|
||||
@@ -1124,6 +1136,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"DevToolsObjectPatchNode": {
|
||||
"description": "오브젝트 패치를 적용하는 노드",
|
||||
"display_name": "오브젝트 패치 노드",
|
||||
"inputs": {
|
||||
"dummy_float": {
|
||||
"name": "더미 플로트"
|
||||
},
|
||||
"model": {
|
||||
"name": "모델"
|
||||
},
|
||||
"target_module": {
|
||||
"name": "대상 모듈"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DevToolsSimpleSlider": {
|
||||
"display_name": "간단한 슬라이더",
|
||||
"inputs": {
|
||||
@@ -1195,6 +1222,9 @@
|
||||
"clip_name2": {
|
||||
"name": "CLIP 파일명2"
|
||||
},
|
||||
"device": {
|
||||
"name": "장치"
|
||||
},
|
||||
"type": {
|
||||
"name": "유형"
|
||||
}
|
||||
@@ -2242,7 +2272,7 @@
|
||||
"name": "길이"
|
||||
},
|
||||
"samples": {
|
||||
"name": "샘플"
|
||||
"name": "잠재 데이터"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2358,6 +2388,9 @@
|
||||
"camera_type": {
|
||||
"name": "카메라 유형"
|
||||
},
|
||||
"fov": {
|
||||
"name": "시야"
|
||||
},
|
||||
"height": {
|
||||
"name": "높이"
|
||||
},
|
||||
@@ -2410,6 +2443,9 @@
|
||||
"camera_type": {
|
||||
"name": "카메라 유형"
|
||||
},
|
||||
"fov": {
|
||||
"name": "시야"
|
||||
},
|
||||
"height": {
|
||||
"name": "높이"
|
||||
},
|
||||
@@ -4232,6 +4268,9 @@
|
||||
"camera_type": {
|
||||
"name": "카메라 유형"
|
||||
},
|
||||
"fov": {
|
||||
"name": "시야"
|
||||
},
|
||||
"light_intensity": {
|
||||
"name": "조명 강도"
|
||||
},
|
||||
@@ -5253,7 +5292,7 @@
|
||||
"display_name": "VAE 디코드",
|
||||
"inputs": {
|
||||
"samples": {
|
||||
"name": "샘플",
|
||||
"name": "잠재 데이터",
|
||||
"tooltip": "디코딩할 잠재입니다."
|
||||
},
|
||||
"vae": {
|
||||
|
||||
@@ -3,7 +3,15 @@
|
||||
"name": "자동 업데이트 확인"
|
||||
},
|
||||
"Comfy-Desktop_SendStatistics": {
|
||||
"name": "익명으로 충돌 보고서 전송"
|
||||
"name": "익명 사용 통계 보내기"
|
||||
},
|
||||
"Comfy-Desktop_WindowStyle": {
|
||||
"name": "창 스타일",
|
||||
"options": {
|
||||
"custom": "사용자 정의",
|
||||
"default": "기본"
|
||||
},
|
||||
"tooltip": "시스템 제목 표시 줄을 숨기려면 사용자 정의 옵션을 선택하세요"
|
||||
},
|
||||
"Comfy_ConfirmClear": {
|
||||
"name": "워크플로 비우기 시 확인 요구"
|
||||
@@ -60,9 +68,6 @@
|
||||
"Comfy_Group_DoubleClickTitleToEdit": {
|
||||
"name": "그룹 제목을 두 번 클릭하여 편집"
|
||||
},
|
||||
"Comfy_InvertMenuScrolling": {
|
||||
"name": "컨텍스트 메뉴 스크롤 반전"
|
||||
},
|
||||
"Comfy_LinkRelease_Action": {
|
||||
"name": "링크 해제 시 동작 (수정자 없음)",
|
||||
"options": {
|
||||
@@ -296,7 +301,8 @@
|
||||
"name": "열린 워크플로 위치",
|
||||
"options": {
|
||||
"Sidebar": "사이드바",
|
||||
"Topbar": "상단바"
|
||||
"Topbar": "상단바",
|
||||
"Topbar (2nd-row)": "상단바 (2번째 행)"
|
||||
}
|
||||
},
|
||||
"LiteGraph_Canvas_MaximumFps": {
|
||||
|
||||
@@ -152,6 +152,9 @@
|
||||
"Comfy_Undo": {
|
||||
"label": "Отменить"
|
||||
},
|
||||
"Workspace_CloseWorkflow": {
|
||||
"label": "Закрыть текущий рабочий процесс"
|
||||
},
|
||||
"Workspace_NextOpenedWorkflow": {
|
||||
"label": "Следующий открытый рабочий процесс"
|
||||
},
|
||||
|
||||
@@ -85,6 +85,7 @@
|
||||
"enableAll": "Включить все",
|
||||
"error": "Ошибка",
|
||||
"experimental": "БЕТА",
|
||||
"export": "Экспорт",
|
||||
"extensionName": "Имя расширения",
|
||||
"findIssues": "Найти проблемы",
|
||||
"firstTimeUIMessage": "Вы впервые используете новый интерфейс. Выберите \"Меню > Использовать новое меню > Отключено\", чтобы восстановить старый интерфейс.",
|
||||
@@ -131,7 +132,8 @@
|
||||
"systemInfo": "Информация о системе",
|
||||
"terminal": "Терминал",
|
||||
"upload": "Загрузить",
|
||||
"videoFailedToLoad": "Не удалось загрузить видео"
|
||||
"videoFailedToLoad": "Не удалось загрузить видео",
|
||||
"workflow": "Рабочий процесс"
|
||||
},
|
||||
"graphCanvasMenu": {
|
||||
"fitView": "Подгонять под выделенные",
|
||||
@@ -204,27 +206,42 @@
|
||||
"pathValidationFailed": "Не удалось проверить путь",
|
||||
"selectItemsToMigrate": "Выберите элементы для миграции",
|
||||
"settings": {
|
||||
"allowMetrics": "Отчеты о сбоях",
|
||||
"allowMetricsDescription": "Помогите улучшить ComfyUI, отправляя анонимные отчеты о сбоях. Личная информация или содержимое рабочего процесса не будут собираться. Это можно отключить в любое время в меню настроек.",
|
||||
"allowMetrics": "Метрики использования",
|
||||
"allowMetricsDescription": "Помогите улучшить ComfyUI, отправляя анонимные метрики использования. Личная информация или содержание рабочего процесса не будут собираться.",
|
||||
"autoUpdate": "Автоматические обновления",
|
||||
"autoUpdateDescription": "Автоматически загружать и устанавливать обновления, когда они становятся доступными. Вы всегда будете уведомлены перед установкой обновлений.",
|
||||
"dataCollectionDialog": {
|
||||
"customNodeConfigurations": "Конфигурации пользовательских узлов",
|
||||
"errorReports": "Сообщения об ошибках и трассировка стека",
|
||||
"fileSystemInformation": "Информация о файловой системе",
|
||||
"personalInformation": "Личная информация",
|
||||
"systemInfo": "Аппаратное обеспечение, тип ОС и версия приложения",
|
||||
"collect": {
|
||||
"errorReports": "Сообщение об ошибке и трассировка стека",
|
||||
"systemInfo": "Аппаратное обеспечение, тип ОС и версия приложения",
|
||||
"userJourneyEvents": "События пользовательского пути",
|
||||
"userJourneyTooltip": "События пользовательского пути используются для отслеживания пути пользователя в процессе установки приложения. Сбор событий заканчивается после первого успешного запуска рабочего процесса ComfyUI."
|
||||
},
|
||||
"doNotCollect": {
|
||||
"customNodeConfigurations": "Пользовательские конфигурации узлов",
|
||||
"fileSystemInformation": "Информация о файловой системе",
|
||||
"personalInformation": "Личная информация",
|
||||
"workflowContents": "Содержание рабочего процесса"
|
||||
},
|
||||
"title": "О сборе данных",
|
||||
"whatWeCollect": "Что мы собираем:",
|
||||
"whatWeDoNotCollect": "Что мы не собираем:",
|
||||
"workflowContent": "Содержимое рабочего процесса",
|
||||
"workflowContents": "Содержимое рабочего процесса"
|
||||
"whatWeDoNotCollect": "Что мы не собираем:"
|
||||
},
|
||||
"learnMoreAboutData": "Узнать больше о сборе данных"
|
||||
},
|
||||
"systemLocations": "Системные места",
|
||||
"unhandledError": "Неизвестная ошибка"
|
||||
},
|
||||
"issueReport": {
|
||||
"contactFollowUp": "Свяжитесь со мной для уточнения",
|
||||
"helpFix": "Помочь исправить это",
|
||||
"notifyResolve": "Уведомить меня, когда проблема будет решена",
|
||||
"provideAdditionalDetails": "Предоставьте дополнительные сведения (необязательно)",
|
||||
"provideEmail": "Укажите вашу электронную почту (необязательно)",
|
||||
"stackTrace": "Трассировка стека",
|
||||
"submitErrorReport": "Отправить отчет об ошибке (необязательно)",
|
||||
"systemStats": "Статистика системы"
|
||||
},
|
||||
"menu": {
|
||||
"autoQueue": "Автоочередь",
|
||||
"batchCount": "Количество пакетов",
|
||||
@@ -257,6 +274,7 @@
|
||||
"Clear Pending Tasks": "Очистить ожидающие задачи",
|
||||
"Clear Workflow": "Очистить рабочий процесс",
|
||||
"Clipspace": "Клиппространство",
|
||||
"Close Current Workflow": "Закрыть текущий рабочий процесс",
|
||||
"Collapse/Expand Selected Nodes": "Свернуть/развернуть выбранные узлы",
|
||||
"Comfy-Org Discord": "Discord Comfy-Org",
|
||||
"ComfyUI Docs": "Документация ComfyUI",
|
||||
@@ -621,11 +639,13 @@
|
||||
"workflows": "Рабочие процессы"
|
||||
},
|
||||
"tabMenu": {
|
||||
"addToBookmarks": "Добавить в закладки",
|
||||
"closeOtherTabs": "Закрыть другие вкладки",
|
||||
"closeTab": "Закрыть вкладку",
|
||||
"closeTabsToLeft": "Закрыть вкладки слева",
|
||||
"closeTabsToRight": "Закрыть вкладки справа",
|
||||
"duplicateTab": "Дублировать вкладку"
|
||||
"duplicateTab": "Дублировать вкладку",
|
||||
"removeFromBookmarks": "Удалить из закладок"
|
||||
},
|
||||
"templateWorkflows": {
|
||||
"template": {
|
||||
|
||||
@@ -119,6 +119,9 @@
|
||||
"clip_name": {
|
||||
"name": "имя_clip"
|
||||
},
|
||||
"device": {
|
||||
"name": "устройство"
|
||||
},
|
||||
"type": {
|
||||
"name": "тип"
|
||||
}
|
||||
@@ -1100,6 +1103,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"DevToolsNodeWithSeedInput": {
|
||||
"description": "Узел с входным сигналом seed",
|
||||
"display_name": "Узел с входным сигналом Seed",
|
||||
"inputs": {
|
||||
"seed": {
|
||||
"name": "seed"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DevToolsNodeWithStringInput": {
|
||||
"description": "Узел со строковым вводом",
|
||||
"display_name": "Узел со строковым вводом",
|
||||
@@ -1124,6 +1136,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"DevToolsObjectPatchNode": {
|
||||
"description": "Узел, применяющий объектный патч",
|
||||
"display_name": "Узел объектного патча",
|
||||
"inputs": {
|
||||
"dummy_float": {
|
||||
"name": "фиктивное число с плавающей точкой"
|
||||
},
|
||||
"model": {
|
||||
"name": "модель"
|
||||
},
|
||||
"target_module": {
|
||||
"name": "целевой модуль"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DevToolsSimpleSlider": {
|
||||
"display_name": "Простой Слайдер",
|
||||
"inputs": {
|
||||
@@ -1195,6 +1222,9 @@
|
||||
"clip_name2": {
|
||||
"name": "clip_name2"
|
||||
},
|
||||
"device": {
|
||||
"name": "устройство"
|
||||
},
|
||||
"type": {
|
||||
"name": "тип"
|
||||
}
|
||||
@@ -2358,6 +2388,9 @@
|
||||
"camera_type": {
|
||||
"name": "тип_камеры"
|
||||
},
|
||||
"fov": {
|
||||
"name": "поле зрения"
|
||||
},
|
||||
"height": {
|
||||
"name": "высота"
|
||||
},
|
||||
@@ -2410,6 +2443,9 @@
|
||||
"camera_type": {
|
||||
"name": "тип_камеры"
|
||||
},
|
||||
"fov": {
|
||||
"name": "поле зрения"
|
||||
},
|
||||
"height": {
|
||||
"name": "высота"
|
||||
},
|
||||
@@ -4232,6 +4268,9 @@
|
||||
"camera_type": {
|
||||
"name": "тип_камеры"
|
||||
},
|
||||
"fov": {
|
||||
"name": "поле зрения"
|
||||
},
|
||||
"light_intensity": {
|
||||
"name": "интенсивность_света"
|
||||
},
|
||||
|
||||
@@ -3,7 +3,15 @@
|
||||
"name": "Автоматически проверять обновления"
|
||||
},
|
||||
"Comfy-Desktop_SendStatistics": {
|
||||
"name": "Отправлять анонимные отчеты о сбоях"
|
||||
"name": "Отправлять анонимную статистику использования"
|
||||
},
|
||||
"Comfy-Desktop_WindowStyle": {
|
||||
"name": "Стиль окна",
|
||||
"options": {
|
||||
"custom": "пользовательский",
|
||||
"default": "по умолчанию"
|
||||
},
|
||||
"tooltip": "Выберите пользовательский вариант, чтобы скрыть системную строку заголовка"
|
||||
},
|
||||
"Comfy_ConfirmClear": {
|
||||
"name": "Требовать подтверждение при очистке рабочего процесса"
|
||||
@@ -60,9 +68,6 @@
|
||||
"Comfy_Group_DoubleClickTitleToEdit": {
|
||||
"name": "Дважды щелкните по заголовку группы, чтобы редактировать"
|
||||
},
|
||||
"Comfy_InvertMenuScrolling": {
|
||||
"name": "Инвертировать прокрутку контекстного меню"
|
||||
},
|
||||
"Comfy_LinkRelease_Action": {
|
||||
"name": "Действие при отпускании ссылки (без модификатора)",
|
||||
"options": {
|
||||
@@ -296,7 +301,8 @@
|
||||
"name": "Положение открытых рабочих процессов",
|
||||
"options": {
|
||||
"Sidebar": "Боковая панель",
|
||||
"Topbar": "Верхняя панель"
|
||||
"Topbar": "Верхняя панель",
|
||||
"Topbar (2nd-row)": "Топбар (2-й ряд)"
|
||||
}
|
||||
},
|
||||
"LiteGraph_Canvas_MaximumFps": {
|
||||
|
||||
@@ -152,6 +152,9 @@
|
||||
"Comfy_Undo": {
|
||||
"label": "撤销"
|
||||
},
|
||||
"Workspace_CloseWorkflow": {
|
||||
"label": "关闭当前工作流"
|
||||
},
|
||||
"Workspace_NextOpenedWorkflow": {
|
||||
"label": "下一个打开的工作流"
|
||||
},
|
||||
|
||||
@@ -85,6 +85,7 @@
|
||||
"enableAll": "启用全部",
|
||||
"error": "错误",
|
||||
"experimental": "测试版",
|
||||
"export": "导出",
|
||||
"extensionName": "扩展名称",
|
||||
"findIssues": "查找问题",
|
||||
"firstTimeUIMessage": "这是您第一次使用新界面。选择 \"菜单 > 使用新菜单 > 禁用\" 来恢复旧界面。",
|
||||
@@ -131,7 +132,8 @@
|
||||
"systemInfo": "系统信息",
|
||||
"terminal": "终端",
|
||||
"upload": "上传",
|
||||
"videoFailedToLoad": "视频加载失败"
|
||||
"videoFailedToLoad": "视频加载失败",
|
||||
"workflow": "工作流"
|
||||
},
|
||||
"graphCanvasMenu": {
|
||||
"fitView": "适应视图",
|
||||
@@ -204,27 +206,42 @@
|
||||
"pathValidationFailed": "路径验证失败",
|
||||
"selectItemsToMigrate": "选择要迁移的项目",
|
||||
"settings": {
|
||||
"allowMetrics": "崩溃报告",
|
||||
"allowMetricsDescription": "发送匿名崩溃报告帮助改善 ComfyUI。报告不会收集任何个人信息或工作流内容。您可以随时在设置菜单中禁用此功能。",
|
||||
"allowMetrics": "使用情况指标",
|
||||
"allowMetricsDescription": "通过发送匿名使用情况指标来帮助改进ComfyUI。不会收集任何个人信息或工作流内容。",
|
||||
"autoUpdate": "自动更新",
|
||||
"autoUpdateDescription": "更新可用时自动更新。您将在安装更新之前收到通知。",
|
||||
"dataCollectionDialog": {
|
||||
"customNodeConfigurations": "自定义节点配置",
|
||||
"errorReports": "错误信息和堆栈跟踪",
|
||||
"fileSystemInformation": "文件系统信息",
|
||||
"personalInformation": "个人信息",
|
||||
"systemInfo": "硬件、操作系统类型和应用版本",
|
||||
"collect": {
|
||||
"errorReports": "错误报告和堆栈跟踪",
|
||||
"systemInfo": "硬件,操作系统类型和应用版本",
|
||||
"userJourneyEvents": "用户旅程事件",
|
||||
"userJourneyTooltip": "用户旅程事件用于跟踪用户通过应用安装过程的旅程。事件收集在第一次成功运行ComfyUI工作流后结束。"
|
||||
},
|
||||
"doNotCollect": {
|
||||
"customNodeConfigurations": "自定义节点配置",
|
||||
"fileSystemInformation": "文件系统信息",
|
||||
"personalInformation": "个人信息",
|
||||
"workflowContents": "工作流内容"
|
||||
},
|
||||
"title": "关于数据收集",
|
||||
"whatWeCollect": "我们收集的内容:",
|
||||
"whatWeDoNotCollect": "我们不收集的内容:",
|
||||
"workflowContent": "工作流内容",
|
||||
"workflowContents": "工作流内容"
|
||||
"whatWeDoNotCollect": "我们不收集的内容:"
|
||||
},
|
||||
"learnMoreAboutData": "了解更多关于数据收集的信息"
|
||||
},
|
||||
"systemLocations": "系统位置",
|
||||
"unhandledError": "未知错误"
|
||||
},
|
||||
"issueReport": {
|
||||
"contactFollowUp": "跟进联系我",
|
||||
"helpFix": "帮助修复这个",
|
||||
"notifyResolve": "解决时通知我",
|
||||
"provideAdditionalDetails": "提供额外的详细信息(可选)",
|
||||
"provideEmail": "提供您的电子邮件(可选)",
|
||||
"stackTrace": "堆栈跟踪",
|
||||
"submitErrorReport": "提交错误报告(可选)",
|
||||
"systemStats": "系统状态"
|
||||
},
|
||||
"menu": {
|
||||
"autoQueue": "自动执行",
|
||||
"batchCount": "批次数量",
|
||||
@@ -257,6 +274,7 @@
|
||||
"Clear Pending Tasks": "清除待处理任务",
|
||||
"Clear Workflow": "清除工作流",
|
||||
"Clipspace": "剪贴空间",
|
||||
"Close Current Workflow": "关闭当前工作流",
|
||||
"Collapse/Expand Selected Nodes": "折叠/展开选定节点",
|
||||
"Comfy-Org Discord": "Comfy-Org Discord",
|
||||
"ComfyUI Docs": "ComfyUI 文档",
|
||||
@@ -621,11 +639,13 @@
|
||||
"workflows": "工作流"
|
||||
},
|
||||
"tabMenu": {
|
||||
"addToBookmarks": "添加到书签",
|
||||
"closeOtherTabs": "关闭其他标签",
|
||||
"closeTab": "关闭标签",
|
||||
"closeTabsToLeft": "关闭左侧标签",
|
||||
"closeTabsToRight": "关闭右侧标签",
|
||||
"duplicateTab": "复制标签"
|
||||
"duplicateTab": "复制标签",
|
||||
"removeFromBookmarks": "从书签中移除"
|
||||
},
|
||||
"templateWorkflows": {
|
||||
"template": {
|
||||
|
||||
@@ -119,6 +119,9 @@
|
||||
"clip_name": {
|
||||
"name": "CLIP名称"
|
||||
},
|
||||
"device": {
|
||||
"name": "设备"
|
||||
},
|
||||
"type": {
|
||||
"name": "类型"
|
||||
}
|
||||
@@ -1100,6 +1103,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"DevToolsNodeWithSeedInput": {
|
||||
"description": "一个带有种子输入的节点",
|
||||
"display_name": "带种子输入的节点",
|
||||
"inputs": {
|
||||
"seed": {
|
||||
"name": "种子"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DevToolsNodeWithStringInput": {
|
||||
"description": "一个具有字符串输入的节点",
|
||||
"display_name": "具有字符串输入的节点",
|
||||
@@ -1124,6 +1136,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"DevToolsObjectPatchNode": {
|
||||
"description": "一个应用对象补丁的节点",
|
||||
"display_name": "对象补丁节点",
|
||||
"inputs": {
|
||||
"dummy_float": {
|
||||
"name": "虚拟浮点数"
|
||||
},
|
||||
"model": {
|
||||
"name": "模型"
|
||||
},
|
||||
"target_module": {
|
||||
"name": "目标模块"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DevToolsSimpleSlider": {
|
||||
"display_name": "简单滑块",
|
||||
"inputs": {
|
||||
@@ -1195,6 +1222,9 @@
|
||||
"clip_name2": {
|
||||
"name": "CLIP名称2"
|
||||
},
|
||||
"device": {
|
||||
"name": "设备"
|
||||
},
|
||||
"type": {
|
||||
"name": "类型"
|
||||
}
|
||||
@@ -2358,6 +2388,9 @@
|
||||
"camera_type": {
|
||||
"name": "相机类型"
|
||||
},
|
||||
"fov": {
|
||||
"name": "视场"
|
||||
},
|
||||
"height": {
|
||||
"name": "高度"
|
||||
},
|
||||
@@ -2410,6 +2443,9 @@
|
||||
"camera_type": {
|
||||
"name": "相机类型"
|
||||
},
|
||||
"fov": {
|
||||
"name": "视场"
|
||||
},
|
||||
"height": {
|
||||
"name": "高度"
|
||||
},
|
||||
@@ -4232,6 +4268,9 @@
|
||||
"camera_type": {
|
||||
"name": "相机类型"
|
||||
},
|
||||
"fov": {
|
||||
"name": "视场"
|
||||
},
|
||||
"light_intensity": {
|
||||
"name": "光照强度"
|
||||
},
|
||||
|
||||
@@ -3,7 +3,15 @@
|
||||
"name": "自动检查更新"
|
||||
},
|
||||
"Comfy-Desktop_SendStatistics": {
|
||||
"name": "发送匿名崩溃报告"
|
||||
"name": "发送匿名使用情况统计"
|
||||
},
|
||||
"Comfy-Desktop_WindowStyle": {
|
||||
"name": "窗口样式",
|
||||
"options": {
|
||||
"custom": "自定义",
|
||||
"default": "默认"
|
||||
},
|
||||
"tooltip": "选择自定义选项以隐藏系统标题栏"
|
||||
},
|
||||
"Comfy_ConfirmClear": {
|
||||
"name": "清除工作流时需要确认"
|
||||
@@ -60,9 +68,6 @@
|
||||
"Comfy_Group_DoubleClickTitleToEdit": {
|
||||
"name": "双击组标题以编辑"
|
||||
},
|
||||
"Comfy_InvertMenuScrolling": {
|
||||
"name": "反转上下文菜单滚动"
|
||||
},
|
||||
"Comfy_LinkRelease_Action": {
|
||||
"name": "释放连线时的操作",
|
||||
"options": {
|
||||
@@ -296,7 +301,8 @@
|
||||
"name": "已打开工作流的位置",
|
||||
"options": {
|
||||
"Sidebar": "侧边栏",
|
||||
"Topbar": "顶部栏"
|
||||
"Topbar": "顶部栏",
|
||||
"Topbar (2nd-row)": "顶部栏 (第二行)"
|
||||
}
|
||||
},
|
||||
"LiteGraph_Canvas_MaximumFps": {
|
||||
|
||||
12
src/main.ts
12
src/main.ts
@@ -2,6 +2,7 @@
|
||||
import '@comfyorg/litegraph/style.css'
|
||||
import { definePreset } from '@primevue/themes'
|
||||
import Aura from '@primevue/themes/aura'
|
||||
import * as Sentry from '@sentry/vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import 'primeicons/primeicons.css'
|
||||
import PrimeVue from 'primevue/config'
|
||||
@@ -24,6 +25,17 @@ const ComfyUIPreset = definePreset(Aura, {
|
||||
|
||||
const app = createApp(App)
|
||||
const pinia = createPinia()
|
||||
Sentry.init({
|
||||
app,
|
||||
dsn: __SENTRY_DSN__,
|
||||
enabled: __SENTRY_ENABLED__,
|
||||
release: __COMFYUI_FRONTEND_VERSION__,
|
||||
integrations: [],
|
||||
autoSessionTracking: false,
|
||||
defaultIntegrations: false,
|
||||
normalizeDepth: 8,
|
||||
tracesSampleRate: 0
|
||||
})
|
||||
app.directive('tooltip', Tooltip)
|
||||
app
|
||||
.use(router)
|
||||
|
||||
@@ -7,6 +7,7 @@ import type {
|
||||
ExecutingWsMessage,
|
||||
ExecutionCachedWsMessage,
|
||||
ExecutionErrorWsMessage,
|
||||
ExecutionInterruptedWsMessage,
|
||||
ExecutionStartWsMessage,
|
||||
ExecutionSuccessWsMessage,
|
||||
ExtensionsResponse,
|
||||
@@ -59,6 +60,7 @@ interface BackendApiCalls {
|
||||
execution_start: ExecutionStartWsMessage
|
||||
execution_success: ExecutionSuccessWsMessage
|
||||
execution_error: ExecutionErrorWsMessage
|
||||
execution_interrupted: ExecutionInterruptedWsMessage
|
||||
execution_cached: ExecutionCachedWsMessage
|
||||
logs: LogsWsMessage
|
||||
/** Mr Blob Preview, I presume? */
|
||||
@@ -355,6 +357,7 @@ export class ComfyApi extends EventTarget {
|
||||
break
|
||||
case 'execution_start':
|
||||
case 'execution_error':
|
||||
case 'execution_interrupted':
|
||||
case 'execution_cached':
|
||||
case 'execution_success':
|
||||
case 'progress':
|
||||
|
||||
@@ -49,7 +49,7 @@ import {
|
||||
} from './pnginfo'
|
||||
import { $el, ComfyUI } from './ui'
|
||||
import { ComfyAppMenu } from './ui/menu/index'
|
||||
import { getStorageValue } from './utils'
|
||||
import { clone, getStorageValue } from './utils'
|
||||
import { type ComfyWidgetConstructor, ComfyWidgets } from './widgets'
|
||||
|
||||
export const ANIM_PREVIEW_WIDGET = '$$comfy_animation_preview'
|
||||
@@ -655,7 +655,7 @@ export class ComfyApp {
|
||||
const keyCombo = KeyComboImpl.fromEvent(e)
|
||||
const keybindingStore = useKeybindingStore()
|
||||
const keybinding = keybindingStore.getKeybinding(keyCombo)
|
||||
if (keybinding && keybinding.targetSelector === '#graph-canvas') {
|
||||
if (keybinding && keybinding.targetElementId === 'graph-canvas') {
|
||||
useCommandStore().execute(keybinding.commandId)
|
||||
block_default = true
|
||||
}
|
||||
@@ -1271,11 +1271,7 @@ export class ComfyApp {
|
||||
reset_invalid_values = true
|
||||
}
|
||||
|
||||
if (typeof structuredClone === 'undefined') {
|
||||
graphData = JSON.parse(JSON.stringify(graphData))
|
||||
} else {
|
||||
graphData = structuredClone(graphData)
|
||||
}
|
||||
graphData = clone(graphData)
|
||||
|
||||
if (useSettingStore().get('Comfy.Validation.Workflows')) {
|
||||
// TODO: Show validation error in a dialog.
|
||||
@@ -1482,7 +1478,7 @@ export class ComfyApp {
|
||||
* @returns The workflow and node links
|
||||
*/
|
||||
async graphToPrompt(graph = this.graph, clean = true) {
|
||||
for (const outerNode of this.graph.computeExecutionOrder(false)) {
|
||||
for (const outerNode of graph.computeExecutionOrder(false)) {
|
||||
if (outerNode.widgets) {
|
||||
for (const widget of outerNode.widgets) {
|
||||
// Allow widgets to run callbacks before a prompt has been queued
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user