Compare commits
171 Commits
v1.6.10
...
task-runne
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
628b44051b | ||
|
|
a7a5e3cf67 | ||
|
|
64e218a9f3 | ||
|
|
0b69d3cbfe | ||
|
|
8257e848c6 | ||
|
|
a07b7693b6 | ||
|
|
26ddf69451 | ||
|
|
ed6ece2099 | ||
|
|
b42516d39c | ||
|
|
ef24efe5a3 | ||
|
|
34c267c755 | ||
|
|
8b9f0ddd1d | ||
|
|
af658b7792 | ||
|
|
9c53bbd53d | ||
|
|
f9be20fa78 | ||
|
|
87fc7a2c5d | ||
|
|
1f266e826e | ||
|
|
911adfe9f8 | ||
|
|
654d72b4cc | ||
|
|
a1ed67fc74 | ||
|
|
78bc635518 | ||
|
|
f49ec175e9 | ||
|
|
e4c60e7e18 | ||
|
|
37cb2cb0a5 | ||
|
|
141825e988 | ||
|
|
78f43b1e06 | ||
|
|
a6105eb8c7 | ||
|
|
79ed598d5d | ||
|
|
816574e0ab | ||
|
|
1a4e77a3ab | ||
|
|
de570712df | ||
|
|
44612e8f97 | ||
|
|
3df911c1bf | ||
|
|
af26b9ad6d | ||
|
|
d503873980 | ||
|
|
842a9f74fc | ||
|
|
29551a36b3 | ||
|
|
d6e5c8950c | ||
|
|
ad1c1ce9c2 | ||
|
|
cb9d2c6bae | ||
|
|
7fd41eeaba | ||
|
|
79fee6ac72 | ||
|
|
edd58cd153 | ||
|
|
e153508955 | ||
|
|
237fca0bf1 | ||
|
|
65542b885a | ||
|
|
f739f704af | ||
|
|
37abdbe35d | ||
|
|
ff445f5c95 | ||
|
|
84b652a281 | ||
|
|
184291d21b | ||
|
|
d7fb25a36a | ||
|
|
c039a60fcc | ||
|
|
3b6108c26e | ||
|
|
49bb247526 | ||
|
|
dd005f5fa5 | ||
|
|
bf90b458d3 | ||
|
|
7e78c5b1dc | ||
|
|
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 | ||
|
|
5c7cbe968e | ||
|
|
cb607ee070 | ||
|
|
f4b5677901 | ||
|
|
9c1eacf0af | ||
|
|
bb988edf9f | ||
|
|
39e9f421f4 | ||
|
|
3d2b9a8d9d | ||
|
|
c77a5cab5b | ||
|
|
e525a3ad98 | ||
|
|
f507142e6a | ||
|
|
3189e310a8 | ||
|
|
174a9a114a | ||
|
|
0b3c0cc0c9 | ||
|
|
74b02237db | ||
|
|
6dc5c6958f | ||
|
|
70bece2d48 | ||
|
|
7eea2e5aa0 | ||
|
|
eb94337c43 | ||
|
|
98b96d2b1a | ||
|
|
1b10e16b2d | ||
|
|
d8f074fea0 | ||
|
|
e88817ea36 | ||
|
|
4ae77e22d4 | ||
|
|
9f23f81879 | ||
|
|
3bf0dfd218 | ||
|
|
be7e2c6505 | ||
|
|
6dce285210 | ||
|
|
5218024395 | ||
|
|
92a5a8057f | ||
|
|
b19f7ca228 | ||
|
|
94f964cf17 | ||
|
|
a7fd9bf0d9 | ||
|
|
642094ead1 | ||
|
|
95a4623c37 |
43
.cursorrules
Normal file
@@ -0,0 +1,43 @@
|
||||
// Vue 3 Composition API .cursorrules
|
||||
|
||||
// Vue 3 Composition API best practices
|
||||
const vue3CompositionApiBestPractices = [
|
||||
"Use setup() function for component logic",
|
||||
"Utilize ref and reactive for reactive state",
|
||||
"Implement computed properties with computed()",
|
||||
"Use watch and watchEffect for side effects",
|
||||
"Implement lifecycle hooks with onMounted, onUpdated, etc.",
|
||||
"Utilize provide/inject for dependency injection",
|
||||
]
|
||||
|
||||
// Folder structure
|
||||
const folderStructure = `
|
||||
src/
|
||||
components/
|
||||
constants/
|
||||
hooks/
|
||||
views/
|
||||
stores/
|
||||
services/
|
||||
App.vue
|
||||
main.ts
|
||||
`;
|
||||
|
||||
// Tailwind CSS best practices
|
||||
const tailwindCssBestPractices = [
|
||||
"Use Tailwind CSS for styling",
|
||||
"Implement responsive design with Tailwind CSS",
|
||||
]
|
||||
|
||||
// Additional instructions
|
||||
const additionalInstructions = `
|
||||
1. Leverage VueUse functions for performance-enhancing styles
|
||||
2. Use lodash for utility functions
|
||||
3. Use TypeScript for type safety
|
||||
4. Implement proper props and emits definitions
|
||||
5. Utilize Vue 3's Teleport component when needed
|
||||
6. Use Suspense for async components
|
||||
7. Implement proper error handling
|
||||
8. Follow Vue 3 style guide and naming conventions
|
||||
9. Use Vite for fast development and building
|
||||
`;
|
||||
2
.github/workflows/format.yaml
vendored
@@ -20,4 +20,4 @@ jobs:
|
||||
run: npm ci
|
||||
|
||||
- name: Run Prettier check
|
||||
run: npx prettier --check './**/*.{js,ts,tsx,vue}'
|
||||
run: npm run format:check
|
||||
47
.github/workflows/i18n-custom-nodes.yaml
vendored
@@ -107,21 +107,56 @@ jobs:
|
||||
cp -rf ComfyUI_frontend/temp/diff/* "$LOCALE_DIR"
|
||||
- name: Check and create fork of custom node repository
|
||||
run: |
|
||||
# Try to fork the repository
|
||||
gh repo fork ${{ inputs.owner }}/${{ inputs.repository }} --clone=false || {
|
||||
echo "Fork failed - repository might already be forked"
|
||||
# Exit 0 to prevent the workflow from failing
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Enable workflows on the forked repository
|
||||
gh api \
|
||||
--method PUT \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
"/repos/${{ inputs.fork_owner }}/${{ inputs.repository }}/actions/permissions/workflow" \
|
||||
-F can_approve_pull_request_reviews=true \
|
||||
-F default_workflow_permissions="write" \
|
||||
-F enabled=true
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
- name: Commit and push changes
|
||||
GH_TOKEN: ${{ secrets.PR_GH_TOKEN }}
|
||||
|
||||
- name: Commit changes
|
||||
working-directory: ComfyUI/custom_nodes/${{ inputs.repository }}
|
||||
run: |
|
||||
git config --global user.name 'github-actions'
|
||||
git config --global user.email 'github-actions@github.com'
|
||||
|
||||
# Create and switch to new branch
|
||||
git checkout -b update-locales
|
||||
|
||||
# Stage and commit changes
|
||||
git add -A
|
||||
git commit -m "Update locales"
|
||||
git remote add org_fork git@github.com:${{ inputs.fork_owner }}/${{ inputs.repository }}.git
|
||||
git push org_fork update-locales
|
||||
gh pr create --title "Update locales for ${{ inputs.repository }}" --repo ${{ inputs.owner }}/${{ inputs.repository }} --head ${{ inputs.fork_owner }}:update-locales
|
||||
|
||||
- name: Install SSH key For PUSH
|
||||
uses: shimataro/ssh-key-action@v2
|
||||
with:
|
||||
# PR private key from action server
|
||||
key: ${{ secrets.PR_SSH_PRIVATE_KEY }}
|
||||
# github public key to confirm it's github server
|
||||
known_hosts: github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==
|
||||
|
||||
- name: Push changes
|
||||
working-directory: ComfyUI/custom_nodes/${{ inputs.repository }}
|
||||
run: |
|
||||
# Force push to create the branch
|
||||
echo "Pushing changes to ${{ inputs.fork_owner }}/${{ inputs.repository }}"
|
||||
git push -f git@github.com:${{ inputs.fork_owner }}/${{ inputs.repository }}.git update-locales
|
||||
|
||||
- name: Create PR
|
||||
working-directory: ComfyUI/custom_nodes/${{ inputs.repository }}
|
||||
run: |
|
||||
# Create PR using gh cli
|
||||
gh pr create --title "Update locales for ${{ inputs.repository }}" --repo ${{ inputs.owner }}/${{ inputs.repository }} --head ${{ inputs.fork_owner }}:update-locales --body "Update locales for ${{ inputs.repository }}"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
GH_TOKEN: ${{ secrets.PR_GH_TOKEN }}
|
||||
|
||||
48
.github/workflows/i18n-node-defs.yaml
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
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
|
||||
path: ComfyUI_frontend
|
||||
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
@@ -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
|
||||
|
||||
3
.github/workflows/test-ui.yaml
vendored
@@ -19,8 +19,7 @@ jobs:
|
||||
- name: Run Jest tests
|
||||
run: |
|
||||
npm run test:generate
|
||||
npm run test:generate:examples
|
||||
npm run test:jest:fast -- --verbose
|
||||
npm run test:jest -- --verbose
|
||||
working-directory: ComfyUI_frontend
|
||||
|
||||
playwright-tests-chromium:
|
||||
|
||||
7
.gitignore
vendored
@@ -16,6 +16,8 @@ dist-ssr
|
||||
.vscode/*
|
||||
*.code-workspace
|
||||
!.vscode/extensions.json
|
||||
!.vscode/tailwind.json
|
||||
!.vscode/settings.json.default
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
@@ -41,4 +43,7 @@ browser_tests/*/*-win32.png
|
||||
|
||||
dist.zip
|
||||
|
||||
/temp/
|
||||
/temp/
|
||||
|
||||
# Generated JSON Schemas
|
||||
/schemas/
|
||||
|
||||
15
.prettierrc
@@ -3,5 +3,16 @@
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 80
|
||||
}
|
||||
"printWidth": 80,
|
||||
"importOrder": ["^@core/(.*)$", "<THIRD_PARTY_MODULES>", "^@/(.*)$", "^[./]"],
|
||||
"importOrderSeparation": true,
|
||||
"importOrderSortSpecifiers": true,
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.{js,cjs,mjs,ts,cts,mts,tsx,vue}",
|
||||
"options": {
|
||||
"plugins": ["@trivago/prettier-plugin-sort-imports"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
5
.vscode/settings.json.default
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"css.customData": [
|
||||
".vscode/tailwind.json"
|
||||
]
|
||||
}
|
||||
55
.vscode/tailwind.json
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"version": 1.1,
|
||||
"atDirectives": [
|
||||
{
|
||||
"name": "@tailwind",
|
||||
"description": "Use the `@tailwind` directive to insert Tailwind's `base`, `components`, `utilities` and `screens` styles into your CSS.",
|
||||
"references": [
|
||||
{
|
||||
"name": "Tailwind Documentation",
|
||||
"url": "https://tailwindcss.com/docs/functions-and-directives#tailwind"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "@apply",
|
||||
"description": "Use the `@apply` directive to inline any existing utility classes into your own custom CSS. This is useful when you find a common utility pattern in your HTML that you’d like to extract to a new component.",
|
||||
"references": [
|
||||
{
|
||||
"name": "Tailwind Documentation",
|
||||
"url": "https://tailwindcss.com/docs/functions-and-directives#apply"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "@responsive",
|
||||
"description": "You can generate responsive variants of your own classes by wrapping their definitions in the `@responsive` directive:\n```css\n@responsive {\n .alert {\n background-color: #E53E3E;\n }\n}\n```\n",
|
||||
"references": [
|
||||
{
|
||||
"name": "Tailwind Documentation",
|
||||
"url": "https://tailwindcss.com/docs/functions-and-directives#responsive"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "@screen",
|
||||
"description": "The `@screen` directive allows you to create media queries that reference your breakpoints by **name** instead of duplicating their values in your own CSS:\n```css\n@screen sm {\n /* ... */\n}\n```\n…gets transformed into this:\n```css\n@media (min-width: 640px) {\n /* ... */\n}\n```\n",
|
||||
"references": [
|
||||
{
|
||||
"name": "Tailwind Documentation",
|
||||
"url": "https://tailwindcss.com/docs/functions-and-directives#screen"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "@variants",
|
||||
"description": "Generate `hover`, `focus`, `active` and other **variants** of your own utilities by wrapping their definitions in the `@variants` directive:\n```css\n@variants hover, focus {\n .btn-brand {\n background-color: #3182CE;\n }\n}\n```\n",
|
||||
"references": [
|
||||
{
|
||||
"name": "Tailwind Documentation",
|
||||
"url": "https://tailwindcss.com/docs/functions-and-directives#variants"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
117
README.md
@@ -58,17 +58,17 @@ 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
|
||||
in `Comfy > Locale > Language` to translate the interface into English, Chinese (Simplified), Russian, Japanese, or Korean. This native
|
||||
ComfyUI now includes built-in translation support, replacing the need for third-party translation extensions. Select your language
|
||||
in `Comfy > Locale > Language` to translate the interface into English, Chinese (Simplified), Russian, Japanese, or Korean. This native
|
||||
implementation offers better performance, reliability, and maintainability compared to previous solutions.<br>
|
||||
|
||||
|
||||
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
|
||||
@@ -229,6 +229,55 @@ https://github.com/user-attachments/assets/c142c43f-2fe9-4030-8196-b3bfd4c6977d
|
||||
|
||||
### Developer APIs
|
||||
|
||||
<details>
|
||||
<summary>v1.6.13: prompt/confirm/alert replacements for ComfyUI desktop</summary>
|
||||
|
||||
Several browser-only APIs are not available in ComfyUI desktop's electron environment.
|
||||
|
||||
- `window.prompt`
|
||||
- `window.confirm`
|
||||
- `window.alert`
|
||||
|
||||
Please use the following APIs as replacements.
|
||||
|
||||
```js
|
||||
// window.prompt
|
||||
window['app'].extensionManager.dialog
|
||||
.prompt({
|
||||
title: 'Test Prompt',
|
||||
message: 'Test Prompt Message'
|
||||
})
|
||||
.then((value: string) => {
|
||||
// Do something with the value user entered
|
||||
})
|
||||
```
|
||||
|
||||

|
||||
|
||||
```js
|
||||
// window.confirm
|
||||
window['app'].extensionManager.dialog
|
||||
.confirm({
|
||||
title: 'Test Confirm',
|
||||
message: 'Test Confirm Message'
|
||||
})
|
||||
.then((value: boolean) => {
|
||||
// Do something with the value user entered
|
||||
})
|
||||
```
|
||||
|
||||

|
||||
|
||||
```js
|
||||
// window.alert
|
||||
window['app'].extensionManager.toast
|
||||
.addAlert("Test Alert")
|
||||
```
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v1.3.34: Register about panel badges</summary>
|
||||
|
||||
@@ -249,7 +298,7 @@ app.registerExtension({
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<details id='extension-api-bottom-panel-tabs'>
|
||||
<summary>v1.3.22: Register bottom panel tabs</summary>
|
||||
|
||||
```js
|
||||
@@ -272,7 +321,7 @@ app.registerExtension({
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<details id='extension-api-settings'>
|
||||
<summary>v1.3.22: New settings API</summary>
|
||||
|
||||
Legacy settings API.
|
||||
@@ -318,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
|
||||
@@ -347,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.
|
||||
@@ -376,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.
|
||||
@@ -394,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.
|
||||
@@ -471,8 +520,7 @@ navigate to `http://<server_ip>:5173` (e.g. `http://192.168.2.20:5173` here), to
|
||||
- `git clone https://github.com/comfyanonymous/ComfyUI_examples.git` to `tests-ui/ComfyUI_examples` or the EXAMPLE_REPO_PATH location specified in .env
|
||||
- `npm i` to install all dependencies
|
||||
- `npm run test:generate` to fetch `tests-ui/data/object_info.json`
|
||||
- `npm run test:generate:examples` to extract the example workflows
|
||||
- `npm run test` to execute all unit tests.
|
||||
- `npm run test:jest` to execute all unit tests.
|
||||
|
||||
### Component Test
|
||||
|
||||
@@ -578,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,6 +1,7 @@
|
||||
import type { Response } from '@playwright/test'
|
||||
import type { StatusWsMessage } from '../src/types/apiTypes.ts'
|
||||
import { expect, mergeTests } from '@playwright/test'
|
||||
|
||||
import type { StatusWsMessage } from '../src/types/apiTypes.ts'
|
||||
import { comfyPageFixture } from './fixtures/ComfyPage'
|
||||
import { webSocketFixture } from './fixtures/ws.ts'
|
||||
|
||||
|
||||
37
browser_tests/assets/collapsed_multiline.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"last_node_id": 1,
|
||||
"last_link_id": 0,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [20, 50],
|
||||
"size": [400, 200],
|
||||
"flags": { "collapsed": true },
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": null,
|
||||
"localized_name": "clip"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": null,
|
||||
"localized_name": "CONDITIONING"
|
||||
}
|
||||
],
|
||||
"properties": {},
|
||||
"widgets_values": ["Should not be displayed."]
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"version": 0.4
|
||||
}
|
||||
|
Before Width: | Height: | Size: 200 KiB After Width: | Height: | Size: 4.5 KiB |
BIN
browser_tests/assets/example.webp
Normal file
|
After Width: | Height: | Size: 828 B |
BIN
browser_tests/assets/image32x32.webp
Normal file
|
After Width: | Height: | Size: 488 B |
BIN
browser_tests/assets/image64x64.webp
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 316 KiB After Width: | Height: | Size: 260 KiB |
|
Before Width: | Height: | Size: 195 KiB After Width: | Height: | Size: 152 B |
@@ -1,4 +1,5 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
test.describe('Browser tab title', () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
ComfyPage,
|
||||
comfyPageFixture as test,
|
||||
comfyExpect as expect
|
||||
comfyExpect as expect,
|
||||
comfyPageFixture as test
|
||||
} from './fixtures/ComfyPage'
|
||||
|
||||
async function beforeChange(comfyPage: ComfyPage) {
|
||||
|
||||
@@ -1,7 +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',
|
||||
@@ -127,17 +129,39 @@ 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'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test.describe('Color Palette', () => {
|
||||
test('Can show custom color palette', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.CustomColorPalettes', customColorPalettes)
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'custom_obsidian_dark')
|
||||
await comfyPage.nextFrame()
|
||||
// Reload to apply the new setting. Setting Comfy.CustomColorPalettes directly
|
||||
// doesn't update the store immediately.
|
||||
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')
|
||||
@@ -149,6 +173,12 @@ test.describe('Color Palette', () => {
|
||||
}, customColorPalettes.obsidian_dark)
|
||||
expect(await comfyPage.getToastErrorCount()).toBe(0)
|
||||
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'obsidian_dark')
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'custom-color-palette-obsidian-dark.png'
|
||||
)
|
||||
// Legacy `custom_` prefix is still supported
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'custom_obsidian_dark')
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
|
||||
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 98 KiB |
@@ -1,4 +1,5 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
test.describe('Keybindings', () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
test.describe('Copy Paste', () => {
|
||||
@@ -10,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()
|
||||
|
||||
|
After Width: | Height: | Size: 118 KiB |
|
After Width: | Height: | Size: 114 KiB |
@@ -1,4 +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', () => {
|
||||
@@ -42,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', () => {
|
||||
@@ -102,4 +116,86 @@ 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))
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Feedback dialog', () => {
|
||||
test('Should open from topmenu help command', async ({ comfyPage }) => {
|
||||
// Open feedback dialog from top menu
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.menu.topbar.triggerTopbarCommand(['Help', 'Feedback'])
|
||||
|
||||
// Verify feedback dialog content is visible
|
||||
const feedbackHeader = comfyPage.page.getByRole('heading', {
|
||||
name: 'Feedback'
|
||||
})
|
||||
await expect(feedbackHeader).toBeVisible()
|
||||
})
|
||||
|
||||
test('Should close when close button clicked', async ({ comfyPage }) => {
|
||||
// Open feedback dialog
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.menu.topbar.triggerTopbarCommand(['Help', 'Feedback'])
|
||||
|
||||
const feedbackHeader = comfyPage.page.getByRole('heading', {
|
||||
name: 'Feedback'
|
||||
})
|
||||
|
||||
// Close feedback dialog
|
||||
await comfyPage.page
|
||||
.getByLabel('', { exact: true })
|
||||
.getByLabel('Close')
|
||||
.click()
|
||||
await feedbackHeader.waitFor({ state: 'hidden' })
|
||||
|
||||
// Verify dialog is closed
|
||||
await expect(feedbackHeader).not.toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
27
browser_tests/domWidget.spec.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
test.describe('DOM Widget', () => {
|
||||
test('Collapsed multiline textarea is not visible', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('collapsed_multiline')
|
||||
|
||||
expect(comfyPage.page.locator('.comfy-multiline-input')).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('Multiline textarea correctly collapses', async ({ comfyPage }) => {
|
||||
const multilineTextAreas = comfyPage.page.locator('.comfy-multiline-input')
|
||||
const firstMultiline = multilineTextAreas.first()
|
||||
const lastMultiline = multilineTextAreas.last()
|
||||
|
||||
await expect(firstMultiline).toBeVisible()
|
||||
await expect(lastMultiline).toBeVisible()
|
||||
|
||||
const nodes = await comfyPage.getNodeRefsByType('CLIPTextEncode')
|
||||
for (const node of nodes) {
|
||||
await node.click('collapse')
|
||||
}
|
||||
await expect(firstMultiline).not.toBeVisible()
|
||||
await expect(lastMultiline).not.toBeVisible()
|
||||
})
|
||||
})
|
||||
@@ -1,4 +1,5 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
test.describe('Topbar commands', () => {
|
||||
@@ -157,4 +158,42 @@ test.describe('Topbar commands', () => {
|
||||
expect(await badge.textContent()).toContain('Test Badge')
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Dialog', () => {
|
||||
test('Should allow showing a prompt dialog', async ({ comfyPage }) => {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
window['app'].extensionManager.dialog
|
||||
.prompt({
|
||||
title: 'Test Prompt',
|
||||
message: 'Test Prompt Message'
|
||||
})
|
||||
.then((value: string) => {
|
||||
window['value'] = value
|
||||
})
|
||||
})
|
||||
|
||||
await comfyPage.fillPromptDialog('Hello, world!')
|
||||
expect(await comfyPage.page.evaluate(() => window['value'])).toBe(
|
||||
'Hello, world!'
|
||||
)
|
||||
})
|
||||
|
||||
test('Should allow showing a confirmation dialog', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
window['app'].extensionManager.dialog
|
||||
.confirm({
|
||||
title: 'Test Confirm',
|
||||
message: 'Test Confirm Message'
|
||||
})
|
||||
.then((value: boolean) => {
|
||||
window['value'] = value
|
||||
})
|
||||
})
|
||||
|
||||
await comfyPage.confirmDialog.click('confirm')
|
||||
expect(await comfyPage.page.evaluate(() => window['value'])).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,28 +1,37 @@
|
||||
import type { Page, Locator, APIRequestContext } from '@playwright/test'
|
||||
import type { APIRequestContext, Locator, Page } from '@playwright/test'
|
||||
import { expect } from '@playwright/test'
|
||||
import { test as base } from '@playwright/test'
|
||||
import { ComfyActionbar } from '../helpers/actionbar'
|
||||
import dotenv from 'dotenv'
|
||||
dotenv.config()
|
||||
import * as fs from 'fs'
|
||||
import { NodeBadgeMode } from '../../src/types/nodeSource'
|
||||
|
||||
import type { useWorkspaceStore } from '../../src/stores/workspaceStore'
|
||||
import type { NodeId } from '../../src/types/comfyWorkflow'
|
||||
import type { KeyCombo } from '../../src/types/keyBindingTypes'
|
||||
import { NodeBadgeMode } from '../../src/types/nodeSource'
|
||||
import { ComfyActionbar } from '../helpers/actionbar'
|
||||
import { ComfyTemplates } from '../helpers/templates'
|
||||
import { ComfyNodeSearchBox } from './components/ComfyNodeSearchBox'
|
||||
import { SettingDialog } from './components/SettingDialog'
|
||||
import {
|
||||
NodeLibrarySidebarTab,
|
||||
QueueSidebarTab,
|
||||
WorkflowsSidebarTab
|
||||
} from './components/SidebarTab'
|
||||
import { Topbar } from './components/Topbar'
|
||||
import { NodeReference } from './utils/litegraphUtils'
|
||||
import type { Position, Size } from './types'
|
||||
import type { useWorkspaceStore } from '../../src/stores/workspaceStore'
|
||||
import { SettingDialog } from './components/SettingDialog'
|
||||
import { NodeReference } from './utils/litegraphUtils'
|
||||
import TaskHistory from './utils/taskHistory'
|
||||
|
||||
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
|
||||
@@ -36,15 +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() {
|
||||
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() {
|
||||
@@ -83,11 +100,13 @@ class ConfirmDialog {
|
||||
public readonly delete: Locator
|
||||
public readonly overwrite: Locator
|
||||
public readonly reject: Locator
|
||||
public readonly confirm: Locator
|
||||
|
||||
constructor(public readonly page: Page) {
|
||||
this.delete = page.locator('button.p-button[aria-label="Delete"]')
|
||||
this.overwrite = page.locator('button.p-button[aria-label="Overwrite"]')
|
||||
this.reject = page.locator('button.p-button[aria-label="Cancel"]')
|
||||
this.confirm = page.locator('button.p-button[aria-label="Confirm"]')
|
||||
}
|
||||
|
||||
async click(locator: KeysOfType<ConfirmDialog, Locator>) {
|
||||
@@ -98,6 +117,8 @@ class ConfirmDialog {
|
||||
}
|
||||
|
||||
export class ComfyPage {
|
||||
private _history: TaskHistory | null = null
|
||||
|
||||
public readonly url: string
|
||||
// All canvas position operations are based on default view of canvas.
|
||||
public readonly canvas: Locator
|
||||
@@ -231,6 +252,11 @@ export class ComfyPage {
|
||||
}
|
||||
}
|
||||
|
||||
setupHistory(): TaskHistory {
|
||||
this._history ??= new TaskHistory(this)
|
||||
return this._history
|
||||
}
|
||||
|
||||
async setup({ clearStorage = true }: { clearStorage?: boolean } = {}) {
|
||||
await this.goto()
|
||||
if (clearStorage) {
|
||||
@@ -343,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)
|
||||
}
|
||||
@@ -518,6 +539,13 @@ export class ComfyPage {
|
||||
return this.page.locator('.p-dialog-content input[type="text"]')
|
||||
}
|
||||
|
||||
async fillPromptDialog(value: string) {
|
||||
await this.promptDialogInput.fill(value)
|
||||
await this.page.keyboard.press('Enter')
|
||||
await this.promptDialogInput.waitFor({ state: 'hidden' })
|
||||
await this.nextFrame()
|
||||
}
|
||||
|
||||
async disconnectEdge() {
|
||||
await this.dragAndDrop(this.clipTextEncodeNode1InputSlot, this.emptySpace)
|
||||
}
|
||||
@@ -785,9 +813,7 @@ export class ComfyPage {
|
||||
await this.canvas.press('Control+a')
|
||||
const node = await this.getFirstNodeRef()
|
||||
await node!.clickContextMenuOption('Convert to Group Node')
|
||||
await this.promptDialogInput.fill(groupNodeName)
|
||||
await this.page.keyboard.press('Enter')
|
||||
await this.promptDialogInput.waitFor({ state: 'hidden' })
|
||||
await this.fillPromptDialog(groupNodeName)
|
||||
await this.nextFrame()
|
||||
}
|
||||
|
||||
@@ -799,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(
|
||||
(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Page } from 'playwright'
|
||||
import { test as base } from '@playwright/test'
|
||||
import { Page } from 'playwright'
|
||||
|
||||
export class UserSelectPage {
|
||||
constructor(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -22,6 +22,12 @@ class SidebarTab {
|
||||
}
|
||||
await this.tabButton.click()
|
||||
}
|
||||
async close() {
|
||||
if (!this.tabButton.isVisible()) {
|
||||
return
|
||||
}
|
||||
await this.tabButton.click()
|
||||
}
|
||||
}
|
||||
|
||||
export class NodeLibrarySidebarTab extends SidebarTab {
|
||||
@@ -146,4 +152,132 @@ 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 {
|
||||
constructor(public readonly page: Page) {
|
||||
super(page, 'queue')
|
||||
}
|
||||
|
||||
get root() {
|
||||
return this.page.locator('.sidebar-content-container', { hasText: 'Queue' })
|
||||
}
|
||||
|
||||
get tasks() {
|
||||
return this.root.locator('[data-virtual-grid-item]')
|
||||
}
|
||||
|
||||
get visibleTasks() {
|
||||
return this.tasks.locator('visible=true')
|
||||
}
|
||||
|
||||
get clearButton() {
|
||||
return this.root.locator('.clear-all-button')
|
||||
}
|
||||
|
||||
get collapseTasksButton() {
|
||||
return this.getToggleExpandButton(false)
|
||||
}
|
||||
|
||||
get expandTasksButton() {
|
||||
return this.getToggleExpandButton(true)
|
||||
}
|
||||
|
||||
get noResultsPlaceholder() {
|
||||
return this.root.locator('.no-results-placeholder')
|
||||
}
|
||||
|
||||
get galleryImage() {
|
||||
return this.page.locator('.galleria-image')
|
||||
}
|
||||
|
||||
private getToggleExpandButton(isExpanded: boolean) {
|
||||
const iconSelector = isExpanded ? '.pi-image' : '.pi-images'
|
||||
return this.root.locator(`.toggle-expanded-button ${iconSelector}`)
|
||||
}
|
||||
|
||||
async open() {
|
||||
await super.open()
|
||||
return this.root.waitFor({ state: 'visible' })
|
||||
}
|
||||
|
||||
async close() {
|
||||
await super.close()
|
||||
await this.root.waitFor({ state: 'hidden' })
|
||||
}
|
||||
|
||||
async expandTasks() {
|
||||
await this.expandTasksButton.click()
|
||||
await this.collapseTasksButton.waitFor({ state: 'visible' })
|
||||
}
|
||||
|
||||
async collapseTasks() {
|
||||
await this.collapseTasksButton.click()
|
||||
await this.expandTasksButton.waitFor({ state: 'visible' })
|
||||
}
|
||||
|
||||
async waitForTasks() {
|
||||
return Promise.all([
|
||||
this.tasks.first().waitFor({ state: 'visible' }),
|
||||
this.tasks.last().waitFor({ state: 'visible' })
|
||||
])
|
||||
}
|
||||
|
||||
async scrollTasks(direction: 'up' | 'down') {
|
||||
const scrollToEl =
|
||||
direction === 'up' ? this.tasks.last() : this.tasks.first()
|
||||
await scrollToEl.scrollIntoViewIfNeeded()
|
||||
await this.waitForTasks()
|
||||
}
|
||||
|
||||
async clearTasks() {
|
||||
await this.clearButton.click()
|
||||
const confirmButton = this.page.getByLabel('Delete')
|
||||
await confirmButton.click()
|
||||
await this.noResultsPlaceholder.waitFor({ state: 'visible' })
|
||||
}
|
||||
|
||||
/** Set the width of the tab (out of 100). Must call before opening the tab */
|
||||
async setTabWidth(width: number) {
|
||||
if (width < 0 || width > 100) {
|
||||
throw new Error('Width must be between 0 and 100')
|
||||
}
|
||||
return this.page.evaluate((width) => {
|
||||
localStorage.setItem('queue', JSON.stringify([width, 100 - width]))
|
||||
}, width)
|
||||
}
|
||||
|
||||
getTaskPreviewButton(taskIndex: number) {
|
||||
return this.tasks.nth(taskIndex).getByRole('button')
|
||||
}
|
||||
|
||||
async openTaskPreview(taskIndex: number) {
|
||||
const previewButton = this.getTaskPreviewButton(taskIndex)
|
||||
await previewButton.click()
|
||||
return this.galleryImage.waitFor({ state: 'visible' })
|
||||
}
|
||||
|
||||
getGalleryImage(imageFilename: string) {
|
||||
return this.galleryImage.and(this.page.getByAltText(imageFilename))
|
||||
}
|
||||
|
||||
getTaskImage(imageFilename: string) {
|
||||
return this.tasks.getByAltText(imageFilename)
|
||||
}
|
||||
|
||||
/** Trigger the queue store and tasks to update */
|
||||
async triggerTasksUpdate() {
|
||||
await this.page.evaluate(() => {
|
||||
window['app']['api'].dispatchCustomEvent('status', {
|
||||
exec_info: { queue_remaining: 0 }
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ManageGroupNode } from '../../helpers/manageGroupNode'
|
||||
import type { NodeId } from '../../../src/types/comfyWorkflow'
|
||||
import type { Page } from '@playwright/test'
|
||||
|
||||
import type { NodeId } from '../../../src/types/comfyWorkflow'
|
||||
import { ManageGroupNode } from '../../helpers/manageGroupNode'
|
||||
import type { ComfyPage } from '../ComfyPage'
|
||||
import type { Position, Size } from '../types'
|
||||
|
||||
@@ -234,9 +235,7 @@ export class NodeReference {
|
||||
}
|
||||
async convertToGroupNode(groupNodeName: string = 'GroupNode') {
|
||||
await this.clickContextMenuOption('Convert to Group Node')
|
||||
await this.comfyPage.promptDialogInput.fill(groupNodeName)
|
||||
await this.comfyPage.page.keyboard.press('Enter')
|
||||
await this.comfyPage.promptDialogInput.waitFor({ state: 'hidden' })
|
||||
await this.comfyPage.fillPromptDialog(groupNodeName)
|
||||
await this.comfyPage.nextFrame()
|
||||
const nodes = await this.comfyPage.getNodeRefsByType(
|
||||
`workflow>${groupNodeName}`
|
||||
|
||||
162
browser_tests/fixtures/utils/taskHistory.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
import fs from 'fs'
|
||||
import _ from 'lodash'
|
||||
import path from 'path'
|
||||
import type { Request, Route } from 'playwright'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
import type {
|
||||
HistoryTaskItem,
|
||||
TaskItem,
|
||||
TaskOutput
|
||||
} from '../../../src/types/apiTypes'
|
||||
import type { ComfyPage } from '../ComfyPage'
|
||||
|
||||
/** keyof TaskOutput[string] */
|
||||
type OutputFileType = 'images' | 'audio' | 'animated'
|
||||
|
||||
const DEFAULT_IMAGE = 'example.webp'
|
||||
|
||||
const getFilenameParam = (request: Request) => {
|
||||
const url = new URL(request.url())
|
||||
return url.searchParams.get('filename') || DEFAULT_IMAGE
|
||||
}
|
||||
|
||||
const getContentType = (filename: string, fileType: OutputFileType) => {
|
||||
const subtype = path.extname(filename).slice(1)
|
||||
switch (fileType) {
|
||||
case 'images':
|
||||
return `image/${subtype}`
|
||||
case 'audio':
|
||||
return `audio/${subtype}`
|
||||
case 'animated':
|
||||
return `video/${subtype}`
|
||||
}
|
||||
}
|
||||
|
||||
const setQueueIndex = (task: TaskItem) => {
|
||||
task.prompt[0] = TaskHistory.queueIndex++
|
||||
}
|
||||
|
||||
const setPromptId = (task: TaskItem) => {
|
||||
task.prompt[1] = uuidv4()
|
||||
}
|
||||
|
||||
export default class TaskHistory {
|
||||
static queueIndex = 0
|
||||
static readonly defaultTask: Readonly<HistoryTaskItem> = {
|
||||
prompt: [0, 'prompt-id', {}, { client_id: uuidv4() }, []],
|
||||
outputs: {},
|
||||
status: {
|
||||
status_str: 'success',
|
||||
completed: true,
|
||||
messages: []
|
||||
},
|
||||
taskType: 'History'
|
||||
}
|
||||
private tasks: HistoryTaskItem[] = []
|
||||
private outputContentTypes: Map<string, string> = new Map()
|
||||
|
||||
constructor(readonly comfyPage: ComfyPage) {}
|
||||
|
||||
private loadAsset: (filename: string) => Buffer = _.memoize(
|
||||
(filename: string) => {
|
||||
const filePath = this.comfyPage.assetPath(filename)
|
||||
return fs.readFileSync(filePath)
|
||||
}
|
||||
)
|
||||
|
||||
private async handleGetHistory(route: Route) {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify(this.tasks)
|
||||
})
|
||||
}
|
||||
|
||||
private async handleGetView(route: Route) {
|
||||
const fileName = getFilenameParam(route.request())
|
||||
if (!this.outputContentTypes.has(fileName)) route.continue()
|
||||
|
||||
const asset = this.loadAsset(fileName)
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: this.outputContentTypes.get(fileName),
|
||||
body: asset,
|
||||
headers: {
|
||||
'Cache-Control': 'public, max-age=31536000',
|
||||
'Content-Length': asset.byteLength.toString()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async setupRoutes() {
|
||||
return this.comfyPage.page.route(
|
||||
/.*\/api\/(view|history)(\?.*)?$/,
|
||||
async (route) => {
|
||||
const request = route.request()
|
||||
const method = request.method()
|
||||
|
||||
const isViewReq = request.url().includes('view') && method === 'GET'
|
||||
if (isViewReq) return this.handleGetView(route)
|
||||
|
||||
const isHistoryPath = request.url().includes('history')
|
||||
const isGetHistoryReq = isHistoryPath && method === 'GET'
|
||||
if (isGetHistoryReq) return this.handleGetHistory(route)
|
||||
|
||||
const isClearReq =
|
||||
method === 'POST' &&
|
||||
isHistoryPath &&
|
||||
request.postDataJSON()?.clear === true
|
||||
if (isClearReq) return this.clearTasks()
|
||||
|
||||
return route.continue()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private createOutputs(
|
||||
filenames: string[],
|
||||
filetype: OutputFileType
|
||||
): TaskOutput {
|
||||
return filenames.reduce((outputs, filename, i) => {
|
||||
const nodeId = `${i + 1}`
|
||||
outputs[nodeId] = {
|
||||
[filetype]: [{ filename, subfolder: '', type: 'output' }]
|
||||
}
|
||||
const contentType = getContentType(filename, filetype)
|
||||
this.outputContentTypes.set(filename, contentType)
|
||||
return outputs
|
||||
}, {})
|
||||
}
|
||||
|
||||
private addTask(task: HistoryTaskItem) {
|
||||
setPromptId(task)
|
||||
setQueueIndex(task)
|
||||
this.tasks.unshift(task) // Tasks are added to the front of the queue
|
||||
}
|
||||
|
||||
clearTasks(): this {
|
||||
this.tasks = []
|
||||
return this
|
||||
}
|
||||
|
||||
withTask(
|
||||
outputFilenames: string[],
|
||||
outputFiletype: OutputFileType = 'images',
|
||||
overrides: Partial<HistoryTaskItem> = {}
|
||||
): this {
|
||||
this.addTask({
|
||||
...TaskHistory.defaultTask,
|
||||
outputs: this.createOutputs(outputFilenames, outputFiletype),
|
||||
...overrides
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
/** Repeats the last task in the task history a specified number of times. */
|
||||
repeat(n: number): this {
|
||||
for (let i = 0; i < n; i++)
|
||||
this.addTask(structuredClone(this.tasks.at(0)) as HistoryTaskItem)
|
||||
return this
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import { FullConfig } from '@playwright/test'
|
||||
import { backupPath } from './utils/backupUtils'
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
import { backupPath } from './utils/backupUtils'
|
||||
|
||||
dotenv.config()
|
||||
|
||||
export default function globalSetup(config: FullConfig) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { FullConfig } from '@playwright/test'
|
||||
import { restorePath } from './utils/backupUtils'
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
import { restorePath } from './utils/backupUtils'
|
||||
|
||||
dotenv.config()
|
||||
|
||||
export default function globalTeardown(config: FullConfig) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
test.describe('Graph Canvas Menu', () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { ComfyPage, comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
import type { NodeReference } from './fixtures/utils/litegraphUtils'
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Page, Locator } from '@playwright/test'
|
||||
import type { Locator, Page } from '@playwright/test'
|
||||
|
||||
import type { AutoQueueMode } from '../../src/stores/queueStore'
|
||||
|
||||
export class ComfyActionbar {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Locator, Page } from '@playwright/test'
|
||||
|
||||
export class ManageGroupNode {
|
||||
footer: Locator
|
||||
header: Locator
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Locator, Page } from '@playwright/test'
|
||||
|
||||
export class ComfyTemplates {
|
||||
readonly content: Locator
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
test.describe('Item Interaction', () => {
|
||||
@@ -452,6 +453,9 @@ test.describe('Canvas Interaction', () => {
|
||||
expect(await getCursorStyle()).toBe('default')
|
||||
await comfyPage.page.mouse.down()
|
||||
expect(await getCursorStyle()).toBe('grabbing')
|
||||
// Move mouse should not alter cursor style.
|
||||
await comfyPage.page.mouse.move(10, 20)
|
||||
expect(await getCursorStyle()).toBe('grabbing')
|
||||
await comfyPage.page.mouse.up()
|
||||
expect(await getCursorStyle()).toBe('default')
|
||||
|
||||
@@ -465,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)
|
||||
@@ -552,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')
|
||||
})
|
||||
|
||||
@@ -569,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', () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
test.describe('Keybindings', () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
function listenForEvent(): Promise<Event> {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
test.describe('Load Workflow in Media', () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
test.describe('Menu', () => {
|
||||
@@ -17,7 +18,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()
|
||||
@@ -288,6 +289,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', {
|
||||
@@ -395,6 +437,56 @@ test.describe('Menu', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test('Can duplicate workflow', async ({ comfyPage }) => {
|
||||
const tab = comfyPage.menu.workflowsTab
|
||||
await comfyPage.menu.topbar.saveWorkflow('workflow1.json')
|
||||
|
||||
expect(await tab.getTopLevelSavedWorkflowNames()).toEqual(
|
||||
expect.arrayContaining(['workflow1.json'])
|
||||
)
|
||||
|
||||
await comfyPage.executeCommand('Comfy.DuplicateWorkflow')
|
||||
expect(await tab.getOpenedWorkflowNames()).toEqual([
|
||||
'workflow1.json',
|
||||
'*workflow1 (Copy).json'
|
||||
])
|
||||
|
||||
await comfyPage.executeCommand('Comfy.DuplicateWorkflow')
|
||||
expect(await tab.getOpenedWorkflowNames()).toEqual([
|
||||
'workflow1.json',
|
||||
'*workflow1 (Copy).json',
|
||||
'*workflow1 (Copy) (2).json'
|
||||
])
|
||||
|
||||
await comfyPage.executeCommand('Comfy.DuplicateWorkflow')
|
||||
expect(await tab.getOpenedWorkflowNames()).toEqual([
|
||||
'workflow1.json',
|
||||
'*workflow1 (Copy).json',
|
||||
'*workflow1 (Copy) (2).json',
|
||||
'*workflow1 (Copy) (3).json'
|
||||
])
|
||||
})
|
||||
|
||||
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
|
||||
}) => {
|
||||
@@ -474,7 +566,7 @@ test.describe('Menu', () => {
|
||||
})
|
||||
|
||||
await comfyPage.setSetting('Comfy.Locale', 'zh')
|
||||
await comfyPage.reload()
|
||||
await comfyPage.setup()
|
||||
|
||||
const downloadedContentZh = await comfyPage.getExportedWorkflow({
|
||||
api: false
|
||||
@@ -570,6 +662,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)
|
||||
|
||||
@@ -703,3 +804,204 @@ test.describe('Menu', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test.describe.skip('Queue sidebar', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
|
||||
test('can display tasks', async ({ comfyPage }) => {
|
||||
await comfyPage.setupHistory().withTask(['example.webp']).setupRoutes()
|
||||
await comfyPage.menu.queueTab.open()
|
||||
await comfyPage.menu.queueTab.waitForTasks()
|
||||
expect(await comfyPage.menu.queueTab.visibleTasks.count()).toBe(1)
|
||||
})
|
||||
|
||||
test('can display tasks after closing then opening', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setupHistory().withTask(['example.webp']).setupRoutes()
|
||||
await comfyPage.menu.queueTab.open()
|
||||
await comfyPage.menu.queueTab.close()
|
||||
await comfyPage.menu.queueTab.open()
|
||||
await comfyPage.menu.queueTab.waitForTasks()
|
||||
expect(await comfyPage.menu.queueTab.visibleTasks.count()).toBe(1)
|
||||
})
|
||||
|
||||
test.describe('Virtual scroll', () => {
|
||||
const layouts = [
|
||||
{ description: 'Five columns layout', width: 95, rows: 3, cols: 5 },
|
||||
{ description: 'Three columns layout', width: 55, rows: 3, cols: 3 },
|
||||
{ description: 'Two columns layout', width: 40, rows: 3, cols: 2 }
|
||||
]
|
||||
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage
|
||||
.setupHistory()
|
||||
.withTask(['example.webp'])
|
||||
.repeat(50)
|
||||
.setupRoutes()
|
||||
})
|
||||
|
||||
layouts.forEach(({ description, width, rows, cols }) => {
|
||||
const preRenderedRows = 1
|
||||
const preRenderedTasks = preRenderedRows * cols * 2
|
||||
const visibleTasks = rows * cols
|
||||
const expectRenderLimit = visibleTasks + preRenderedTasks
|
||||
|
||||
test.describe(description, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.menu.queueTab.setTabWidth(width)
|
||||
await comfyPage.menu.queueTab.open()
|
||||
await comfyPage.menu.queueTab.waitForTasks()
|
||||
})
|
||||
|
||||
test('should not render items outside of view', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const renderedCount =
|
||||
await comfyPage.menu.queueTab.visibleTasks.count()
|
||||
expect(renderedCount).toBeLessThanOrEqual(expectRenderLimit)
|
||||
})
|
||||
|
||||
test('should teardown items after scrolling away', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.menu.queueTab.scrollTasks('down')
|
||||
const renderedCount =
|
||||
await comfyPage.menu.queueTab.visibleTasks.count()
|
||||
expect(renderedCount).toBeLessThanOrEqual(expectRenderLimit)
|
||||
})
|
||||
|
||||
test('should re-render items after scrolling away then back', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.menu.queueTab.scrollTasks('down')
|
||||
await comfyPage.menu.queueTab.scrollTasks('up')
|
||||
const renderedCount =
|
||||
await comfyPage.menu.queueTab.visibleTasks.count()
|
||||
expect(renderedCount).toBeLessThanOrEqual(expectRenderLimit)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Expand tasks', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
// 2-item batch and 3-item batch -> 3 additional items when expanded
|
||||
await comfyPage
|
||||
.setupHistory()
|
||||
.withTask(['example.webp', 'example.webp', 'example.webp'])
|
||||
.withTask(['example.webp', 'example.webp'])
|
||||
.setupRoutes()
|
||||
await comfyPage.menu.queueTab.open()
|
||||
await comfyPage.menu.queueTab.waitForTasks()
|
||||
})
|
||||
|
||||
test('can expand tasks with multiple outputs', async ({ comfyPage }) => {
|
||||
const initialCount = await comfyPage.menu.queueTab.visibleTasks.count()
|
||||
await comfyPage.menu.queueTab.expandTasks()
|
||||
expect(await comfyPage.menu.queueTab.visibleTasks.count()).toBe(
|
||||
initialCount + 3
|
||||
)
|
||||
})
|
||||
|
||||
test('can collapse flat tasks', async ({ comfyPage }) => {
|
||||
const initialCount = await comfyPage.menu.queueTab.visibleTasks.count()
|
||||
await comfyPage.menu.queueTab.expandTasks()
|
||||
await comfyPage.menu.queueTab.collapseTasks()
|
||||
expect(await comfyPage.menu.queueTab.visibleTasks.count()).toBe(
|
||||
initialCount
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Clear tasks', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage
|
||||
.setupHistory()
|
||||
.withTask(['example.webp'])
|
||||
.repeat(6)
|
||||
.setupRoutes()
|
||||
await comfyPage.menu.queueTab.open()
|
||||
})
|
||||
|
||||
test('can clear all tasks', async ({ comfyPage }) => {
|
||||
await comfyPage.menu.queueTab.clearTasks()
|
||||
expect(await comfyPage.menu.queueTab.visibleTasks.count()).toBe(0)
|
||||
expect(
|
||||
await comfyPage.menu.queueTab.noResultsPlaceholder.isVisible()
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
test('can load new tasks after clearing all', async ({ comfyPage }) => {
|
||||
await comfyPage.menu.queueTab.clearTasks()
|
||||
await comfyPage.menu.queueTab.close()
|
||||
await comfyPage.setupHistory().withTask(['example.webp']).setupRoutes()
|
||||
await comfyPage.menu.queueTab.open()
|
||||
await comfyPage.menu.queueTab.waitForTasks()
|
||||
expect(await comfyPage.menu.queueTab.visibleTasks.count()).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Gallery', () => {
|
||||
const firstImage = 'example.webp'
|
||||
const secondImage = 'image32x32.webp'
|
||||
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage
|
||||
.setupHistory()
|
||||
.withTask([secondImage])
|
||||
.withTask([firstImage])
|
||||
.setupRoutes()
|
||||
await comfyPage.menu.queueTab.open()
|
||||
await comfyPage.menu.queueTab.waitForTasks()
|
||||
await comfyPage.menu.queueTab.openTaskPreview(0)
|
||||
})
|
||||
|
||||
test('displays gallery image after opening task preview', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.nextFrame()
|
||||
expect(comfyPage.menu.queueTab.getGalleryImage(firstImage)).toBeVisible()
|
||||
})
|
||||
|
||||
test('maintains active gallery item when new tasks are added', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Add a new task while the gallery is still open
|
||||
const newImage = 'image64x64.webp'
|
||||
comfyPage.setupHistory().withTask([newImage])
|
||||
await comfyPage.menu.queueTab.triggerTasksUpdate()
|
||||
await comfyPage.page.waitForTimeout(500)
|
||||
const newTask = comfyPage.menu.queueTab.tasks.getByAltText(newImage)
|
||||
await newTask.waitFor({ state: 'visible' })
|
||||
// The active gallery item should still be the initial image
|
||||
expect(comfyPage.menu.queueTab.getGalleryImage(firstImage)).toBeVisible()
|
||||
})
|
||||
|
||||
test.describe('Gallery navigation', () => {
|
||||
const paths: {
|
||||
description: string
|
||||
path: ('Right' | 'Left')[]
|
||||
end: string
|
||||
}[] = [
|
||||
{ description: 'Right', path: ['Right'], end: secondImage },
|
||||
{ description: 'Left', path: ['Right', 'Left'], end: firstImage },
|
||||
{ description: 'Left wrap', path: ['Left'], end: secondImage },
|
||||
{ description: 'Right wrap', path: ['Right', 'Right'], end: firstImage }
|
||||
]
|
||||
|
||||
paths.forEach(({ description, path, end }) => {
|
||||
test(`can navigate gallery ${description}`, async ({ comfyPage }) => {
|
||||
for (const direction of path)
|
||||
await comfyPage.page.keyboard.press(`Arrow${direction}`, {
|
||||
delay: 256
|
||||
})
|
||||
await comfyPage.nextFrame()
|
||||
expect(comfyPage.menu.queueTab.getGalleryImage(end)).toBeVisible()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { expect } from '@playwright/test'
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
import type { ComfyApp } from '../src/scripts/app'
|
||||
import { NodeBadgeMode } from '../src/types/nodeSource'
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
test.describe('Node Badge', () => {
|
||||
test('Can add badge', async ({ comfyPage }) => {
|
||||
|
||||
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 105 KiB |
@@ -1,4 +1,5 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
// If an input is optional by node definition, it should be shown as
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
comfyPageFixture as test,
|
||||
comfyExpect as expect
|
||||
comfyExpect as expect,
|
||||
comfyPageFixture as test
|
||||
} from './fixtures/ComfyPage'
|
||||
|
||||
test.describe('Node search box', () => {
|
||||
@@ -82,10 +82,14 @@ test.describe('Node search box', () => {
|
||||
test('Has correct aria-labels on search results', async ({ comfyPage }) => {
|
||||
const node = 'Load Checkpoint'
|
||||
await comfyPage.doubleClickCanvas()
|
||||
await comfyPage.searchBox.fillAndSelectFirstNode(node)
|
||||
const firstResult = comfyPage.page
|
||||
.locator('li.p-autocomplete-option')
|
||||
.first()
|
||||
await comfyPage.searchBox.input.waitFor({ state: 'visible' })
|
||||
await comfyPage.searchBox.input.fill(node)
|
||||
await comfyPage.searchBox.dropdown.waitFor({ state: 'visible' })
|
||||
// Wait for some time for the auto complete list to update.
|
||||
// The auto complete list is debounced and may take some time to update.
|
||||
await comfyPage.page.waitForTimeout(500)
|
||||
|
||||
const firstResult = comfyPage.searchBox.dropdown.locator('li').first()
|
||||
await expect(firstResult).toHaveAttribute('aria-label', node)
|
||||
})
|
||||
|
||||
@@ -132,6 +136,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')
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
import type { NodeReference } from './fixtures/utils/litegraphUtils'
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
test.describe('Properties Panel', () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
test.describe('Reroute Node', () => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { expect } from '@playwright/test'
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
import { NodeBadgeMode } from '../src/types/nodeSource'
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
test.describe('Canvas Right Click Menu', () => {
|
||||
// See https://github.com/comfyanonymous/ComfyUI/issues/3883
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
test.describe('Templates', () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
test.describe('Combo text widget', () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { userSelectPageFixture as test } from './fixtures/UserSelectPage'
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import path from 'path'
|
||||
import fs from 'fs-extra'
|
||||
import path from 'path'
|
||||
|
||||
type PathParts = readonly [string, ...string[]]
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import globals from 'globals'
|
||||
import pluginJs from '@eslint/js'
|
||||
import tseslint from 'typescript-eslint'
|
||||
import pluginVue from 'eslint-plugin-vue'
|
||||
import unusedImports from 'eslint-plugin-unused-imports'
|
||||
import pluginVue from 'eslint-plugin-vue'
|
||||
import globals from 'globals'
|
||||
import tseslint from 'typescript-eslint'
|
||||
|
||||
export default [
|
||||
{
|
||||
|
||||
2
global.d.ts
vendored
@@ -1 +1,3 @@
|
||||
declare const __COMFYUI_FRONTEND_VERSION__: string
|
||||
declare const __SENTRY_ENABLED__: boolean
|
||||
declare const __SENTRY_DSN__: string
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import type { JestConfigWithTsJest } from 'ts-jest'
|
||||
import baseConfig from './jest.config.base'
|
||||
|
||||
const jestConfig: JestConfigWithTsJest = {
|
||||
...baseConfig,
|
||||
displayName: 'fast',
|
||||
setupFiles: ['./tests-ui/tests/fast/globalSetup.ts'],
|
||||
setupFilesAfterEnv: undefined,
|
||||
testMatch: ['**/tests-ui/tests/fast/**/*.test.ts']
|
||||
}
|
||||
|
||||
export default jestConfig
|
||||
@@ -1,11 +0,0 @@
|
||||
import type { JestConfigWithTsJest } from 'ts-jest'
|
||||
import baseConfig from './jest.config.base'
|
||||
|
||||
const jestConfig: JestConfigWithTsJest = {
|
||||
...baseConfig,
|
||||
displayName: 'slow (DOM)',
|
||||
testTimeout: 10000,
|
||||
testMatch: ['**/tests-ui/tests/slow/**/*.test.ts']
|
||||
}
|
||||
|
||||
export default jestConfig
|
||||
@@ -21,8 +21,7 @@ const jestConfig: JestConfigWithTsJest = {
|
||||
},
|
||||
clearMocks: true,
|
||||
resetModules: true,
|
||||
setupFiles: ['./tests-ui/tests/globalSetup.ts'],
|
||||
setupFilesAfterEnv: ['./tests-ui/tests/afterSetup.ts']
|
||||
setupFiles: ['./tests-ui/tests/globalSetup.ts']
|
||||
}
|
||||
|
||||
export default jestConfig
|
||||
@@ -1,14 +1,17 @@
|
||||
export default {
|
||||
'./**/*.js': (stagedFiles) => formatFiles(stagedFiles),
|
||||
'./**/*.js': (stagedFiles) => formatAndEslint(stagedFiles),
|
||||
|
||||
'./**/*.{ts,tsx,vue}': (stagedFiles) => [
|
||||
...formatFiles(stagedFiles),
|
||||
'./**/*.{ts,tsx,vue,mts}': (stagedFiles) => [
|
||||
...formatAndEslint(stagedFiles),
|
||||
'vue-tsc --noEmit',
|
||||
'tsc --noEmit',
|
||||
'tsc-strict'
|
||||
]
|
||||
}
|
||||
|
||||
function formatFiles(fileNames) {
|
||||
return [`prettier --write ${fileNames.join(' ')}`]
|
||||
function formatAndEslint(fileNames) {
|
||||
return [
|
||||
`prettier --write ${fileNames.join(' ')}`,
|
||||
`eslint --fix ${fileNames.join(' ')}`
|
||||
]
|
||||
}
|
||||
|
||||
603
package-lock.json
generated
@@ -1,18 +1,20 @@
|
||||
{
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"version": "1.6.10",
|
||||
"version": "1.8.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"version": "1.6.10",
|
||||
"version": "1.8.2",
|
||||
"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.50",
|
||||
"@primevue/themes": "^4.0.5",
|
||||
"@comfyorg/comfyui-electron-types": "^0.4.11",
|
||||
"@comfyorg/litegraph": "^0.8.61",
|
||||
"@primevue/forms": "^4.2.5",
|
||||
"@primevue/themes": "^4.2.5",
|
||||
"@sentry/vue": "^8.48.0",
|
||||
"@tiptap/core": "^2.10.4",
|
||||
"@tiptap/extension-link": "^2.10.4",
|
||||
"@tiptap/extension-table": "^2.10.4",
|
||||
@@ -31,10 +33,10 @@
|
||||
"loglevel": "^1.9.2",
|
||||
"pinia": "^2.1.7",
|
||||
"primeicons": "^7.0.0",
|
||||
"primevue": "^4.0.5",
|
||||
"primevue": "^4.2.5",
|
||||
"three": "^0.170.0",
|
||||
"tiptap-markdown": "^0.8.10",
|
||||
"vue": "^3.4.31",
|
||||
"vue": "^3.5.13",
|
||||
"vue-i18n": "^9.13.1",
|
||||
"vue-router": "^4.4.3",
|
||||
"zod": "^3.23.8",
|
||||
@@ -48,6 +50,7 @@
|
||||
"@lobehub/i18n-cli": "^1.20.0",
|
||||
"@pinia/testing": "^0.1.5",
|
||||
"@playwright/test": "^1.44.1",
|
||||
"@trivago/prettier-plugin-sort-imports": "^5.2.0",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/lodash": "^4.17.6",
|
||||
"@types/node": "^20.14.8",
|
||||
@@ -86,7 +89,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": {
|
||||
@@ -197,12 +201,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
|
||||
"integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==",
|
||||
"version": "7.26.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
|
||||
"integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/highlight": "^7.24.7",
|
||||
"@babel/helper-validator-identifier": "^7.25.9",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -249,15 +255,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/generator": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz",
|
||||
"integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==",
|
||||
"version": "7.26.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.3.tgz",
|
||||
"integrity": "sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.24.7",
|
||||
"@babel/parser": "^7.26.3",
|
||||
"@babel/types": "^7.26.3",
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.25",
|
||||
"jsesc": "^2.5.1"
|
||||
"jsesc": "^3.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -590,42 +598,13 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz",
|
||||
"integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.24.7",
|
||||
"chalk": "^2.4.2",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.26.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz",
|
||||
"integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==",
|
||||
"version": "7.26.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz",
|
||||
"integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.26.0"
|
||||
"@babel/types": "^7.26.3"
|
||||
},
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
@@ -1895,33 +1874,32 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz",
|
||||
"integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==",
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
|
||||
"integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.24.7",
|
||||
"@babel/parser": "^7.24.7",
|
||||
"@babel/types": "^7.24.7"
|
||||
"@babel/code-frame": "^7.25.9",
|
||||
"@babel/parser": "^7.25.9",
|
||||
"@babel/types": "^7.25.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz",
|
||||
"integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==",
|
||||
"version": "7.26.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.4.tgz",
|
||||
"integrity": "sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.24.7",
|
||||
"@babel/generator": "^7.24.7",
|
||||
"@babel/helper-environment-visitor": "^7.24.7",
|
||||
"@babel/helper-function-name": "^7.24.7",
|
||||
"@babel/helper-hoist-variables": "^7.24.7",
|
||||
"@babel/helper-split-export-declaration": "^7.24.7",
|
||||
"@babel/parser": "^7.24.7",
|
||||
"@babel/types": "^7.24.7",
|
||||
"@babel/code-frame": "^7.26.2",
|
||||
"@babel/generator": "^7.26.3",
|
||||
"@babel/parser": "^7.26.3",
|
||||
"@babel/template": "^7.25.9",
|
||||
"@babel/types": "^7.26.3",
|
||||
"debug": "^4.3.1",
|
||||
"globals": "^11.1.0"
|
||||
},
|
||||
@@ -1940,9 +1918,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.26.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz",
|
||||
"integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==",
|
||||
"version": "7.26.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz",
|
||||
"integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.25.9",
|
||||
@@ -1959,15 +1937,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.11",
|
||||
"resolved": "https://registry.npmjs.org/@comfyorg/comfyui-electron-types/-/comfyui-electron-types-0.4.11.tgz",
|
||||
"integrity": "sha512-RGJeWwXjyv0Ojj7xkZKgcRxC1nFv1nh7qEWpNBiofxVgFiap9Ei79b/KJYxNE0no4BoYqRMaRg+sFtCE6yEukA==",
|
||||
"license": "GPL-3.0-only"
|
||||
},
|
||||
"node_modules/@comfyorg/litegraph": {
|
||||
"version": "0.8.50",
|
||||
"resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.8.50.tgz",
|
||||
"integrity": "sha512-sBwiXySHHjNmHQ4erf53cYsArXAAdFBAPY7/g+1Sp6aWA4krrLekD3yHmetgbfKq/pUy/MbZ8JgHTd68rx0mhg==",
|
||||
"version": "0.8.61",
|
||||
"resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.8.61.tgz",
|
||||
"integrity": "sha512-7DroJ0PLgI9TFvQR//6rf0NRXRvV60hapxVX5lmKzNn4Mn2Ni/JsB2ypNLKeSU5sacNyu8QT3W5Jdpafl7lcnA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@cspotcode/source-map-support": {
|
||||
@@ -3984,63 +3962,129 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@primeuix/forms": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@primeuix/forms/-/forms-0.0.2.tgz",
|
||||
"integrity": "sha512-DpecPQd/Qf/kav4LKCaIeGuT3AkwhJzuHCkLANTVlN/zBvo8KIj3OZHsCkm0zlIMVVnaJdtx1ULNlRQdudef+A==",
|
||||
"dependencies": {
|
||||
"@primeuix/utils": "^0.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@primeuix/forms/node_modules/@primeuix/utils": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@primeuix/utils/-/utils-0.3.2.tgz",
|
||||
"integrity": "sha512-B+nphqTQeq+i6JuICLdVWnDMjONome2sNz0xI65qIOyeB4EF12CoKRiCsxuZ5uKAkHi/0d1LqlQ9mIWRSdkavw==",
|
||||
"engines": {
|
||||
"node": ">=12.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@primeuix/styled": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@primeuix/styled/-/styled-0.0.5.tgz",
|
||||
"integrity": "sha512-pVoGn/uPkVm/DyF3TR3EmH/pL/dP4nR42FcYbVduFq9VfO3KVeOEqvcCULHXos66RZO9MCbCFUoLy6ctf9GUGQ==",
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@primeuix/styled/-/styled-0.3.2.tgz",
|
||||
"integrity": "sha512-ColZes0+/WKqH4ob2x8DyNYf1NENpe5ZguOvx5yCLxaP8EIMVhLjWLO/3umJiDnQU4XXMLkn2mMHHw+fhTX/mw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@primeuix/utils": "^0.0.5"
|
||||
"@primeuix/utils": "^0.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@primeuix/utils": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@primeuix/utils/-/utils-0.0.5.tgz",
|
||||
"integrity": "sha512-ntUiUgtRtkF8KuaxHffzhYxQxoXk6LAPHm7CVlFjdqS8Rx8xRkLkZVyo84E+pO2hcNFkOGVP/GxHhQ2s94O8zA==",
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@primeuix/utils/-/utils-0.3.2.tgz",
|
||||
"integrity": "sha512-B+nphqTQeq+i6JuICLdVWnDMjONome2sNz0xI65qIOyeB4EF12CoKRiCsxuZ5uKAkHi/0d1LqlQ9mIWRSdkavw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@primevue/core": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@primevue/core/-/core-4.0.5.tgz",
|
||||
"integrity": "sha512-DUCslDA93eUOVW0A1I3yoZgRLI4zmI2++loZQXbUF5jaXCwKiAza14+iyUU+cWH27VSq+jQnCEP9QJtPZiJJ0w==",
|
||||
"version": "4.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@primevue/core/-/core-4.2.5.tgz",
|
||||
"integrity": "sha512-+oWBIQs5dLd2Ini4KEVOlvPILk989EHAskiFS3R/dz3jeOllJDMZFcSp8V9ddV0R3yDaPdLVkfHm2Q5t42kU2Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@primeuix/styled": "^0.0.5",
|
||||
"@primeuix/utils": "^0.0.5"
|
||||
"@primeuix/styled": "^0.3.2",
|
||||
"@primeuix/utils": "^0.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.11.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.0.0"
|
||||
"vue": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@primevue/forms": {
|
||||
"version": "4.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@primevue/forms/-/forms-4.2.5.tgz",
|
||||
"integrity": "sha512-5jarJQ9Qv32bOo/0tY5bqR3JZI6+YmmoUQ2mjhVSbVElQsE4FNfhT7a7JwF+xgBPMPc8KWGNA1QB248HhPNVAg==",
|
||||
"dependencies": {
|
||||
"@primeuix/forms": "^0.0.2",
|
||||
"@primeuix/utils": "^0.3.2",
|
||||
"@primevue/core": "4.2.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@primevue/forms/node_modules/@primeuix/styled": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@primeuix/styled/-/styled-0.3.2.tgz",
|
||||
"integrity": "sha512-ColZes0+/WKqH4ob2x8DyNYf1NENpe5ZguOvx5yCLxaP8EIMVhLjWLO/3umJiDnQU4XXMLkn2mMHHw+fhTX/mw==",
|
||||
"dependencies": {
|
||||
"@primeuix/utils": "^0.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@primevue/forms/node_modules/@primeuix/utils": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@primeuix/utils/-/utils-0.3.2.tgz",
|
||||
"integrity": "sha512-B+nphqTQeq+i6JuICLdVWnDMjONome2sNz0xI65qIOyeB4EF12CoKRiCsxuZ5uKAkHi/0d1LqlQ9mIWRSdkavw==",
|
||||
"engines": {
|
||||
"node": ">=12.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@primevue/forms/node_modules/@primevue/core": {
|
||||
"version": "4.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@primevue/core/-/core-4.2.5.tgz",
|
||||
"integrity": "sha512-+oWBIQs5dLd2Ini4KEVOlvPILk989EHAskiFS3R/dz3jeOllJDMZFcSp8V9ddV0R3yDaPdLVkfHm2Q5t42kU2Q==",
|
||||
"dependencies": {
|
||||
"@primeuix/styled": "^0.3.2",
|
||||
"@primeuix/utils": "^0.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.11.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@primevue/icons": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@primevue/icons/-/icons-4.0.5.tgz",
|
||||
"integrity": "sha512-ZxR9W1wlAE2fTtUhrHyeMx5t0jNyAgxDcHPm0cNXpX8q1XF95rSM/qb48QKXIBDBrJ/xs57BcyCNADP/VDPY4g==",
|
||||
"version": "4.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@primevue/icons/-/icons-4.2.5.tgz",
|
||||
"integrity": "sha512-WFbUMZhQkXf/KmwcytkjGVeJ9aGEDXjP3uweOjQZMmRdEIxFnqYYpd90wE90JE1teZn3+TVnT4ZT7ejGyEXnFQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@primeuix/utils": "^0.0.5",
|
||||
"@primevue/core": "4.0.5"
|
||||
"@primeuix/utils": "^0.3.2",
|
||||
"@primevue/core": "4.2.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@primevue/themes": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@primevue/themes/-/themes-4.0.5.tgz",
|
||||
"integrity": "sha512-cRrAhOapOT8eFCTDwNdB/acg2ZEEkn7y6h6p188PYSjJsWnYK+D8eI1Js1ZB5HwWo4sWs3oR3Sy8bPwejnGbAw==",
|
||||
"version": "4.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@primevue/themes/-/themes-4.2.5.tgz",
|
||||
"integrity": "sha512-8F7yA36xYIKtNuAuyBdZZEks/bKDwlhH5WjpqGGB0FdwfAEoBYsynQ5sdqcT2Lb/NsajHmS5lc++Ttlvr1g1Lw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@primeuix/styled": "^0.0.5"
|
||||
"@primeuix/styled": "^0.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.11.0"
|
||||
@@ -4486,6 +4530,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",
|
||||
@@ -4917,6 +5051,41 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@trivago/prettier-plugin-sort-imports": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-5.2.0.tgz",
|
||||
"integrity": "sha512-yEIJ7xMKYQwyNRjxSdi4Gs37iszikAjxfky+3hu9bn24u8eHLJNDMAoOTyowp8p6EpSl8IQMdkfBx+WnJTttsw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@babel/generator": "^7.26.2",
|
||||
"@babel/parser": "^7.26.2",
|
||||
"@babel/traverse": "^7.25.9",
|
||||
"@babel/types": "^7.26.0",
|
||||
"javascript-natural-sort": "^0.7.1",
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">18.12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/compiler-sfc": "3.x",
|
||||
"prettier": "2.x - 3.x",
|
||||
"prettier-plugin-svelte": "3.x",
|
||||
"svelte": "4.x"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/compiler-sfc": {
|
||||
"optional": true
|
||||
},
|
||||
"prettier-plugin-svelte": {
|
||||
"optional": true
|
||||
},
|
||||
"svelte": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@tsconfig/node10": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
|
||||
@@ -5624,49 +5793,53 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-core": {
|
||||
"version": "3.4.31",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.31.tgz",
|
||||
"integrity": "sha512-skOiodXWTV3DxfDhB4rOf3OGalpITLlgCeOwb+Y9GJpfQ8ErigdBUHomBzvG78JoVE8MJoQsb+qhZiHfKeNeEg==",
|
||||
"version": "3.5.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz",
|
||||
"integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.24.7",
|
||||
"@vue/shared": "3.4.31",
|
||||
"@babel/parser": "^7.25.3",
|
||||
"@vue/shared": "3.5.13",
|
||||
"entities": "^4.5.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map-js": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-dom": {
|
||||
"version": "3.4.31",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.31.tgz",
|
||||
"integrity": "sha512-wK424WMXsG1IGMyDGyLqB+TbmEBFM78hIsOJ9QwUVLGrcSk0ak6zYty7Pj8ftm7nEtdU/DGQxAXp0/lM/2cEpQ==",
|
||||
"version": "3.5.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz",
|
||||
"integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-core": "3.4.31",
|
||||
"@vue/shared": "3.4.31"
|
||||
"@vue/compiler-core": "3.5.13",
|
||||
"@vue/shared": "3.5.13"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-sfc": {
|
||||
"version": "3.4.31",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.31.tgz",
|
||||
"integrity": "sha512-einJxqEw8IIJxzmnxmJBuK2usI+lJonl53foq+9etB2HAzlPjAS/wa7r0uUpXw5ByX3/0uswVSrjNb17vJm1kQ==",
|
||||
"version": "3.5.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz",
|
||||
"integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.24.7",
|
||||
"@vue/compiler-core": "3.4.31",
|
||||
"@vue/compiler-dom": "3.4.31",
|
||||
"@vue/compiler-ssr": "3.4.31",
|
||||
"@vue/shared": "3.4.31",
|
||||
"@babel/parser": "^7.25.3",
|
||||
"@vue/compiler-core": "3.5.13",
|
||||
"@vue/compiler-dom": "3.5.13",
|
||||
"@vue/compiler-ssr": "3.5.13",
|
||||
"@vue/shared": "3.5.13",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.30.10",
|
||||
"postcss": "^8.4.38",
|
||||
"magic-string": "^0.30.11",
|
||||
"postcss": "^8.4.48",
|
||||
"source-map-js": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-ssr": {
|
||||
"version": "3.4.31",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.31.tgz",
|
||||
"integrity": "sha512-RtefmITAje3fJ8FSg1gwgDhdKhZVntIVbwupdyZDSifZTRMiWxWehAOTCc8/KZDnBOcYQ4/9VWxsTbd3wT0hAA==",
|
||||
"version": "3.5.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz",
|
||||
"integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.4.31",
|
||||
"@vue/shared": "3.4.31"
|
||||
"@vue/compiler-dom": "3.5.13",
|
||||
"@vue/shared": "3.5.13"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-vue2": {
|
||||
@@ -5710,38 +5883,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/language-core/node_modules/@vue/compiler-core": {
|
||||
"version": "3.5.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz",
|
||||
"integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.25.3",
|
||||
"@vue/shared": "3.5.13",
|
||||
"entities": "^4.5.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map-js": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/language-core/node_modules/@vue/compiler-dom": {
|
||||
"version": "3.5.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz",
|
||||
"integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-core": "3.5.13",
|
||||
"@vue/shared": "3.5.13"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/language-core/node_modules/@vue/shared": {
|
||||
"version": "3.5.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz",
|
||||
"integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vue/language-core/node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
@@ -5769,49 +5910,54 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/reactivity": {
|
||||
"version": "3.4.31",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.31.tgz",
|
||||
"integrity": "sha512-VGkTani8SOoVkZNds1PfJ/T1SlAIOf8E58PGAhIOUDYPC4GAmFA2u/E14TDAFcf3vVDKunc4QqCe/SHr8xC65Q==",
|
||||
"version": "3.5.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz",
|
||||
"integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.4.31"
|
||||
"@vue/shared": "3.5.13"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-core": {
|
||||
"version": "3.4.31",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.31.tgz",
|
||||
"integrity": "sha512-LDkztxeUPazxG/p8c5JDDKPfkCDBkkiNLVNf7XZIUnJ+66GVGkP+TIh34+8LtPisZ+HMWl2zqhIw0xN5MwU1cw==",
|
||||
"version": "3.5.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz",
|
||||
"integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.4.31",
|
||||
"@vue/shared": "3.4.31"
|
||||
"@vue/reactivity": "3.5.13",
|
||||
"@vue/shared": "3.5.13"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-dom": {
|
||||
"version": "3.4.31",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.31.tgz",
|
||||
"integrity": "sha512-2Auws3mB7+lHhTFCg8E9ZWopA6Q6L455EcU7bzcQ4x6Dn4cCPuqj6S2oBZgN2a8vJRS/LSYYxwFFq2Hlx3Fsaw==",
|
||||
"version": "3.5.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz",
|
||||
"integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.4.31",
|
||||
"@vue/runtime-core": "3.4.31",
|
||||
"@vue/shared": "3.4.31",
|
||||
"@vue/reactivity": "3.5.13",
|
||||
"@vue/runtime-core": "3.5.13",
|
||||
"@vue/shared": "3.5.13",
|
||||
"csstype": "^3.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/server-renderer": {
|
||||
"version": "3.4.31",
|
||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.31.tgz",
|
||||
"integrity": "sha512-D5BLbdvrlR9PE3by9GaUp1gQXlCNadIZytMIb8H2h3FMWJd4oUfkUTEH2wAr3qxoRz25uxbTcbqd3WKlm9EHQA==",
|
||||
"version": "3.5.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz",
|
||||
"integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-ssr": "3.4.31",
|
||||
"@vue/shared": "3.4.31"
|
||||
"@vue/compiler-ssr": "3.5.13",
|
||||
"@vue/shared": "3.5.13"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "3.4.31"
|
||||
"vue": "3.5.13"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/shared": {
|
||||
"version": "3.4.31",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.31.tgz",
|
||||
"integrity": "sha512-Yp3wtJk//8cO4NItOPpi3QkLExAr/aLBGZMmTtW9WpdwBCJpRM6zj9WgWktXAl8IDIozwNMByT45JP3tO3ACWA=="
|
||||
"version": "3.5.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz",
|
||||
"integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vue/test-utils": {
|
||||
"version": "2.4.6",
|
||||
@@ -6937,9 +7083,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001640",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001640.tgz",
|
||||
"integrity": "sha512-lA4VMpW0PSUrFnkmVuEKBUovSWKhj7puyCg8StBChgu298N1AtuF1sKWEvfDuimSEDbhlb/KqPKC3fs1HbuQUA==",
|
||||
"version": "1.0.30001690",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz",
|
||||
"integrity": "sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -6954,7 +7100,8 @@
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
]
|
||||
],
|
||||
"license": "CC-BY-4.0"
|
||||
},
|
||||
"node_modules/ccount": {
|
||||
"version": "2.0.1",
|
||||
@@ -7744,7 +7891,8 @@
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/data-urls": {
|
||||
"version": "3.0.2",
|
||||
@@ -10353,6 +10501,13 @@
|
||||
"@pkgjs/parseargs": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/javascript-natural-sort": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
|
||||
"integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jest": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
|
||||
@@ -12106,15 +12261,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jsesc": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
|
||||
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
||||
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"jsesc": "bin/jsesc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/json-buffer": {
|
||||
@@ -14316,15 +14472,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
||||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
|
||||
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
@@ -15059,9 +15216,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
|
||||
"integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw=="
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
@@ -15225,9 +15383,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.47",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
|
||||
"integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
|
||||
"version": "8.5.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz",
|
||||
"integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -15242,9 +15400,10 @@
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.7",
|
||||
"picocolors": "^1.1.0",
|
||||
"nanoid": "^3.3.8",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -15417,15 +15576,15 @@
|
||||
"integrity": "sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw=="
|
||||
},
|
||||
"node_modules/primevue": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/primevue/-/primevue-4.0.5.tgz",
|
||||
"integrity": "sha512-MALszGIZ5SnEQy1XeZLBFhpMXQ1OS7D1U7H+l/JAX5U46RQ1vufo7NAiWbbV5/ADjPGw4uLplqMQxujkksNY2g==",
|
||||
"version": "4.2.5",
|
||||
"resolved": "https://registry.npmjs.org/primevue/-/primevue-4.2.5.tgz",
|
||||
"integrity": "sha512-7UMOIJvdFz4jQyhC76yhNdSlHtXvVpmE2JSo2ndUTBWjWJOkYyT562rQ4ayO+bMdJLtzBGqgY64I9ZfEvNd7vQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@primeuix/styled": "^0.0.5",
|
||||
"@primeuix/utils": "^0.0.5",
|
||||
"@primevue/core": "4.0.5",
|
||||
"@primevue/icons": "4.0.5"
|
||||
"@primeuix/styled": "^0.3.2",
|
||||
"@primeuix/utils": "^0.3.2",
|
||||
"@primevue/core": "4.2.5",
|
||||
"@primevue/icons": "4.2.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.11.0"
|
||||
@@ -18705,15 +18864,16 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vue": {
|
||||
"version": "3.4.31",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.31.tgz",
|
||||
"integrity": "sha512-njqRrOy7W3YLAlVqSKpBebtZpDVg21FPoaq1I7f/+qqBThK9ChAIjkRWgeP6Eat+8C+iia4P3OYqpATP21BCoQ==",
|
||||
"version": "3.5.13",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz",
|
||||
"integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.4.31",
|
||||
"@vue/compiler-sfc": "3.4.31",
|
||||
"@vue/runtime-dom": "3.4.31",
|
||||
"@vue/server-renderer": "3.4.31",
|
||||
"@vue/shared": "3.4.31"
|
||||
"@vue/compiler-dom": "3.5.13",
|
||||
"@vue/compiler-sfc": "3.5.13",
|
||||
"@vue/runtime-dom": "3.5.13",
|
||||
"@vue/server-renderer": "3.5.13",
|
||||
"@vue/shared": "3.5.13"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "*"
|
||||
@@ -19421,21 +19581,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": {
|
||||
|
||||
29
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"private": true,
|
||||
"version": "1.6.10",
|
||||
"version": "1.8.2",
|
||||
"type": "module",
|
||||
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
|
||||
"homepage": "https://comfy.org",
|
||||
@@ -17,11 +17,9 @@
|
||||
"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}'",
|
||||
"test": "jest --config jest.config.base.ts",
|
||||
"test:jest:fast": "jest --config jest.config.fast.ts",
|
||||
"test:jest:slow": "jest --config jest.config.slow.ts",
|
||||
"test:generate:examples": "npx tsx tests-ui/extractExamples",
|
||||
"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",
|
||||
"test:component": "vitest run src/components/",
|
||||
@@ -30,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",
|
||||
@@ -40,6 +39,7 @@
|
||||
"@lobehub/i18n-cli": "^1.20.0",
|
||||
"@pinia/testing": "^0.1.5",
|
||||
"@playwright/test": "^1.44.1",
|
||||
"@trivago/prettier-plugin-sort-imports": "^5.2.0",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/lodash": "^4.17.6",
|
||||
"@types/node": "^20.14.8",
|
||||
@@ -78,13 +78,16 @@
|
||||
"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.50",
|
||||
"@primevue/themes": "^4.0.5",
|
||||
"@comfyorg/comfyui-electron-types": "^0.4.11",
|
||||
"@comfyorg/litegraph": "^0.8.61",
|
||||
"@primevue/forms": "^4.2.5",
|
||||
"@primevue/themes": "^4.2.5",
|
||||
"@sentry/vue": "^8.48.0",
|
||||
"@tiptap/core": "^2.10.4",
|
||||
"@tiptap/extension-link": "^2.10.4",
|
||||
"@tiptap/extension-table": "^2.10.4",
|
||||
@@ -103,10 +106,10 @@
|
||||
"loglevel": "^1.9.2",
|
||||
"pinia": "^2.1.7",
|
||||
"primeicons": "^7.0.0",
|
||||
"primevue": "^4.0.5",
|
||||
"primevue": "^4.2.5",
|
||||
"three": "^0.170.0",
|
||||
"tiptap-markdown": "^0.8.10",
|
||||
"vue": "^3.4.31",
|
||||
"vue": "^3.5.13",
|
||||
"vue-i18n": "^9.13.1",
|
||||
"vue-router": "^4.4.3",
|
||||
"zod": "^3.23.8",
|
||||
|
||||
@@ -8,7 +8,7 @@ const config: PlaywrightTestConfig = {
|
||||
},
|
||||
reporter: 'list',
|
||||
timeout: 10000,
|
||||
testMatch: /collect-i18n\.ts/
|
||||
testMatch: /collect-i18n-.*\.ts/
|
||||
}
|
||||
|
||||
export default config
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
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 { formatCamelCase, normalizeI18nKey } from '../src/utils/formatUtil'
|
||||
import { ComfyNodeDefImpl } from '../src/stores/nodeDefStore'
|
||||
import type { ComfyCommandImpl } from '../src/stores/commandStore'
|
||||
import type { FormItem, SettingParams } from '../src/types/settingTypes'
|
||||
import type { ComfyApi } from '../src/scripts/api'
|
||||
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'
|
||||
|
||||
@@ -21,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
|
||||
@@ -128,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(
|
||||
@@ -240,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
@@ -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))
|
||||
})
|
||||
@@ -1,8 +1,10 @@
|
||||
import { copy } from 'fs-extra'
|
||||
import { config } from 'dotenv'
|
||||
import { copy } from 'fs-extra'
|
||||
|
||||
config()
|
||||
|
||||
const sourceDir = './dist'
|
||||
// eslint-disable-next-line no-undef
|
||||
const targetDir = process.env.DEPLOY_COMFYUI_DIR
|
||||
|
||||
copy(sourceDir, targetDir)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import {
|
||||
readFileSync,
|
||||
writeFileSync,
|
||||
readdirSync,
|
||||
mkdirSync,
|
||||
existsSync,
|
||||
rmSync
|
||||
mkdirSync,
|
||||
readFileSync,
|
||||
readdirSync,
|
||||
rmSync,
|
||||
writeFileSync
|
||||
} from 'fs'
|
||||
import { join, dirname } from 'path'
|
||||
import { dirname, join } from 'path'
|
||||
|
||||
// Ensure directories exist
|
||||
function ensureDir(dir: string) {
|
||||
|
||||
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)
|
||||
43
scripts/update-electron-types.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { execSync } from 'child_process'
|
||||
import { readFileSync } from 'fs'
|
||||
|
||||
const packageName = '@comfyorg/comfyui-electron-types'
|
||||
const description = 'desktop API types'
|
||||
|
||||
try {
|
||||
// Create a new branch
|
||||
console.log('Creating new branch...')
|
||||
const date = new Date()
|
||||
const isoDate = date.toISOString().split('T')[0]
|
||||
const timestamp = date.getTime()
|
||||
const branchName = `update-electron-types-${isoDate}-${timestamp}`
|
||||
execSync(`git checkout -b ${branchName} -t origin/main`, { stdio: 'inherit' })
|
||||
|
||||
// Update npm package to latest version
|
||||
console.log(`Updating ${description}...`)
|
||||
execSync(`npm install ${packageName}@latest`, {
|
||||
stdio: 'inherit'
|
||||
})
|
||||
|
||||
// Get the new version from package.json
|
||||
const packageLock = JSON.parse(readFileSync('./package-lock.json', 'utf8'))
|
||||
const newVersion = packageLock.packages[`node_modules/${packageName}`].version
|
||||
|
||||
// Stage changes
|
||||
const message = `[chore] Update electron-types to ${newVersion}`
|
||||
execSync('git add package.json package-lock.json', { stdio: 'inherit' })
|
||||
execSync(`git commit -m "${message}"`, { stdio: 'inherit' })
|
||||
|
||||
// Create the PR
|
||||
console.log('Creating PR...')
|
||||
execSync(
|
||||
`gh pr create --title "${message}" --label "dependencies" --body "Automated update of ${description} to version ${newVersion}."`,
|
||||
{ stdio: 'inherit' }
|
||||
)
|
||||
|
||||
console.log(
|
||||
`✅ Successfully created PR for ${description} update to ${newVersion}`
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('❌ Error during update process:', error.message)
|
||||
}
|
||||
14
src/App.vue
@@ -9,14 +9,16 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import config from '@/config'
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
import { useEventListener } from '@vueuse/core'
|
||||
import BlockUI from 'primevue/blockui'
|
||||
import ProgressSpinner from 'primevue/progressspinner'
|
||||
import { computed, onMounted } from 'vue'
|
||||
|
||||
import GlobalDialog from '@/components/dialog/GlobalDialog.vue'
|
||||
import { useEventListener } from '@vueuse/core'
|
||||
import { isElectron, showNativeMenu } from './utils/envUtil'
|
||||
import config from '@/config'
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
|
||||
import { electronAPI, isElectron } from './utils/envUtil'
|
||||
|
||||
const workspaceStore = useWorkspaceStore()
|
||||
const isLoading = computed<boolean>(() => workspaceStore.spinner)
|
||||
@@ -32,7 +34,7 @@ const showContextMenu = (event: PointerEvent) => {
|
||||
case target instanceof HTMLTextAreaElement:
|
||||
case target instanceof HTMLInputElement && target.type === 'text':
|
||||
// TODO: Context input menu explicitly for text input
|
||||
showNativeMenu({ type: 'text' })
|
||||
electronAPI()?.showContextMenu({ type: 'text' })
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -5,11 +5,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useTitle } from '@vueuse/core'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { useExecutionStore } from '@/stores/executionStore'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
import { useWorkflowStore } from '@/stores/workflowStore'
|
||||
import { useTitle } from '@vueuse/core'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const DEFAULT_TITLE = 'ComfyUI'
|
||||
const TITLE_SUFFIX = ' - ComfyUI'
|
||||
|
||||
@@ -46,13 +46,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
|
||||
import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore'
|
||||
import Splitter from 'primevue/splitter'
|
||||
import SplitterPanel from 'primevue/splitterpanel'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
|
||||
import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore'
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
const sidebarLocation = computed<'left' | 'right'>(() =>
|
||||
settingStore.get('Comfy.Sidebar.Location')
|
||||
|
||||
@@ -1,24 +1,31 @@
|
||||
<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">
|
||||
import Button from 'primevue/button'
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
import { computed, CSSProperties, watchEffect } from 'vue'
|
||||
import { CSSProperties, computed, watchEffect } from 'vue'
|
||||
|
||||
import { app } from '@/scripts/app'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
import { showNativeMenu } from '@/utils/envUtil'
|
||||
|
||||
const workspaceState = useWorkspaceStore()
|
||||
@@ -50,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"
|
||||
@@ -30,12 +31,13 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useQueueSettingsStore } from '@/stores/queueStore'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import InputNumber from 'primevue/inputnumber'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { useQueueSettingsStore } from '@/stores/queueStore'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
|
||||
interface Props {
|
||||
class?: string
|
||||
}
|
||||
|
||||
@@ -13,10 +13,6 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, inject, nextTick, onMounted, Ref, ref, watch } from 'vue'
|
||||
import Panel from 'primevue/panel'
|
||||
import ComfyQueueButton from './ComfyQueueButton.vue'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
import {
|
||||
useDraggable,
|
||||
useElementBounding,
|
||||
@@ -26,6 +22,12 @@ import {
|
||||
watchDebounced
|
||||
} from '@vueuse/core'
|
||||
import { clamp } from 'lodash'
|
||||
import Panel from 'primevue/panel'
|
||||
import { Ref, computed, inject, nextTick, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
|
||||
import ComfyQueueButton from './ComfyQueueButton.vue'
|
||||
|
||||
const settingsStore = useSettingStore()
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
</template>
|
||||
<template #item="{ item }">
|
||||
<Button
|
||||
:label="item.label"
|
||||
:label="String(item.label)"
|
||||
:icon="item.icon"
|
||||
:severity="item.key === queueMode ? 'primary' : 'secondary'"
|
||||
size="small"
|
||||
@@ -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>
|
||||
@@ -55,20 +57,22 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import SplitButton from 'primevue/splitbutton'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import Button from 'primevue/button'
|
||||
import BatchCountEdit from './BatchCountEdit.vue'
|
||||
import ButtonGroup from 'primevue/buttongroup'
|
||||
import SplitButton from 'primevue/splitbutton'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import {
|
||||
useQueuePendingTaskCountStore,
|
||||
useQueueSettingsStore
|
||||
} from '@/stores/queueStore'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed } from 'vue'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
|
||||
import BatchCountEdit from './BatchCountEdit.vue'
|
||||
|
||||
const workspaceStore = useWorkspaceStore()
|
||||
const queueCountStore = storeToRefs(useQueuePendingTaskCountStore())
|
||||
const { mode: queueMode } = storeToRefs(useQueueSettingsStore())
|
||||
|
||||
@@ -41,9 +41,10 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import Button from 'primevue/button'
|
||||
import Tabs from 'primevue/tabs'
|
||||
import TabList from 'primevue/tablist'
|
||||
import Tab from 'primevue/tab'
|
||||
import TabList from 'primevue/tablist'
|
||||
import Tabs from 'primevue/tabs'
|
||||
|
||||
import ExtensionSlot from '@/components/common/ExtensionSlot.vue'
|
||||
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, Ref } from 'vue'
|
||||
import { Ref, ref } from 'vue'
|
||||
|
||||
import { useTerminal } from '@/hooks/bottomPanelTabs/useTerminal'
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, Ref } from 'vue'
|
||||
import { IDisposable } from '@xterm/xterm'
|
||||
import { Ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
import type { useTerminal } from '@/hooks/bottomPanelTabs/useTerminal'
|
||||
import { electronAPI } from '@/utils/envUtil'
|
||||
import { IDisposable } from '@xterm/xterm'
|
||||
|
||||
import BaseTerminal from './BaseTerminal.vue'
|
||||
|
||||
const terminalCreated = (
|
||||
@@ -18,11 +20,16 @@ const terminalCreated = (
|
||||
let offData: IDisposable
|
||||
let offOutput: () => void
|
||||
|
||||
useAutoSize(root, true, true, () => {
|
||||
// If we aren't visible, don't resize
|
||||
if (!terminal.element?.offsetParent) return
|
||||
useAutoSize({
|
||||
root,
|
||||
autoRows: true,
|
||||
autoCols: true,
|
||||
onResize: () => {
|
||||
// If we aren't visible, don't resize
|
||||
if (!terminal.element?.offsetParent) return
|
||||
|
||||
terminalApi.resize(terminal.cols, terminal.rows)
|
||||
terminalApi.resize(terminal.cols, terminal.rows)
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
|
||||
@@ -10,15 +10,17 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, Ref, ref } from 'vue'
|
||||
import type { useTerminal } from '@/hooks/bottomPanelTabs/useTerminal'
|
||||
import { LogEntry, LogsWsMessage, TerminalSize } from '@/types/apiTypes'
|
||||
import { api } from '@/scripts/api'
|
||||
import { useExecutionStore } from '@/stores/executionStore'
|
||||
import { until } from '@vueuse/core'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import BaseTerminal from './BaseTerminal.vue'
|
||||
import ProgressSpinner from 'primevue/progressspinner'
|
||||
import { Ref, onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
import type { useTerminal } from '@/hooks/bottomPanelTabs/useTerminal'
|
||||
import { api } from '@/scripts/api'
|
||||
import { useExecutionStore } from '@/stores/executionStore'
|
||||
import { LogEntry, LogsWsMessage, TerminalSize } from '@/types/apiTypes'
|
||||
|
||||
import BaseTerminal from './BaseTerminal.vue'
|
||||
|
||||
const errorMessage = ref('')
|
||||
const loading = ref(true)
|
||||
@@ -27,7 +29,12 @@ const terminalCreated = (
|
||||
{ terminal, useAutoSize }: ReturnType<typeof useTerminal>,
|
||||
root: Ref<HTMLElement>
|
||||
) => {
|
||||
useAutoSize(root, true, false)
|
||||
// `autoCols` is false because we don't want the progress bar in the terminal
|
||||
// to render incorrectly as the progress bar is rendered based on the
|
||||
// server's terminal size.
|
||||
// Apply a min cols of 80 for colab environments
|
||||
// See https://github.com/comfyanonymous/ComfyUI/issues/6396
|
||||
useAutoSize({ root, autoRows: true, autoCols: false, minCols: 80 })
|
||||
|
||||
const update = (entries: Array<LogEntry>, size?: TerminalSize) => {
|
||||
if (size) {
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
renderFunction: () => HTMLElement
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
:options="colorOptions"
|
||||
optionLabel="name"
|
||||
dataKey="value"
|
||||
:allow-empty="false"
|
||||
>
|
||||
<template #option="slotProps">
|
||||
<div
|
||||
@@ -70,15 +71,16 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import Dialog from 'primevue/dialog'
|
||||
import SelectButton from 'primevue/selectbutton'
|
||||
import Button from 'primevue/button'
|
||||
import Divider from 'primevue/divider'
|
||||
import ColorPicker from 'primevue/colorpicker'
|
||||
import { useNodeBookmarkStore } from '@/stores/nodeBookmarkStore'
|
||||
import Dialog from 'primevue/dialog'
|
||||
import Divider from 'primevue/divider'
|
||||
import SelectButton from 'primevue/selectbutton'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { useNodeBookmarkStore } from '@/stores/nodeBookmarkStore'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
:label="$t('g.download') + ' (' + fileSize + ')'"
|
||||
size="small"
|
||||
outlined
|
||||
:disabled="props.error"
|
||||
:disabled="!!props.error"
|
||||
@click="triggerDownload"
|
||||
v-if="status === null || status === 'error'"
|
||||
icon="pi pi-download"
|
||||
@@ -30,7 +30,7 @@
|
||||
v-if="status === 'in_progress' || status === 'paused'"
|
||||
>
|
||||
<!-- Temporary fix for issue when % only comes into view only if the progress bar is large enough
|
||||
https://comfy-organization.slack.com/archives/C07H3GLKDPF/p1731551013385499
|
||||
https://comfy-organization.slack.com/archives/C07H3GLKDPF/p1731551013385499
|
||||
-->
|
||||
<ProgressBar
|
||||
class="flex-1"
|
||||
@@ -42,7 +42,7 @@
|
||||
class="file-action-button"
|
||||
size="small"
|
||||
outlined
|
||||
:disabled="props.error"
|
||||
:disabled="!!props.error"
|
||||
@click="triggerPauseDownload"
|
||||
v-if="status === 'in_progress'"
|
||||
icon="pi pi-pause-circle"
|
||||
@@ -53,7 +53,7 @@
|
||||
class="file-action-button"
|
||||
size="small"
|
||||
outlined
|
||||
:disabled="props.error"
|
||||
:disabled="!!props.error"
|
||||
@click="triggerResumeDownload"
|
||||
v-if="status === 'paused'"
|
||||
icon="pi pi-play-circle"
|
||||
@@ -64,7 +64,7 @@
|
||||
class="file-action-button"
|
||||
size="small"
|
||||
outlined
|
||||
:disabled="props.error"
|
||||
:disabled="!!props.error"
|
||||
@click="triggerCancelDownload"
|
||||
icon="pi pi-times-circle"
|
||||
severity="danger"
|
||||
@@ -75,13 +75,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useDownload } from '@/hooks/downloadHooks'
|
||||
import Button from 'primevue/button'
|
||||
import ProgressBar from 'primevue/progressbar'
|
||||
import { ref, computed } from 'vue'
|
||||
import { formatSize } from '@/utils/formatUtil'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { useDownload } from '@/hooks/downloadHooks'
|
||||
import { useElectronDownloadStore } from '@/stores/electronDownloadStore'
|
||||
import { formatSize } from '@/utils/formatUtil'
|
||||
|
||||
const props = defineProps<{
|
||||
url: string
|
||||
|
||||