diff --git a/.github/actions/start-comfyui-server/action.yml b/.github/actions/start-comfyui-server/action.yml new file mode 100644 index 000000000..2af727508 --- /dev/null +++ b/.github/actions/start-comfyui-server/action.yml @@ -0,0 +1,23 @@ +name: Start ComfyUI Server +description: 'Start ComfyUI server in a container environment (assumes ComfyUI is pre-installed)' + +inputs: + front_end_root: + description: 'Path to frontend dist directory' + required: false + default: '$GITHUB_WORKSPACE/dist' + timeout: + description: 'Timeout in seconds for server startup' + required: false + default: '600' + +runs: + using: 'composite' + steps: + - name: Copy devtools and start server + shell: bash + run: | + set -euo pipefail + cp -r ./tools/devtools/* /ComfyUI/custom_nodes/ComfyUI_devtools/ + cd /ComfyUI && python3 main.py --cpu --multi-user --front-end-root "${{ inputs.front_end_root }}" & + wait-for-it --service 127.0.0.1:8188 -t ${{ inputs.timeout }} diff --git a/.github/workflows/ci-tests-e2e-forks.yaml b/.github/workflows/ci-tests-e2e-forks.yaml index c1828b7fb..8f039f1c4 100644 --- a/.github/workflows/ci-tests-e2e-forks.yaml +++ b/.github/workflows/ci-tests-e2e-forks.yaml @@ -1,9 +1,9 @@ # Description: Deploys test results from forked PRs (forks can't access deployment secrets) -name: "CI: Tests E2E (Deploy for Forks)" +name: 'CI: Tests E2E (Deploy for Forks)' on: workflow_run: - workflows: ["CI: Tests E2E"] + workflows: ['CI: Tests E2E'] types: [requested, completed] env: @@ -81,6 +81,7 @@ jobs: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} GITHUB_TOKEN: ${{ github.token }} + GITHUB_SHA: ${{ github.event.workflow_run.head_sha }} run: | # Rename merged report if exists [ -d "reports/playwright-report-chromium-merged" ] && \ diff --git a/.github/workflows/ci-tests-e2e.yaml b/.github/workflows/ci-tests-e2e.yaml index 5a2cc028b..90bf4112c 100644 --- a/.github/workflows/ci-tests-e2e.yaml +++ b/.github/workflows/ci-tests-e2e.yaml @@ -1,5 +1,5 @@ # Description: End-to-end testing with Playwright across multiple browsers, deploys test reports to Cloudflare Pages -name: "CI: Tests E2E" +name: 'CI: Tests E2E' on: push: @@ -15,66 +15,56 @@ concurrency: jobs: setup: runs-on: ubuntu-latest - outputs: - cache-key: ${{ steps.cache-key.outputs.key }} steps: - name: Checkout repository uses: actions/checkout@v5 - - # Setup Test Environment, build frontend but do not start server yet - - name: Setup ComfyUI server - uses: ./.github/actions/setup-comfyui-server - name: Setup frontend uses: ./.github/actions/setup-frontend with: include_build_step: true - - name: Setup Playwright - uses: ./.github/actions/setup-playwright # Setup Playwright and cache browsers - # Save the entire workspace as cache for later test jobs to restore - - name: Generate cache key - id: cache-key - run: echo "key=$(date +%s)" >> $GITHUB_OUTPUT - - name: Save cache - uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 + # Upload only built dist/ (containerized test jobs will pnpm install without cache) + - name: Upload built frontend + uses: actions/upload-artifact@v4 with: - path: . - key: comfyui-setup-${{ steps.cache-key.outputs.key }} + name: frontend-dist + path: dist/ + retention-days: 1 # Sharded chromium tests playwright-tests-chromium-sharded: needs: setup runs-on: ubuntu-latest timeout-minutes: 60 + container: + image: ghcr.io/comfy-org/comfyui-ci-container:0.0.10 + credentials: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} permissions: contents: read + packages: read strategy: fail-fast: false matrix: shardIndex: [1, 2, 3, 4, 5, 6, 7, 8] shardTotal: [8] steps: - # download built frontend repo from setup job - - name: Wait for cache propagation - run: sleep 10 - - name: Restore cached setup - uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 + - name: Checkout repository + uses: actions/checkout@v5 + - name: Download built frontend + uses: actions/download-artifact@v4 with: - fail-on-cache-miss: true - path: . - key: comfyui-setup-${{ needs.setup.outputs.cache-key }} + name: frontend-dist + path: dist/ - # Setup Test Environment for this runner, start server, use cached built frontend ./dist from 'setup' job - - name: Setup ComfyUI server - uses: ./.github/actions/setup-comfyui-server - with: - launch_server: true - - name: Setup nodejs, pnpm, reuse built frontend - uses: ./.github/actions/setup-frontend - - name: Setup Playwright - uses: ./.github/actions/setup-playwright + - name: Start ComfyUI server + uses: ./.github/actions/start-comfyui-server - # Run sharded tests and upload sharded reports + - name: Install frontend deps + run: pnpm install --frozen-lockfile + + # Run sharded tests (browsers pre-installed in container) - name: Run Playwright tests (Shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }}) id: playwright run: pnpm exec playwright test --project=chromium --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --reporter=blob @@ -94,39 +84,37 @@ jobs: timeout-minutes: 15 needs: setup runs-on: ubuntu-latest + container: + image: ghcr.io/comfy-org/comfyui-ci-container:0.0.10 + credentials: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} permissions: contents: read + packages: read strategy: fail-fast: false matrix: browser: [chromium-2x, chromium-0.5x, mobile-chrome] steps: - # download built frontend repo from setup job - - name: Wait for cache propagation - run: sleep 10 - - name: Restore cached setup - uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 + - name: Checkout repository + uses: actions/checkout@v5 + - name: Download built frontend + uses: actions/download-artifact@v4 with: - fail-on-cache-miss: true - path: . - key: comfyui-setup-${{ needs.setup.outputs.cache-key }} + name: frontend-dist + path: dist/ - # Setup Test Environment for this runner, start server, use cached built frontend ./dist from 'setup' job - - name: Setup ComfyUI server - uses: ./.github/actions/setup-comfyui-server - with: - launch_server: true - - name: Setup nodejs, pnpm, reuse built frontend - uses: ./.github/actions/setup-frontend - - name: Setup Playwright - uses: ./.github/actions/setup-playwright + - name: Start ComfyUI server + uses: ./.github/actions/start-comfyui-server - # Run tests and upload reports + - name: Install frontend deps + run: pnpm install --frozen-lockfile + + # Run tests (browsers pre-installed in container) - name: Run Playwright tests (${{ matrix.browser }}) id: playwright - run: | - # Run tests with blob reporter first - pnpm exec playwright test --project=${{ matrix.browser }} --reporter=blob + run: pnpm exec playwright test --project=${{ matrix.browser }} --reporter=blob env: PLAYWRIGHT_BLOB_OUTPUT_DIR: ./blob-report @@ -147,7 +135,7 @@ jobs: path: ./playwright-report/ retention-days: 30 - # Merge sharded test reports + # Merge sharded test reports (no container needed - only runs CLI) merge-reports: needs: [playwright-tests-chromium-sharded] runs-on: ubuntu-latest @@ -156,11 +144,9 @@ jobs: - name: Checkout repository uses: actions/checkout@v5 - # Setup Test Environment, we only need playwright to merge reports + # Setup pnpm/node to run playwright merge-reports (no browsers needed) - name: Setup frontend uses: ./.github/actions/setup-frontend - - name: Setup Playwright - uses: ./.github/actions/setup-playwright - name: Download blob reports uses: actions/download-artifact@v4 @@ -236,6 +222,7 @@ jobs: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} GITHUB_TOKEN: ${{ github.token }} + GITHUB_SHA: ${{ github.event.pull_request.head.sha }} run: | bash ./scripts/cicd/pr-playwright-deploy-and-comment.sh \ "${{ github.event.pull_request.number }}" \ diff --git a/.github/workflows/pr-update-playwright-expectations.yaml b/.github/workflows/pr-update-playwright-expectations.yaml index d78ad96bf..0ca696721 100644 --- a/.github/workflows/pr-update-playwright-expectations.yaml +++ b/.github/workflows/pr-update-playwright-expectations.yaml @@ -25,7 +25,6 @@ jobs: ) && startsWith(github.event.comment.body, '/update-playwright') ) outputs: - cache-key: ${{ steps.cache-key.outputs.key }} pr-number: ${{ steps.pr-info.outputs.pr-number }} branch: ${{ steps.pr-info.outputs.branch }} comment-id: ${{ steps.find-update-comment.outputs.comment-id }} @@ -64,70 +63,63 @@ jobs: uses: ./.github/actions/setup-frontend with: include_build_step: true - # Save expensive build artifacts (Python env, built frontend, node_modules) - # Source code will be checked out fresh in sharded jobs - - name: Generate cache key - id: cache-key - run: echo "key=$(date +%s)" >> $GITHUB_OUTPUT - - name: Save cache - uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 + + # Upload built dist/ (containerized test jobs will pnpm install without cache) + - name: Upload built frontend + uses: actions/upload-artifact@v4 with: - path: | - ComfyUI - dist - key: comfyui-setup-${{ steps.cache-key.outputs.key }} + name: frontend-dist + path: dist/ + retention-days: 1 # Sharded snapshot updates update-snapshots-sharded: needs: setup runs-on: ubuntu-latest + container: + image: ghcr.io/comfy-org/comfyui-ci-container:0.0.10 + credentials: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + permissions: + contents: read + packages: read strategy: fail-fast: false matrix: shardIndex: [1, 2, 3, 4] shardTotal: [4] steps: - # Checkout source code fresh (not cached) - name: Checkout repository uses: actions/checkout@v5 with: ref: ${{ needs.setup.outputs.branch }} - - # Restore expensive build artifacts from setup job - - name: Restore cached artifacts - uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 + - name: Download built frontend + uses: actions/download-artifact@v4 with: - fail-on-cache-miss: true - path: | - ComfyUI - dist - key: comfyui-setup-${{ needs.setup.outputs.cache-key }} + name: frontend-dist + path: dist/ - - name: Setup ComfyUI server (from cache) - uses: ./.github/actions/setup-comfyui-server - with: - launch_server: true + - name: Start ComfyUI server + uses: ./.github/actions/start-comfyui-server - - name: Setup nodejs, pnpm, reuse built frontend - uses: ./.github/actions/setup-frontend + - name: Install frontend deps + run: pnpm install --frozen-lockfile - - name: Setup Playwright - uses: ./.github/actions/setup-playwright - - # Run sharded tests with snapshot updates + # Run sharded tests with snapshot updates (browsers pre-installed in container) - name: Update snapshots (Shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }}) id: playwright-tests run: pnpm exec playwright test --update-snapshots --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} continue-on-error: true - # Identify and stage only changed snapshot files - name: Stage changed snapshot files id: changed-snapshots + shell: bash run: | set -euo pipefail - echo "==========================================" - echo "STAGING CHANGED SNAPSHOTS (Shard ${{ matrix.shardIndex }})" - echo "==========================================" + + # Configure git safe.directory for container environment + git config --global --add safe.directory "$(pwd)" # Get list of changed snapshot files (including untracked/new files) changed_files=$( ( @@ -136,43 +128,25 @@ jobs: ) | sort -u | grep -E '\-snapshots/' || true ) if [ -z "$changed_files" ]; then - echo "No snapshot changes in this shard" + echo "No snapshot changes in shard ${{ matrix.shardIndex }}" echo "has-changes=false" >> $GITHUB_OUTPUT exit 0 fi - echo "✓ Found changed files:" - echo "$changed_files" file_count=$(echo "$changed_files" | wc -l) - echo "Count: $file_count" + echo "Shard ${{ matrix.shardIndex }}: $file_count changed snapshot(s):" + echo "$changed_files" echo "has-changes=true" >> $GITHUB_OUTPUT - echo "" - # Create staging directory + # Copy changed files to staging directory mkdir -p /tmp/changed_snapshots_shard - - # Copy only changed files, preserving directory structure - # Strip 'browser_tests/' prefix to avoid double nesting - echo "Copying changed files to staging directory..." while IFS= read -r file; do - # Skip paths that no longer exist (e.g. deletions) - if [ ! -f "$file" ]; then - echo " → (skipped; not a file) $file" - continue - fi - # Remove 'browser_tests/' prefix + [ -f "$file" ] || continue file_without_prefix="${file#browser_tests/}" - # Create parent directories mkdir -p "/tmp/changed_snapshots_shard/$(dirname "$file_without_prefix")" - # Copy file cp "$file" "/tmp/changed_snapshots_shard/$file_without_prefix" - echo " → $file_without_prefix" done <<< "$changed_files" - echo "" - echo "Staged files for upload:" - find /tmp/changed_snapshots_shard -type f - # Upload ONLY the changed files from this shard - name: Upload changed snapshots uses: actions/upload-artifact@v4 @@ -213,9 +187,15 @@ jobs: echo "==========================================" echo "DOWNLOADED SNAPSHOT FILES" echo "==========================================" - find ./downloaded-snapshots -type f - echo "" - echo "Total files: $(find ./downloaded-snapshots -type f | wc -l)" + if [ -d "./downloaded-snapshots" ]; then + find ./downloaded-snapshots -type f + echo "" + echo "Total files: $(find ./downloaded-snapshots -type f | wc -l)" + else + echo "No snapshot artifacts downloaded (no changes in any shard)" + echo "" + echo "Total files: 0" + fi # Merge only the changed files into browser_tests - name: Merge changed snapshots @@ -226,6 +206,16 @@ jobs: echo "MERGING CHANGED SNAPSHOTS" echo "==========================================" + # Check if any artifacts were downloaded + if [ ! -d "./downloaded-snapshots" ]; then + echo "No snapshot artifacts to merge" + echo "==========================================" + echo "MERGE COMPLETE" + echo "==========================================" + echo "Shards merged: 0" + exit 0 + fi + # Verify target directory exists if [ ! -d "browser_tests" ]; then echo "::error::Target directory 'browser_tests' does not exist" diff --git a/.i18nrc.cjs b/.i18nrc.cjs index 53acf0546..86ce06eaa 100644 --- a/.i18nrc.cjs +++ b/.i18nrc.cjs @@ -6,11 +6,12 @@ const { defineConfig } = require('@lobehub/i18n-cli'); module.exports = defineConfig({ modelName: 'gpt-4.1', splitToken: 1024, + saveImmediately: true, entry: 'src/locales/en', entryLocale: 'en', output: 'src/locales', - outputLocales: ['zh', 'zh-TW', 'ru', 'ja', 'ko', 'fr', 'es', 'ar', 'tr', 'pt-BR'], - reference: `Special names to keep untranslated: flux, photomaker, clip, vae, cfg, stable audio, stable cascade, stable zero, controlnet, lora, HiDream. + outputLocales: ['zh', 'zh-TW', 'ru', 'ja', 'ko', 'fr', 'es', 'ar', 'tr', 'pt-BR', 'fa'], + reference: `Special names to keep untranslated: flux, photomaker, clip, vae, cfg, stable audio, stable cascade, stable zero, controlnet, lora, HiDream, Civitai, Hugging Face. 'latent' is the short form of 'latent space'. 'mask' is in the context of image processing. @@ -18,5 +19,11 @@ module.exports = defineConfig({ - For 'zh' locale: Use ONLY Simplified Chinese characters (简体中文). Common examples: 节点 (not 節點), 画布 (not 畫布), 图像 (not 圖像), 选择 (not 選擇), 减小 (not 減小). - For 'zh-TW' locale: Use ONLY Traditional Chinese characters (繁體中文) with Taiwan-specific terminology. - NEVER mix Simplified and Traditional Chinese characters within the same locale. + + IMPORTANT Persian Translation Guidelines: + - For 'fa' locale: Use formal Persian (فارسی رسمی) for professional tone throughout the UI. + - Keep commonly used technical terms in English when they are standard in Persian software (e.g., node, workflow). + - Use Arabic-Indic numerals (۰-۹) for numbers where appropriate. + - Maintain consistency with terminology used in Persian software and design applications. ` }); diff --git a/.storybook/main.ts b/.storybook/main.ts index 897094ade..5b7c126e9 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -7,7 +7,7 @@ import type { InlineConfig } from 'vite' const config: StorybookConfig = { stories: ['../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], - addons: ['@storybook/addon-docs'], + addons: ['@storybook/addon-docs', '@storybook/addon-mcp'], framework: { name: '@storybook/vue3-vite', options: {} @@ -69,9 +69,32 @@ const config: StorybookConfig = { allowedHosts: true }, resolve: { - alias: { - '@': process.cwd() + '/src' - } + alias: [ + { + find: '@/composables/queue/useJobList', + replacement: process.cwd() + '/src/storybook/mocks/useJobList.ts' + }, + { + find: '@/composables/queue/useJobActions', + replacement: process.cwd() + '/src/storybook/mocks/useJobActions.ts' + }, + { + find: '@/utils/formatUtil', + replacement: + process.cwd() + + '/packages/shared-frontend-utils/src/formatUtil.ts' + }, + { + find: '@/utils/networkUtil', + replacement: + process.cwd() + + '/packages/shared-frontend-utils/src/networkUtil.ts' + }, + { + find: '@', + replacement: process.cwd() + '/src' + } + ] }, esbuild: { // Prevent minification of identifiers to preserve _sfc_main diff --git a/AGENTS.md b/AGENTS.md index af0ab12a5..743572be3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -63,6 +63,9 @@ The project uses **Nx** for build orchestration and task management - Imports: - sorted/grouped by plugin - run `pnpm format` before committing + - use separate `import type` statements, not inline `type` in mixed imports + - ✅ `import type { Foo } from './foo'` + `import { bar } from './foo'` + - ❌ `import { bar, type Foo } from './foo'` - ESLint: - Vue + TS rules - no floating promises @@ -119,7 +122,10 @@ The project uses **Nx** for build orchestration and task management - Prefer reactive props destructuring to `const props = defineProps<...>` - Do not use `withDefaults` or runtime props declaration - Do not import Vue macros unnecessarily - - Prefer `useModel` to separately defining a prop and emit + - Prefer `defineModel` to separately defining a prop and emit for v-model bindings + - Define slots via template usage, not `defineSlots` + - Use same-name shorthand for slot prop bindings: `:isExpanded` instead of `:is-expanded="isExpanded"` + - Derive component types using `vue-component-type-helpers` (`ComponentProps`, `ComponentSlots`) instead of separate type files - Be judicious with addition of new refs or other state - If it's possible to accomplish the design goals with just a prop, don't add a `ref` - If it's possible to use the `ref` or prop directly, don't add a `computed` @@ -137,7 +143,7 @@ The project uses **Nx** for build orchestration and task management 8. Implement proper error handling 9. Follow Vue 3 style guide and naming conventions 10. Use Vite for fast development and building -11. Use vue-i18n in composition API for any string literals. Place new translation entries in src/locales/en/main.json +11. Use vue-i18n in composition API for any string literals. Place new translation entries in src/locales/en/main.json. Use the plurals system in i18n instead of hardcoding pluralization in templates. 12. Avoid new usage of PrimeVue components 13. Write tests for all changes, especially bug fixes to catch future regressions 14. Write code that is expressive and self-documenting to the furthest degree possible. This reduces the need for code comments which can get out of sync with the code itself. Try to avoid comments unless absolutely necessary @@ -155,6 +161,8 @@ The project uses **Nx** for build orchestration and task management ## Testing Guidelines +See @docs/testing/*.md for detailed patterns. + - Frameworks: - Vitest (unit/component, happy-dom) - Playwright (E2E) @@ -266,3 +274,18 @@ When referencing Comfy-Org repos: - Always use `import { cn } from '@/utils/tailwindUtil'` - e.g. `
` - Use `cn()` inline in the template when feasible instead of creating a `computed` to hold the value +- NEVER use `!important` or the `!` important prefix for tailwind classes + - Find existing `!important` classes that are interfering with the styling and propose corrections of those instead. +- NEVER use arbitrary percentage values like `w-[80%]` when a Tailwind fraction utility exists + - Use `w-4/5` instead of `w-[80%]`, `w-1/2` instead of `w-[50%]`, etc. + +## Agent-only rules + +Rules for agent-based coding tasks. + +### Temporary Files + +- Put planning documents under `/temp/plans/` +- Put scripts used under `/temp/scripts/` +- Put summaries of work performed under `/temp/summaries/` +- Put TODOs and status updates under `/temp/in_progress/` diff --git a/CODEOWNERS b/CODEOWNERS index 05e8d324c..fcba1e400 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -46,7 +46,6 @@ # Mask Editor /src/extensions/core/maskeditor.ts @trsommer @brucew4yn3rp /src/extensions/core/maskEditorLayerFilenames.ts @trsommer @brucew4yn3rp -/src/extensions/core/maskEditorOld.ts @trsommer @brucew4yn3rp # 3D /src/extensions/core/load3d.ts @jtydhr88 diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md new file mode 100644 index 000000000..ff6a1191d --- /dev/null +++ b/THIRD_PARTY_NOTICES.md @@ -0,0 +1 @@ +AMD and the AMD Arrow logo are trademarks of Advanced Micro Devices, Inc. \ No newline at end of file diff --git a/apps/desktop-ui/package.json b/apps/desktop-ui/package.json index a3d36f1a5..ed65e99a5 100644 --- a/apps/desktop-ui/package.json +++ b/apps/desktop-ui/package.json @@ -1,6 +1,6 @@ { "name": "@comfyorg/desktop-ui", - "version": "0.0.4", + "version": "0.0.6", "type": "module", "nx": { "tags": [ diff --git a/apps/desktop-ui/public/assets/images/amd-rocm-logo.png b/apps/desktop-ui/public/assets/images/amd-rocm-logo.png new file mode 100644 index 000000000..82de495cd Binary files /dev/null and b/apps/desktop-ui/public/assets/images/amd-rocm-logo.png differ diff --git a/apps/desktop-ui/src/components/install/GpuPicker.stories.ts b/apps/desktop-ui/src/components/install/GpuPicker.stories.ts new file mode 100644 index 000000000..d49893c38 --- /dev/null +++ b/apps/desktop-ui/src/components/install/GpuPicker.stories.ts @@ -0,0 +1,84 @@ +// eslint-disable-next-line storybook/no-renderer-packages +import type { Meta, StoryObj } from '@storybook/vue3' +import type { + ElectronAPI, + TorchDeviceType +} from '@comfyorg/comfyui-electron-types' +import { ref } from 'vue' + +import GpuPicker from './GpuPicker.vue' + +type Platform = ReturnTypeHoneyToast is hidden when visible=false. Nothing appears at the bottom.
+ +