From cf9847a11e7b352779f10d294f114ed5585df2c8 Mon Sep 17 00:00:00 2001 From: Benjamin Lu Date: Tue, 19 Aug 2025 12:52:22 -0400 Subject: [PATCH 01/21] Remove PR checks workflows (#5099) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Claude --- .github/workflows/pr-checks.yml | 154 ------------------------------- .github/workflows/pr-comment.yml | 149 ------------------------------ 2 files changed, 303 deletions(-) delete mode 100644 .github/workflows/pr-checks.yml delete mode 100644 .github/workflows/pr-comment.yml diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml deleted file mode 100644 index b49eb544a..000000000 --- a/.github/workflows/pr-checks.yml +++ /dev/null @@ -1,154 +0,0 @@ -name: PR Checks -on: - pull_request: - types: [opened, edited, synchronize, reopened] - -permissions: - contents: read - pull-requests: read - -jobs: - analyze: - runs-on: ubuntu-latest - outputs: - should_run: ${{ steps.check-changes.outputs.should_run }} - has_browser_tests: ${{ steps.check-coverage.outputs.has_browser_tests }} - has_screen_recording: ${{ steps.check-recording.outputs.has_recording }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Ensure base branch is available - run: | - # Fetch the specific base commit to ensure it's available for git diff - git fetch origin ${{ github.event.pull_request.base.sha }} - - - name: Check if significant changes exist - id: check-changes - run: | - # Get list of changed files - CHANGED_FILES=$(git diff --name-only ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }}) - - # Filter for src/ files - SRC_FILES=$(echo "$CHANGED_FILES" | grep '^src/' || true) - - if [ -z "$SRC_FILES" ]; then - echo "No src/ files changed" - echo "should_run=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - - # Count lines changed in src files - TOTAL_LINES=0 - for file in $SRC_FILES; do - if [ -f "$file" ]; then - # Count added lines (non-empty) - ADDED=$(git diff ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }} -- "$file" | grep '^+' | grep -v '^+++' | grep -v '^+$' | wc -l) - # Count removed lines (non-empty) - REMOVED=$(git diff ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }} -- "$file" | grep '^-' | grep -v '^---' | grep -v '^-$' | wc -l) - TOTAL_LINES=$((TOTAL_LINES + ADDED + REMOVED)) - fi - done - - echo "Total lines changed in src/: $TOTAL_LINES" - - if [ $TOTAL_LINES -gt 3 ]; then - echo "should_run=true" >> "$GITHUB_OUTPUT" - else - echo "should_run=false" >> "$GITHUB_OUTPUT" - fi - - - name: Check browser test coverage - id: check-coverage - if: steps.check-changes.outputs.should_run == 'true' - run: | - # Check if browser tests were updated - BROWSER_TEST_CHANGES=$(git diff --name-only ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }} | grep '^browser_tests/.*\.ts$' || true) - - if [ -n "$BROWSER_TEST_CHANGES" ]; then - echo "has_browser_tests=true" >> "$GITHUB_OUTPUT" - else - echo "has_browser_tests=false" >> "$GITHUB_OUTPUT" - fi - - - name: Check for screen recording - id: check-recording - if: steps.check-changes.outputs.should_run == 'true' - env: - PR_BODY: ${{ github.event.pull_request.body }} - run: | - # Check PR body for screen recording - # Check for GitHub user attachments or YouTube links - if echo "$PR_BODY" | grep -qiE 'github\.com/user-attachments/assets/[a-f0-9-]+|youtube\.com/watch|youtu\.be/'; then - echo "has_recording=true" >> "$GITHUB_OUTPUT" - else - echo "has_recording=false" >> "$GITHUB_OUTPUT" - fi - - - name: Final check and create results - id: final-check - if: always() - run: | - # Initialize results - WARNINGS_JSON="" - - # Only run checks if should_run is true - if [ "${{ steps.check-changes.outputs.should_run }}" == "true" ]; then - # Check browser test coverage - if [ "${{ steps.check-coverage.outputs.has_browser_tests }}" != "true" ]; then - if [ -n "$WARNINGS_JSON" ]; then - WARNINGS_JSON="${WARNINGS_JSON}," - fi - WARNINGS_JSON="${WARNINGS_JSON}{\"message\":\"⚠️ **Warning: E2E Test Coverage Missing**\\n\\nIf this PR modifies behavior that can be covered by browser-based E2E tests, those tests are required. PRs lacking applicable test coverage may not be reviewed until added. Please add or update browser tests to ensure code quality and prevent regressions.\"}" - fi - - # Check screen recording - if [ "${{ steps.check-recording.outputs.has_recording }}" != "true" ]; then - if [ -n "$WARNINGS_JSON" ]; then - WARNINGS_JSON="${WARNINGS_JSON}," - fi - WARNINGS_JSON="${WARNINGS_JSON}{\"message\":\"⚠️ **Warning: Visual Documentation Missing**\\n\\nIf this PR changes user-facing behavior, visual proof (screen recording or screenshot) is required. PRs without applicable visual documentation may not be reviewed until provided.\\nYou can add it by:\\n\\n- GitHub: Drag & drop media directly into the PR description\\n\\n- YouTube: Include a link to a short demo\"}" - fi - fi - - # Create results JSON - if [ -n "$WARNINGS_JSON" ]; then - # Create JSON with warnings - cat > pr-check-results.json << EOF - { - "fails": [], - "warnings": [$WARNINGS_JSON], - "messages": [], - "markdowns": [] - } - EOF - echo "failed=false" >> "$GITHUB_OUTPUT" - else - # Create JSON with success - cat > pr-check-results.json << 'EOF' - { - "fails": [], - "warnings": [], - "messages": [], - "markdowns": [] - } - EOF - echo "failed=false" >> "$GITHUB_OUTPUT" - fi - - # Write PR metadata - echo "${{ github.event.pull_request.number }}" > pr-number.txt - echo "${{ github.event.pull_request.head.sha }}" > pr-sha.txt - - - name: Upload results - uses: actions/upload-artifact@v4 - if: always() - with: - name: pr-check-results-${{ github.run_id }} - path: | - pr-check-results.json - pr-number.txt - pr-sha.txt - retention-days: 1 \ No newline at end of file diff --git a/.github/workflows/pr-comment.yml b/.github/workflows/pr-comment.yml deleted file mode 100644 index 5ae8cf83a..000000000 --- a/.github/workflows/pr-comment.yml +++ /dev/null @@ -1,149 +0,0 @@ -name: PR Comment -on: - workflow_run: - workflows: ["PR Checks"] - types: [completed] - -permissions: - pull-requests: write - issues: write - statuses: write - -jobs: - comment: - if: github.event.workflow_run.event == 'pull_request' - runs-on: ubuntu-latest - steps: - - name: Download artifacts - uses: actions/download-artifact@v4 - with: - name: pr-check-results-${{ github.event.workflow_run.id }} - path: /tmp/pr-artifacts - github-token: ${{ secrets.GITHUB_TOKEN }} - run-id: ${{ github.event.workflow_run.id }} - - - name: Post results - uses: actions/github-script@v7 - with: - script: | - const fs = require('fs'); - const path = require('path'); - - // Helper function to safely read files - function safeReadFile(filePath) { - try { - if (!fs.existsSync(filePath)) return null; - return fs.readFileSync(filePath, 'utf8').trim(); - } catch (e) { - console.error(`Error reading ${filePath}:`, e); - return null; - } - } - - // Read artifact files - const artifactDir = '/tmp/pr-artifacts'; - const prNumber = safeReadFile(path.join(artifactDir, 'pr-number.txt')); - const prSha = safeReadFile(path.join(artifactDir, 'pr-sha.txt')); - const resultsJson = safeReadFile(path.join(artifactDir, 'pr-check-results.json')); - - // Validate PR number - if (!prNumber || isNaN(parseInt(prNumber))) { - throw new Error('Invalid or missing PR number'); - } - - // Parse and validate results - let results; - try { - results = JSON.parse(resultsJson || '{}'); - } catch (e) { - console.error('Failed to parse check results:', e); - - // Post error comment - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: parseInt(prNumber), - body: `⚠️ PR checks failed to complete properly. Error parsing results: ${e.message}` - }); - return; - } - - // Format check messages - const messages = []; - - if (results.fails && results.fails.length > 0) { - messages.push('### ❌ Failures\n' + results.fails.map(f => f.message).join('\n\n')); - } - - if (results.warnings && results.warnings.length > 0) { - messages.push('### ⚠️ Warnings\n' + results.warnings.map(w => w.message).join('\n\n')); - } - - if (results.messages && results.messages.length > 0) { - messages.push('### 💬 Messages\n' + results.messages.map(m => m.message).join('\n\n')); - } - - if (results.markdowns && results.markdowns.length > 0) { - messages.push(...results.markdowns.map(m => m.message)); - } - - // Find existing bot comment - const comments = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: parseInt(prNumber) - }); - - const botComment = comments.data.find(comment => - comment.user.type === 'Bot' && - comment.body.includes('') - ); - - // Post comment if there are any messages - if (messages.length > 0) { - const body = messages.join('\n\n'); - const commentBody = `\n${body}`; - - if (botComment) { - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: botComment.id, - body: commentBody - }); - } else { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: parseInt(prNumber), - body: commentBody - }); - } - } else { - // No messages - delete existing comment if present - if (botComment) { - await github.rest.issues.deleteComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: botComment.id - }); - } - } - - // Set commit status based on failures - if (prSha) { - const hasFailures = results.fails && results.fails.length > 0; - const hasWarnings = results.warnings && results.warnings.length > 0; - await github.rest.repos.createCommitStatus({ - owner: context.repo.owner, - repo: context.repo.repo, - sha: prSha, - state: hasFailures ? 'failure' : 'success', - context: 'pr-checks', - description: hasFailures - ? `${results.fails.length} check(s) failed` - : hasWarnings - ? `${results.warnings.length} warning(s)` - : 'All checks passed' - }); - } \ No newline at end of file From 2240645a7dee3504881ec0c0e5062b3aed2342d4 Mon Sep 17 00:00:00 2001 From: snomiao Date: Wed, 20 Aug 2025 01:22:45 +0800 Subject: [PATCH 02/21] [feat] Add Cloudflare Pages deployment for Playwright test reports (#5045) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [feat] Add Cloudflare Pages deployment for Playwright test reports - Deploy test reports to separate Cloudflare projects per browser - Add real-time PR comments with progressive test status updates - Use wrangler-action for unified Cloudflare tooling - Support cross-browser testing with individual report links - Document CI/CD integration in browser_tests/README.md 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * Fix Cloudflare project name for chromium-0.5x browser * Extract project name transformation to variable for consistent URL formatting * chore(ci): update branch filters for push and pull_request events in test-ui workflow to refine CI triggers * [feat] Improve test-ui deployment with branch isolation and building page - Use Cloudflare Pages --branch flag for proper branch isolation instead of modifying project names - Add auto-refresh building page that shows test progress in real-time - Deploy building page immediately when tests start for instant feedback - Update URL generation to use branch-based Cloudflare Pages URLs format - Maintain clean project names while isolating branches properly 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * chore(test-ui.yaml): increase sleep duration from 5 to 10 seconds for cache propagation and restore cached setup steps for improved workflow efficiency * [refactor] Remove building-page to reduce complexity - Remove auto-refresh building page and related deployment steps - Simplify PR comments to show basic test status without progress page - Keep branch-based deployment for proper isolation while reducing complexity - Maintain clean workflow focused on core functionality 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * chore(test-ui.yaml): add a separator in the workflow file for better readability and organization of the test status section * [feat] Add Cloudflare Pages deployment for Playwright test reports - Deploy test reports to separate Cloudflare projects per browser - Add real-time PR comments with progressive test status updates - Use wrangler-action for unified Cloudflare tooling - Support cross-browser testing with individual report links - Document CI/CD integration in browser_tests/README.md 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * Fix Cloudflare project name for chromium-0.5x browser * Extract project name transformation to variable for consistent URL formatting * chore(ci): update branch filters for push and pull_request events in test-ui workflow to refine CI triggers * [feat] Improve test-ui deployment with branch isolation and building page - Use Cloudflare Pages --branch flag for proper branch isolation instead of modifying project names - Add auto-refresh building page that shows test progress in real-time - Deploy building page immediately when tests start for instant feedback - Update URL generation to use branch-based Cloudflare Pages URLs format - Maintain clean project names while isolating branches properly 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * chore(test-ui.yaml): increase sleep duration from 5 to 10 seconds for cache propagation and restore cached setup steps for improved workflow efficiency * [refactor] Remove building-page to reduce complexity - Remove auto-refresh building page and related deployment steps - Simplify PR comments to show basic test status without progress page - Keep branch-based deployment for proper isolation while reducing complexity - Maintain clean workflow focused on core functionality 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * chore(test-ui.yaml): add a separator in the workflow file for better readability and organization of the test status section * [fix] Address PR review feedback - improve workflow architecture and security - [HIGH] Fix continue-on-error masking by adding final test result check that fails CI on test failures - [MEDIUM] Move branch sanitization to setup job to reduce performance overhead - [MEDIUM] Add compatibility-date to Cloudflare deployment for stability - [LOW] Extract date format to environment variable to follow DRY principle - [LOW] Quote shell variables properly to prevent word splitting - [LOW] Update documentation to use dynamic branch-specific URLs Addresses all security, performance, and code quality issues raised in automated PR review. Maintains test report deployment while ensuring CI integrity. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * chore(test-ui.yaml): replace loading emoji with an image for better visual consistency in test logs style(test-ui.yaml): clean up whitespace in the workflow file for improved readability * style(test-ui.yaml): format message to combine two lines into one for better readability * chore(test-ui.yaml): add a blank line for better readability in the workflow file * style(test-ui.yaml): update loading image alt text and format messages for better readability in GitHub Actions workflow * [architecture] Separate test execution from deployment - clean CI design BREAKING: Remove continue-on-error from test execution for proper CI integrity **Clean Architecture Changes:** - Remove `continue-on-error: true` from Playwright test execution - Create separate `deploy-reports` job that always runs for debugging - Test jobs now properly fail when tests fail (maintains CI integrity) - Reports still deploy for debugging via dedicated deployment job - Capture and pass actual exit codes between jobs via artifacts **Benefits:** - ✅ CI fails when tests fail (no longer masked) - ✅ Reports still deploy for debugging regardless of test outcome - ✅ Clean separation of concerns (test vs deploy responsibilities) - ✅ Proper job dependencies and error handling - ✅ Individual browser test results preserved **Job Flow:** 1. `setup` - Cache and prepare environment 2. `playwright-tests` - Run tests, fail if tests fail, upload artifacts 3. `deploy-reports` - Always deploy reports using artifacts (parallel) 4. `comment-summary` - Generate summary and fail workflow if needed This addresses the high-priority architecture concern about continue-on-error masking test failures while maintaining report deployment functionality. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * [refactor] Simplify deployment architecture - remove over-engineering **Reverted to clean, simple approach based on feedback:** 1. ✅ **Faster deployment** - Deploy immediately after each test (no waiting for matrix completion) 2. ✅ **Remove unnecessary GITHUB_OUTPUT** - Don't save exit codes, use step.conclusion instead 3. ✅ **Single job approach** - Use `if: always()` instead of separate deploy-reports job **Key Changes:** - Removed separate `deploy-reports` job (86 lines deleted!) - Deploy in same job with `if: always()` - much faster - Use `steps.playwright.conclusion` instead of captured exit codes - Cleaner, simpler architecture with same functionality **Benefits:** - 🚀 **Much faster** - Reports deploy immediately per browser, not waiting for all tests - 🧹 **Simpler** - One job handles test + deploy, easier to understand - ✅ **Still maintains CI integrity** - Tests fail properly when they should - 📊 **Reports always deploy** - Available for debugging regardless of test outcome The previous approach was over-engineered. This is much cleaner and faster. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * fix(workflow): reorder condition in PR comment step for clarity and consistency * chore(test-ui.yaml): update deployment command to remove compatibility date for better maintainability docs(test-ui.yaml): add note to always() condition for clarity on artifact upload behavior * [performance] Remove redundant branch sanitization - 75% processing reduction **Issue**: Complex bash string operations running 4 times per build in matrix jobs **Solution**: Remove duplicate branch sanitization, use pre-computed value from setup job **Before**: Branch sanitization ran in both setup job AND each matrix job (5 total times) **After**: Branch sanitization runs only once in setup job, reused via outputs **Performance Impact**: - 4 redundant tr/sed operations eliminated (matrix chromium, chromium-2x, chromium-0.5x, mobile-chrome) - 75% reduction in branch name processing overhead - Cleaner, more maintainable code **Implementation**: - Setup job: Computes `sanitized-branch` output once - Matrix jobs: Use `${{ needs.setup.outputs.sanitized-branch }}` directly - No duplicate string processing logic Addresses PR review comment: [performance] medium Priority - Complex bash string operations in GitHub Actions matrix 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --------- Co-authored-by: Claude --- .github/workflows/test-ui.yaml | 225 ++++++++++++++++++++++++++++++++- browser_tests/README.md | 40 ++++++ 2 files changed, 262 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-ui.yaml b/.github/workflows/test-ui.yaml index 39411ade3..4a5157301 100644 --- a/.github/workflows/test-ui.yaml +++ b/.github/workflows/test-ui.yaml @@ -4,13 +4,18 @@ on: push: branches: [main, master, core/*, desktop/*] pull_request: - branches-ignore: [wip/*, draft/*, temp/*, vue-nodes-migration] + branches-ignore: + [wip/*, draft/*, temp/*, vue-nodes-migration, sno-playwright-*] + +env: + DATE_FORMAT: '+%m/%d/%Y, %I:%M:%S %p' jobs: setup: runs-on: ubuntu-latest outputs: cache-key: ${{ steps.cache-key.outputs.key }} + sanitized-branch: ${{ steps.branch-info.outputs.sanitized }} steps: - name: Checkout ComfyUI uses: actions/checkout@v4 @@ -36,6 +41,29 @@ jobs: with: node-version: lts/* + - name: Get current time + id: current-time + run: echo "time=$(date -u '${{ env.DATE_FORMAT }}')" >> $GITHUB_OUTPUT + + - name: Comment PR - Tests Started + if: github.event_name == 'pull_request' + uses: edumserrano/find-create-or-update-comment@v3 + with: + issue-number: ${{ github.event.pull_request.number }} + body-includes: '' + comment-author: 'github-actions[bot]' + edit-mode: append + body: | + + + --- + + claude-loading-gif + [${{ steps.current-time.outputs.time }} UTC] Preparing browser tests across multiple browsers... + + --- + *This comment will be updated when tests complete* + - name: Build ComfyUI_frontend run: | npm ci @@ -46,6 +74,14 @@ jobs: id: cache-key run: echo "key=$(date +%s)" >> $GITHUB_OUTPUT + - name: Generate sanitized branch name + id: branch-info + run: | + # Get branch name and sanitize it for Cloudflare branch names + BRANCH_NAME="${{ github.head_ref || github.ref_name }}" + SANITIZED_BRANCH=$(echo "$BRANCH_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g' | sed 's/--*/-/g' | sed 's/^-\|-$//g') + echo "sanitized=${SANITIZED_BRANCH}" >> $GITHUB_OUTPUT + - name: Save cache uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 with: @@ -57,6 +93,10 @@ jobs: playwright-tests: needs: setup runs-on: ubuntu-latest + permissions: + pull-requests: write + issues: write + contents: read strategy: fail-fast: false matrix: @@ -78,6 +118,32 @@ jobs: with: python-version: '3.10' + - name: Get current time + id: current-time + run: echo "time=$(date -u '${{ env.DATE_FORMAT }}')" >> $GITHUB_OUTPUT + + - name: Set project name + id: project-name + run: | + if [ "${{ matrix.browser }}" = "chromium-0.5x" ]; then + echo "name=comfyui-playwright-chromium-0-5x" >> $GITHUB_OUTPUT + else + echo "name=comfyui-playwright-${{ matrix.browser }}" >> $GITHUB_OUTPUT + fi + echo "branch=${{ needs.setup.outputs.sanitized-branch }}" >> $GITHUB_OUTPUT + + - name: Comment PR - Browser Test Started + if: github.event_name == 'pull_request' + uses: edumserrano/find-create-or-update-comment@v3 + with: + issue-number: ${{ github.event.pull_request.number }} + body-includes: '' + comment-author: 'github-actions[bot]' + edit-mode: append + body: | + claude-loading-gif + ${{ matrix.browser }}: Running tests... + - name: Install requirements run: | python -m pip install --upgrade pip @@ -97,12 +163,165 @@ jobs: working-directory: ComfyUI_frontend - name: Run Playwright tests (${{ matrix.browser }}) - run: npx playwright test --project=${{ matrix.browser }} + id: playwright + run: npx playwright test --project=${{ matrix.browser }} --reporter=html working-directory: ComfyUI_frontend - uses: actions/upload-artifact@v4 - if: always() + if: always() # note: use always() to allow results to be upload/report even tests failed. with: name: playwright-report-${{ matrix.browser }} path: ComfyUI_frontend/playwright-report/ retention-days: 30 + + - name: Deploy to Cloudflare Pages (${{ matrix.browser }}) + id: cloudflare-deploy + if: always() + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: pages deploy ComfyUI_frontend/playwright-report --project-name=${{ steps.project-name.outputs.name }} --branch=${{ steps.project-name.outputs.branch }} + + - name: Save deployment info for summary + if: always() + run: | + mkdir -p deployment-info + # Use step conclusion to determine test result + if [ "${{ steps.playwright.conclusion }}" = "success" ]; then + EXIT_CODE="0" + else + EXIT_CODE="1" + fi + DEPLOYMENT_URL="${{ steps.cloudflare-deploy.outputs.deployment-url || steps.cloudflare-deploy.outputs.url || format('https://{0}.{1}.pages.dev', steps.project-name.outputs.branch, steps.project-name.outputs.name) }}" + echo "${{ matrix.browser }}|${EXIT_CODE}|${DEPLOYMENT_URL}" > deployment-info/${{ matrix.browser }}.txt + + - name: Upload deployment info + if: always() + uses: actions/upload-artifact@v4 + with: + name: deployment-info-${{ matrix.browser }} + path: deployment-info/ + retention-days: 1 + + - name: Get completion time + id: completion-time + if: always() + run: echo "time=$(date -u '${{ env.DATE_FORMAT }}')" >> $GITHUB_OUTPUT + + - name: Comment PR - Browser Test Complete + if: always() && github.event_name == 'pull_request' + uses: edumserrano/find-create-or-update-comment@v3 + with: + issue-number: ${{ github.event.pull_request.number }} + body-includes: '' + comment-author: 'github-actions[bot]' + edit-mode: append + body: | + ${{ steps.playwright.conclusion == 'success' && '✅' || '❌' }} **${{ matrix.browser }}**: ${{ steps.playwright.conclusion == 'success' && 'Tests passed!' || 'Tests failed!' }} [View Report](${{ steps.cloudflare-deploy.outputs.deployment-url || format('https://{0}.{1}.pages.dev', steps.project-name.outputs.branch, steps.project-name.outputs.name) }}) + + comment-summary: + needs: playwright-tests + runs-on: ubuntu-latest + if: always() && github.event_name == 'pull_request' + permissions: + pull-requests: write + steps: + - name: Download all deployment info + uses: actions/download-artifact@v4 + with: + pattern: deployment-info-* + merge-multiple: true + path: deployment-info + + - name: Get completion time + id: completion-time + run: echo "time=$(date -u '${{ env.DATE_FORMAT }}')" >> $GITHUB_OUTPUT + + - name: Generate comment body + id: comment-body + run: | + echo "" > comment.md + echo "## 🎭 Playwright Test Results" >> comment.md + echo "" >> comment.md + + # Check if all tests passed + ALL_PASSED=true + for file in deployment-info/*.txt; do + if [ -f "$file" ]; then + browser=$(basename "$file" .txt) + info=$(cat "$file") + exit_code=$(echo "$info" | cut -d'|' -f2) + if [ "$exit_code" != "0" ]; then + ALL_PASSED=false + break + fi + fi + done + + if [ "$ALL_PASSED" = "true" ]; then + echo "✅ **All tests passed across all browsers!**" >> comment.md + else + echo "❌ **Some tests failed!**" >> comment.md + fi + + echo "" >> comment.md + echo "⏰ Completed at: ${{ steps.completion-time.outputs.time }} UTC" >> comment.md + echo "" >> comment.md + echo "### 📊 Test Reports by Browser" >> comment.md + + for file in deployment-info/*.txt; do + if [ -f "$file" ]; then + browser=$(basename "$file" .txt) + info=$(cat "$file") + exit_code=$(echo "$info" | cut -d'|' -f2) + url=$(echo "$info" | cut -d'|' -f3) + + if [ "$exit_code" = "0" ]; then + status="✅" + else + status="❌" + fi + + echo "- $status **$browser**: [View Report]($url)" >> comment.md + fi + done + + echo "" >> comment.md + echo "---" >> comment.md + if [ "$ALL_PASSED" = "true" ]; then + echo "🎉 Your tests are passing across all browsers!" >> comment.md + else + echo "⚠️ Please check the test reports for details on failures." >> comment.md + fi + + - name: Comment PR - Tests Complete + uses: edumserrano/find-create-or-update-comment@v3 + with: + issue-number: ${{ github.event.pull_request.number }} + body-includes: '' + comment-author: 'github-actions[bot]' + edit-mode: replace + body-path: comment.md + + - name: Check test results and fail if needed + run: | + # Check if all tests passed and fail the job if not + ALL_PASSED=true + for file in deployment-info/*.txt; do + if [ -f "$file" ]; then + info=$(cat "$file") + exit_code=$(echo "$info" | cut -d'|' -f2) + if [ "$exit_code" != "0" ]; then + ALL_PASSED=false + break + fi + fi + done + + if [ "$ALL_PASSED" = "false" ]; then + echo "❌ Tests failed in one or more browsers. Failing the CI job." + exit 1 + else + echo "✅ All tests passed across all browsers!" + fi diff --git a/browser_tests/README.md b/browser_tests/README.md index 9c4d48f6b..91f2f0a46 100644 --- a/browser_tests/README.md +++ b/browser_tests/README.md @@ -412,6 +412,46 @@ For PRs from `Comfy-Org/ComfyUI_frontend` branches: > **Note:** Fork PRs cannot auto-commit screenshots. A maintainer will need to commit the screenshots manually for you (don't worry, they'll do it). +## CI/CD Integration + +### Automated Test Deployment + +The project automatically deploys Playwright test reports to Cloudflare Pages for every PR and push to main branches. This provides: + +- **Live test reports** with interactive HTML views +- **Cross-browser testing** across chromium, mobile-chrome, and different viewport sizes +- **Real-time PR comments** with test status and links to detailed reports + +#### How it works: + +1. **Test execution**: All browser tests run in parallel across multiple browsers +2. **Report generation**: HTML reports are generated for each browser configuration +3. **Cloudflare deployment**: Each browser's report deploys to its own Cloudflare Pages project with branch isolation: + - `comfyui-playwright-chromium` (with branch-specific URLs) + - `comfyui-playwright-mobile-chrome` (with branch-specific URLs) + - `comfyui-playwright-chromium-2x` (2x scale, with branch-specific URLs) + - `comfyui-playwright-chromium-0-5x` (0.5x scale, with branch-specific URLs) + +4. **PR comments**: GitHub automatically updates PR comments with: + - ✅/❌ Test status for each browser + - Direct links to interactive test reports + - Real-time progress updates as tests complete + +#### Accessing test reports: + +- **From PR comments**: Click the "View Report" links for each browser +- **From GitHub Actions**: Download artifacts from failed runs +- **Direct URLs**: Reports are available at `https://[branch].comfyui-playwright-[browser].pages.dev` (branch-specific deployments) + +#### Report features: + +- **Interactive HTML reports** with test results, screenshots, and traces +- **Detailed failure analysis** with before/after screenshots +- **Test execution videos** for failed tests +- **Network logs** and console output for debugging + +This integration ensures that test results are easily accessible to reviewers and maintainers, making it simple to verify that changes don't break existing functionality across different browsers and viewport sizes. + ## Resources - [Playwright UI Mode](https://playwright.dev/docs/test-ui-mode) - Interactive test debugging From 28d74be3639ace1fd6856e435143a69e5a347c1c Mon Sep 17 00:00:00 2001 From: Christian Byrne Date: Tue, 19 Aug 2025 10:39:20 -0700 Subject: [PATCH 03/21] add onRemove invoke to removeWidget method (#5102) --- src/lib/litegraph/src/LGraphNode.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/litegraph/src/LGraphNode.ts b/src/lib/litegraph/src/LGraphNode.ts index 34e8b9432..59d55002d 100644 --- a/src/lib/litegraph/src/LGraphNode.ts +++ b/src/lib/litegraph/src/LGraphNode.ts @@ -1925,6 +1925,7 @@ export class LGraphNode } } + widget.onRemove?.() this.widgets.splice(widgetIndex, 1) } From 694817297d31b706768026bdf91ec62a2994ad5b Mon Sep 17 00:00:00 2001 From: Christian Byrne Date: Tue, 19 Aug 2025 12:33:55 -0700 Subject: [PATCH 04/21] [ci] Add caching support to format and knip commands (#5107) - Enable caching for prettier and knip commands to improve CI performance - Add no-cache variants for consistency with existing lint scripts - Exclude generated type files from prettier formatting - Add .prettiercache to .gitignore for proper cache management Follows the same optimization pattern as ESLint caching from PR #4926. --- .gitignore | 3 ++- .prettierignore | 2 ++ package.json | 9 ++++++--- 3 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 .prettierignore diff --git a/.gitignore b/.gitignore index db5315411..60cca8f98 100644 --- a/.gitignore +++ b/.gitignore @@ -13,8 +13,9 @@ bun.lockb pnpm-lock.yaml yarn.lock -# ESLint cache +# Cache files .eslintcache +.prettiercache node_modules dist diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..cccae51c9 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +src/types/comfyRegistryTypes.ts +src/types/generatedManagerTypes.ts \ No newline at end of file diff --git a/package.json b/package.json index 9627875af..5f9f81745 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,10 @@ "build:types": "vite build --config vite.types.config.mts && node scripts/prepare-types.js", "zipdist": "node scripts/zipdist.js", "typecheck": "vue-tsc --noEmit", - "format": "prettier --write './**/*.{js,ts,tsx,vue,mts}'", - "format:check": "prettier --check './**/*.{js,ts,tsx,vue,mts}'", + "format": "prettier --write './**/*.{js,ts,tsx,vue,mts}' --cache", + "format:check": "prettier --check './**/*.{js,ts,tsx,vue,mts}' --cache", + "format:no-cache": "prettier --write './**/*.{js,ts,tsx,vue,mts}'", + "format:check:no-cache": "prettier --check './**/*.{js,ts,tsx,vue,mts}'", "test:browser": "npx playwright test", "test:unit": "vitest run tests-ui/tests", "test:component": "vitest run src/components/", @@ -25,7 +27,8 @@ "lint:fix": "eslint src --cache --fix", "lint:no-cache": "eslint src", "lint:fix:no-cache": "eslint src --fix", - "knip": "knip", + "knip": "knip --cache", + "knip:no-cache": "knip", "locale": "lobe-i18n locale", "collect-i18n": "playwright test --config=playwright.i18n.config.ts", "json-schema": "tsx scripts/generate-json-schema.ts", From deba8df8ce3b93bab9821d88d1f4318c1cf3f3c8 Mon Sep 17 00:00:00 2001 From: Arjan Singh <1598641+arjansingh@users.noreply.github.com> Date: Tue, 19 Aug 2025 13:53:00 -0700 Subject: [PATCH 05/21] [chore] ignore ./claude/settings.json (#5110) --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 60cca8f98..8d19ceec5 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ dist-ssr *.local # Claude configuration .claude/*.local.json +.claude/settings.json # Editor directories and files .vscode/* From 80097007c981cf6cee5a5222890ea61b4b007695 Mon Sep 17 00:00:00 2001 From: Christian Byrne Date: Tue, 19 Aug 2025 15:03:02 -0700 Subject: [PATCH 06/21] refactor readme to suggest deployed reports (#5112) --- browser_tests/README.md | 39 ++++++++------------------------------- 1 file changed, 8 insertions(+), 31 deletions(-) diff --git a/browser_tests/README.md b/browser_tests/README.md index 91f2f0a46..ede6a303a 100644 --- a/browser_tests/README.md +++ b/browser_tests/README.md @@ -392,16 +392,6 @@ Option 2 - Generate local baselines for comparison: npx playwright test --update-snapshots ``` -### Getting Test Artifacts from GitHub Actions - -When tests fail in CI, you can download screenshots and traces: - -1. Go to the failed workflow run in GitHub Actions -2. Scroll to "Artifacts" section at the bottom -3. Download `playwright-report` or `test-results` -4. Extract and open the HTML report locally -5. View actual vs expected screenshots and execution traces - ### Creating New Screenshot Baselines For PRs from `Comfy-Org/ComfyUI_frontend` branches: @@ -412,17 +402,19 @@ For PRs from `Comfy-Org/ComfyUI_frontend` branches: > **Note:** Fork PRs cannot auto-commit screenshots. A maintainer will need to commit the screenshots manually for you (don't worry, they'll do it). -## CI/CD Integration +## Viewing Test Reports ### Automated Test Deployment -The project automatically deploys Playwright test reports to Cloudflare Pages for every PR and push to main branches. This provides: +The project automatically deploys Playwright test reports to Cloudflare Pages for every PR and push to main branches. -- **Live test reports** with interactive HTML views -- **Cross-browser testing** across chromium, mobile-chrome, and different viewport sizes -- **Real-time PR comments** with test status and links to detailed reports +### Accessing Test Reports -#### How it works: +- **From PR comments**: Click the "View Report" links for each browser +- **Direct URLs**: Reports are available at `https://[branch].comfyui-playwright-[browser].pages.dev` (branch-specific deployments) +- **From GitHub Actions**: Download artifacts from failed runs + +### How It Works 1. **Test execution**: All browser tests run in parallel across multiple browsers 2. **Report generation**: HTML reports are generated for each browser configuration @@ -437,21 +429,6 @@ The project automatically deploys Playwright test reports to Cloudflare Pages fo - Direct links to interactive test reports - Real-time progress updates as tests complete -#### Accessing test reports: - -- **From PR comments**: Click the "View Report" links for each browser -- **From GitHub Actions**: Download artifacts from failed runs -- **Direct URLs**: Reports are available at `https://[branch].comfyui-playwright-[browser].pages.dev` (branch-specific deployments) - -#### Report features: - -- **Interactive HTML reports** with test results, screenshots, and traces -- **Detailed failure analysis** with before/after screenshots -- **Test execution videos** for failed tests -- **Network logs** and console output for debugging - -This integration ensures that test results are easily accessible to reviewers and maintainers, making it simple to verify that changes don't break existing functionality across different browsers and viewport sizes. - ## Resources - [Playwright UI Mode](https://playwright.dev/docs/test-ui-mode) - Interactive test debugging From 97e5291b05bc48eb2beb5d7b6298f3f2664071ca Mon Sep 17 00:00:00 2001 From: pythongosssss <125205205+pythongosssss@users.noreply.github.com> Date: Tue, 19 Aug 2025 23:14:06 +0100 Subject: [PATCH 07/21] Update to latest version of workflow icon (#5103) --- src/assets/icons/custom/workflow.svg | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/assets/icons/custom/workflow.svg b/src/assets/icons/custom/workflow.svg index 72f90c1a4..043d24e7b 100644 --- a/src/assets/icons/custom/workflow.svg +++ b/src/assets/icons/custom/workflow.svg @@ -1,7 +1,3 @@ - - - - - - + + From 8d0a523ffd3cffec6931389688be2d666ac38ae4 Mon Sep 17 00:00:00 2001 From: Christian Byrne Date: Tue, 19 Aug 2025 18:36:00 -0700 Subject: [PATCH 08/21] [refactor] Remove obsolete Kontext Edit Button (#5108) * feat: Remove obsolete Kontext Edit Button Removes the 'Kontext Edit Button' and its associated code, as it has been made obsolete by the new 'Subgraphs + Partial Execution' feature. Fixes #5093 * Update locales [skip ci] --------- Co-authored-by: github-actions --- src/components/graph/SelectionToolbox.vue | 2 - .../selectionToolbox/EditModelButton.vue | 37 - src/composables/useCoreCommands.ts | 12 - src/locales/ar/commands.json | 3 - src/locales/ar/main.json | 1 - src/locales/en/commands.json | 3 - src/locales/en/main.json | 1 - src/locales/es/commands.json | 3 - src/locales/es/main.json | 1 - src/locales/fr/commands.json | 3 - src/locales/fr/main.json | 1 - src/locales/ja/commands.json | 3 - src/locales/ja/main.json | 1 - src/locales/ko/commands.json | 3 - src/locales/ko/main.json | 1 - src/locales/ru/commands.json | 3 - src/locales/ru/main.json | 1 - src/locales/zh-TW/commands.json | 3 - src/locales/zh-TW/main.json | 1 - src/locales/zh/commands.json | 3 - src/locales/zh/main.json | 1 - src/scripts/fluxKontextEditNode.ts | 693 ------------------ 22 files changed, 780 deletions(-) delete mode 100644 src/components/graph/selectionToolbox/EditModelButton.vue delete mode 100644 src/scripts/fluxKontextEditNode.ts diff --git a/src/components/graph/SelectionToolbox.vue b/src/components/graph/SelectionToolbox.vue index d4cf408fc..bbbeadef9 100644 --- a/src/components/graph/SelectionToolbox.vue +++ b/src/components/graph/SelectionToolbox.vue @@ -12,7 +12,6 @@ - @@ -35,7 +34,6 @@ import BypassButton from '@/components/graph/selectionToolbox/BypassButton.vue' import ColorPickerButton from '@/components/graph/selectionToolbox/ColorPickerButton.vue' import ConvertToSubgraphButton from '@/components/graph/selectionToolbox/ConvertToSubgraphButton.vue' import DeleteButton from '@/components/graph/selectionToolbox/DeleteButton.vue' -import EditModelButton from '@/components/graph/selectionToolbox/EditModelButton.vue' import ExecuteButton from '@/components/graph/selectionToolbox/ExecuteButton.vue' import ExtensionCommandButton from '@/components/graph/selectionToolbox/ExtensionCommandButton.vue' import HelpButton from '@/components/graph/selectionToolbox/HelpButton.vue' diff --git a/src/components/graph/selectionToolbox/EditModelButton.vue b/src/components/graph/selectionToolbox/EditModelButton.vue deleted file mode 100644 index d9f515ddd..000000000 --- a/src/components/graph/selectionToolbox/EditModelButton.vue +++ /dev/null @@ -1,37 +0,0 @@ - - - diff --git a/src/composables/useCoreCommands.ts b/src/composables/useCoreCommands.ts index 80eeca78f..fe6d112bb 100644 --- a/src/composables/useCoreCommands.ts +++ b/src/composables/useCoreCommands.ts @@ -16,7 +16,6 @@ import { import { Point } from '@/lib/litegraph/src/litegraph' import { api } from '@/scripts/api' import { app } from '@/scripts/app' -import { addFluxKontextGroupNode } from '@/scripts/fluxKontextEditNode' import { useDialogService } from '@/services/dialogService' import { useLitegraphService } from '@/services/litegraphService' import { useWorkflowService } from '@/services/workflowService' @@ -775,17 +774,6 @@ export function useCoreCommands(): ComfyCommand[] { versionAdded: moveSelectedNodesVersionAdded, function: () => moveSelectedNodes(([x, y], gridSize) => [x + gridSize, y]) }, - { - id: 'Comfy.Canvas.AddEditModelStep', - icon: 'pi pi-pen-to-square', - label: 'Add Edit Model Step', - versionAdded: '1.23.3', - function: async () => { - const node = app.canvas.selectedItems.values().next().value - if (!(node instanceof LGraphNode)) return - await addFluxKontextGroupNode(node) - } - }, { id: 'Comfy.Graph.ConvertToSubgraph', icon: 'pi pi-sitemap', diff --git a/src/locales/ar/commands.json b/src/locales/ar/commands.json index 760d0a178..95b417dc1 100644 --- a/src/locales/ar/commands.json +++ b/src/locales/ar/commands.json @@ -41,9 +41,6 @@ "Comfy_BrowseTemplates": { "label": "تصفح القوالب" }, - "Comfy_Canvas_AddEditModelStep": { - "label": "إضافة خطوة تحرير النموذج" - }, "Comfy_Canvas_DeleteSelectedItems": { "label": "حذف العناصر المحددة" }, diff --git a/src/locales/ar/main.json b/src/locales/ar/main.json index af0549674..8a9cc8b2c 100644 --- a/src/locales/ar/main.json +++ b/src/locales/ar/main.json @@ -762,7 +762,6 @@ }, "menuLabels": { "About ComfyUI": "حول ComfyUI", - "Add Edit Model Step": "إضافة خطوة تعديل النموذج", "Bottom Panel": "لوحة سفلية", "Browse Templates": "تصفح القوالب", "Bypass/Unbypass Selected Nodes": "تجاوز/إلغاء تجاوز العقد المحددة", diff --git a/src/locales/en/commands.json b/src/locales/en/commands.json index 5b0d9c445..8508497e6 100644 --- a/src/locales/en/commands.json +++ b/src/locales/en/commands.json @@ -41,9 +41,6 @@ "Comfy_BrowseTemplates": { "label": "Browse Templates" }, - "Comfy_Canvas_AddEditModelStep": { - "label": "Add Edit Model Step" - }, "Comfy_Canvas_DeleteSelectedItems": { "label": "Delete Selected Items" }, diff --git a/src/locales/en/main.json b/src/locales/en/main.json index b4c253708..b6ed989a1 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -958,7 +958,6 @@ "Restart": "Restart", "Open 3D Viewer (Beta) for Selected Node": "Open 3D Viewer (Beta) for Selected Node", "Browse Templates": "Browse Templates", - "Add Edit Model Step": "Add Edit Model Step", "Delete Selected Items": "Delete Selected Items", "Zoom to fit": "Zoom to fit", "Move Selected Nodes Down": "Move Selected Nodes Down", diff --git a/src/locales/es/commands.json b/src/locales/es/commands.json index 580515847..d41962c03 100644 --- a/src/locales/es/commands.json +++ b/src/locales/es/commands.json @@ -41,9 +41,6 @@ "Comfy_BrowseTemplates": { "label": "Explorar plantillas" }, - "Comfy_Canvas_AddEditModelStep": { - "label": "Agregar paso de edición de modelo" - }, "Comfy_Canvas_DeleteSelectedItems": { "label": "Eliminar elementos seleccionados" }, diff --git a/src/locales/es/main.json b/src/locales/es/main.json index e056b0afb..ef27fc8c2 100644 --- a/src/locales/es/main.json +++ b/src/locales/es/main.json @@ -762,7 +762,6 @@ }, "menuLabels": { "About ComfyUI": "Acerca de ComfyUI", - "Add Edit Model Step": "Agregar paso de edición de modelo", "Bottom Panel": "Panel inferior", "Browse Templates": "Explorar plantillas", "Bypass/Unbypass Selected Nodes": "Evitar/No evitar nodos seleccionados", diff --git a/src/locales/fr/commands.json b/src/locales/fr/commands.json index 69a9f4fc3..e04e372b4 100644 --- a/src/locales/fr/commands.json +++ b/src/locales/fr/commands.json @@ -41,9 +41,6 @@ "Comfy_BrowseTemplates": { "label": "Parcourir les modèles" }, - "Comfy_Canvas_AddEditModelStep": { - "label": "Ajouter/Modifier une étape de modèle" - }, "Comfy_Canvas_DeleteSelectedItems": { "label": "Supprimer les éléments sélectionnés" }, diff --git a/src/locales/fr/main.json b/src/locales/fr/main.json index 3680db24e..31dcbe86f 100644 --- a/src/locales/fr/main.json +++ b/src/locales/fr/main.json @@ -762,7 +762,6 @@ }, "menuLabels": { "About ComfyUI": "À propos de ComfyUI", - "Add Edit Model Step": "Ajouter une étape d’édition de modèle", "Bottom Panel": "Panneau inférieur", "Browse Templates": "Parcourir les modèles", "Bypass/Unbypass Selected Nodes": "Contourner/Ne pas contourner les nœuds sélectionnés", diff --git a/src/locales/ja/commands.json b/src/locales/ja/commands.json index caaf7732d..307ef72e5 100644 --- a/src/locales/ja/commands.json +++ b/src/locales/ja/commands.json @@ -41,9 +41,6 @@ "Comfy_BrowseTemplates": { "label": "テンプレートを参照" }, - "Comfy_Canvas_AddEditModelStep": { - "label": "編集モデルステップを追加" - }, "Comfy_Canvas_DeleteSelectedItems": { "label": "選択したアイテムを削除" }, diff --git a/src/locales/ja/main.json b/src/locales/ja/main.json index 34caa47a2..c3af5ee25 100644 --- a/src/locales/ja/main.json +++ b/src/locales/ja/main.json @@ -762,7 +762,6 @@ }, "menuLabels": { "About ComfyUI": "ComfyUIについて", - "Add Edit Model Step": "モデル編集ステップを追加", "Bottom Panel": "下部パネル", "Browse Templates": "テンプレートを参照", "Bypass/Unbypass Selected Nodes": "選択したノードのバイパス/バイパス解除", diff --git a/src/locales/ko/commands.json b/src/locales/ko/commands.json index 7105cd821..431ae7ae0 100644 --- a/src/locales/ko/commands.json +++ b/src/locales/ko/commands.json @@ -41,9 +41,6 @@ "Comfy_BrowseTemplates": { "label": "템플릿 탐색" }, - "Comfy_Canvas_AddEditModelStep": { - "label": "모델 편집 단계 추가" - }, "Comfy_Canvas_DeleteSelectedItems": { "label": "선택한 항목 삭제" }, diff --git a/src/locales/ko/main.json b/src/locales/ko/main.json index 672fad4fc..f11bc201f 100644 --- a/src/locales/ko/main.json +++ b/src/locales/ko/main.json @@ -762,7 +762,6 @@ }, "menuLabels": { "About ComfyUI": "ComfyUI에 대하여", - "Add Edit Model Step": "모델 편집 단계 추가", "Bottom Panel": "하단 패널", "Browse Templates": "템플릿 탐색", "Bypass/Unbypass Selected Nodes": "선택한 노드 우회/우회 해제", diff --git a/src/locales/ru/commands.json b/src/locales/ru/commands.json index fa3927190..86fc88f17 100644 --- a/src/locales/ru/commands.json +++ b/src/locales/ru/commands.json @@ -41,9 +41,6 @@ "Comfy_BrowseTemplates": { "label": "Просмотр шаблонов" }, - "Comfy_Canvas_AddEditModelStep": { - "label": "Добавить или изменить шаг модели" - }, "Comfy_Canvas_DeleteSelectedItems": { "label": "Удалить выбранные элементы" }, diff --git a/src/locales/ru/main.json b/src/locales/ru/main.json index 3257bc1b1..04e2b2bef 100644 --- a/src/locales/ru/main.json +++ b/src/locales/ru/main.json @@ -762,7 +762,6 @@ }, "menuLabels": { "About ComfyUI": "О ComfyUI", - "Add Edit Model Step": "Добавить или изменить шаг модели", "Bottom Panel": "Нижняя панель", "Browse Templates": "Просмотреть шаблоны", "Bypass/Unbypass Selected Nodes": "Обойти/восстановить выбранные ноды", diff --git a/src/locales/zh-TW/commands.json b/src/locales/zh-TW/commands.json index 71d1907b6..46fbcb30d 100644 --- a/src/locales/zh-TW/commands.json +++ b/src/locales/zh-TW/commands.json @@ -41,9 +41,6 @@ "Comfy_BrowseTemplates": { "label": "瀏覽範本" }, - "Comfy_Canvas_AddEditModelStep": { - "label": "新增編輯模型步驟" - }, "Comfy_Canvas_DeleteSelectedItems": { "label": "刪除選取項目" }, diff --git a/src/locales/zh-TW/main.json b/src/locales/zh-TW/main.json index b61ea22cc..fe0d5f416 100644 --- a/src/locales/zh-TW/main.json +++ b/src/locales/zh-TW/main.json @@ -762,7 +762,6 @@ }, "menuLabels": { "About ComfyUI": "關於 ComfyUI", - "Add Edit Model Step": "新增編輯模型步驟", "Bottom Panel": "底部面板", "Browse Templates": "瀏覽範本", "Bypass/Unbypass Selected Nodes": "繞過/取消繞過選取節點", diff --git a/src/locales/zh/commands.json b/src/locales/zh/commands.json index a42111c54..5d319aa17 100644 --- a/src/locales/zh/commands.json +++ b/src/locales/zh/commands.json @@ -41,9 +41,6 @@ "Comfy_BrowseTemplates": { "label": "浏览模板" }, - "Comfy_Canvas_AddEditModelStep": { - "label": "添加编辑模型步骤" - }, "Comfy_Canvas_DeleteSelectedItems": { "label": "删除选定的项目" }, diff --git a/src/locales/zh/main.json b/src/locales/zh/main.json index 6c678c651..19b6533e8 100644 --- a/src/locales/zh/main.json +++ b/src/locales/zh/main.json @@ -762,7 +762,6 @@ }, "menuLabels": { "About ComfyUI": "关于ComfyUI", - "Add Edit Model Step": "添加编辑模型步骤", "Bottom Panel": "底部面板", "Browse Templates": "浏览模板", "Bypass/Unbypass Selected Nodes": "忽略/取消忽略选定节点", diff --git a/src/scripts/fluxKontextEditNode.ts b/src/scripts/fluxKontextEditNode.ts deleted file mode 100644 index fe794d43c..000000000 --- a/src/scripts/fluxKontextEditNode.ts +++ /dev/null @@ -1,693 +0,0 @@ -import _ from 'es-toolkit/compat' - -import { - type INodeOutputSlot, - type LGraph, - type LGraphNode, - LLink, - LiteGraph, - type Point -} from '@/lib/litegraph/src/litegraph' -import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets' -import { parseFilePath } from '@/utils/formatUtil' - -import { app } from './app' - -const fluxKontextGroupNode = { - nodes: [ - { - id: -1, - type: 'Reroute', - pos: [2354.87890625, -127.23468780517578], - size: [75, 26], - flags: {}, - order: 20, - mode: 0, - inputs: [{ name: '', type: '*', link: null }], - outputs: [{ name: '', type: '*', links: null }], - properties: { showOutputText: false, horizontal: false }, - index: 0 - }, - { - id: -1, - type: 'ReferenceLatent', - pos: [2730, -220], - size: [197.712890625, 46], - flags: {}, - order: 22, - mode: 0, - inputs: [ - { - localized_name: 'conditioning', - name: 'conditioning', - type: 'CONDITIONING', - link: null - }, - { - localized_name: 'latent', - name: 'latent', - shape: 7, - type: 'LATENT', - link: null - } - ], - outputs: [ - { - localized_name: 'CONDITIONING', - name: 'CONDITIONING', - type: 'CONDITIONING', - links: [] - } - ], - properties: { - 'Node name for S&R': 'ReferenceLatent', - cnr_id: 'comfy-core', - ver: '0.3.38' - }, - index: 1 - }, - { - id: -1, - type: 'VAEDecode', - pos: [3270, -110], - size: [210, 46], - flags: {}, - order: 25, - mode: 0, - inputs: [ - { - localized_name: 'samples', - name: 'samples', - type: 'LATENT', - link: null - }, - { - localized_name: 'vae', - name: 'vae', - type: 'VAE', - link: null - } - ], - outputs: [ - { - localized_name: 'IMAGE', - name: 'IMAGE', - type: 'IMAGE', - slot_index: 0, - links: [] - } - ], - properties: { - 'Node name for S&R': 'VAEDecode', - cnr_id: 'comfy-core', - ver: '0.3.38' - }, - index: 2 - }, - { - id: -1, - type: 'KSampler', - pos: [2930, -110], - size: [315, 262], - flags: {}, - order: 24, - mode: 0, - inputs: [ - { - localized_name: 'model', - name: 'model', - type: 'MODEL', - link: null - }, - { - localized_name: 'positive', - name: 'positive', - type: 'CONDITIONING', - link: null - }, - { - localized_name: 'negative', - name: 'negative', - type: 'CONDITIONING', - link: null - }, - { - localized_name: 'latent_image', - name: 'latent_image', - type: 'LATENT', - link: null - }, - { - localized_name: 'seed', - name: 'seed', - type: 'INT', - widget: { name: 'seed' }, - link: null - }, - { - localized_name: 'steps', - name: 'steps', - type: 'INT', - widget: { name: 'steps' }, - link: null - }, - { - localized_name: 'cfg', - name: 'cfg', - type: 'FLOAT', - widget: { name: 'cfg' }, - link: null - }, - { - localized_name: 'sampler_name', - name: 'sampler_name', - type: 'COMBO', - widget: { name: 'sampler_name' }, - link: null - }, - { - localized_name: 'scheduler', - name: 'scheduler', - type: 'COMBO', - widget: { name: 'scheduler' }, - link: null - }, - { - localized_name: 'denoise', - name: 'denoise', - type: 'FLOAT', - widget: { name: 'denoise' }, - link: null - } - ], - outputs: [ - { - localized_name: 'LATENT', - name: 'LATENT', - type: 'LATENT', - slot_index: 0, - links: [] - } - ], - properties: { - 'Node name for S&R': 'KSampler', - cnr_id: 'comfy-core', - ver: '0.3.38' - }, - widgets_values: [972054013131369, 'fixed', 20, 1, 'euler', 'simple', 1], - index: 3 - }, - { - id: -1, - type: 'FluxGuidance', - pos: [2940, -220], - size: [211.60000610351562, 58], - flags: {}, - order: 23, - mode: 0, - inputs: [ - { - localized_name: 'conditioning', - name: 'conditioning', - type: 'CONDITIONING', - link: null - }, - { - localized_name: 'guidance', - name: 'guidance', - type: 'FLOAT', - widget: { name: 'guidance' }, - link: null - } - ], - outputs: [ - { - localized_name: 'CONDITIONING', - name: 'CONDITIONING', - type: 'CONDITIONING', - slot_index: 0, - links: [] - } - ], - properties: { - 'Node name for S&R': 'FluxGuidance', - cnr_id: 'comfy-core', - ver: '0.3.38' - }, - widgets_values: [2.5], - index: 4 - }, - { - id: -1, - type: 'SaveImage', - pos: [3490, -110], - size: [985.3012084960938, 1060.3828125], - flags: {}, - order: 26, - mode: 0, - inputs: [ - { - localized_name: 'images', - name: 'images', - type: 'IMAGE', - link: null - }, - { - localized_name: 'filename_prefix', - name: 'filename_prefix', - type: 'STRING', - widget: { name: 'filename_prefix' }, - link: null - } - ], - outputs: [], - properties: { cnr_id: 'comfy-core', ver: '0.3.38' }, - widgets_values: ['ComfyUI'], - index: 5 - }, - { - id: -1, - type: 'CLIPTextEncode', - pos: [2500, -110], - size: [422.84503173828125, 164.31304931640625], - flags: {}, - order: 12, - mode: 0, - inputs: [ - { - localized_name: 'clip', - name: 'clip', - type: 'CLIP', - link: null - }, - { - localized_name: 'text', - name: 'text', - type: 'STRING', - widget: { name: 'text' }, - link: null - } - ], - outputs: [ - { - localized_name: 'CONDITIONING', - name: 'CONDITIONING', - type: 'CONDITIONING', - slot_index: 0, - links: [] - } - ], - title: 'CLIP Text Encode (Positive Prompt)', - properties: { - 'Node name for S&R': 'CLIPTextEncode', - cnr_id: 'comfy-core', - ver: '0.3.38' - }, - widgets_values: ['there is a bright light'], - color: '#232', - bgcolor: '#353', - index: 6 - }, - { - id: -1, - type: 'CLIPTextEncode', - pos: [2504.1435546875, 97.9598617553711], - size: [422.84503173828125, 164.31304931640625], - flags: { collapsed: true }, - order: 13, - mode: 0, - inputs: [ - { - localized_name: 'clip', - name: 'clip', - type: 'CLIP', - link: null - }, - { - localized_name: 'text', - name: 'text', - type: 'STRING', - widget: { name: 'text' }, - link: null - } - ], - outputs: [ - { - localized_name: 'CONDITIONING', - name: 'CONDITIONING', - type: 'CONDITIONING', - slot_index: 0, - links: [] - } - ], - title: 'CLIP Text Encode (Negative Prompt)', - properties: { - 'Node name for S&R': 'CLIPTextEncode', - cnr_id: 'comfy-core', - ver: '0.3.38' - }, - widgets_values: [''], - color: '#322', - bgcolor: '#533', - index: 7 - }, - { - id: -1, - type: 'UNETLoader', - pos: [2630, -370], - size: [270, 82], - flags: {}, - order: 6, - mode: 0, - inputs: [ - { - localized_name: 'unet_name', - name: 'unet_name', - type: 'COMBO', - widget: { name: 'unet_name' }, - link: null - }, - { - localized_name: 'weight_dtype', - name: 'weight_dtype', - type: 'COMBO', - widget: { name: 'weight_dtype' }, - link: null - } - ], - outputs: [ - { - localized_name: 'MODEL', - name: 'MODEL', - type: 'MODEL', - links: [] - } - ], - properties: { - 'Node name for S&R': 'UNETLoader', - cnr_id: 'comfy-core', - ver: '0.3.38' - }, - widgets_values: ['flux1-kontext-dev.safetensors', 'default'], - color: '#223', - bgcolor: '#335', - index: 8 - }, - { - id: -1, - type: 'DualCLIPLoader', - pos: [2100, -290], - size: [337.76861572265625, 130], - flags: {}, - order: 8, - mode: 0, - inputs: [ - { - localized_name: 'clip_name1', - name: 'clip_name1', - type: 'COMBO', - widget: { name: 'clip_name1' }, - link: null - }, - { - localized_name: 'clip_name2', - name: 'clip_name2', - type: 'COMBO', - widget: { name: 'clip_name2' }, - link: null - }, - { - localized_name: 'type', - name: 'type', - type: 'COMBO', - widget: { name: 'type' }, - link: null - }, - { - localized_name: 'device', - name: 'device', - shape: 7, - type: 'COMBO', - widget: { name: 'device' }, - link: null - } - ], - outputs: [ - { - localized_name: 'CLIP', - name: 'CLIP', - type: 'CLIP', - links: [] - } - ], - properties: { - 'Node name for S&R': 'DualCLIPLoader', - cnr_id: 'comfy-core', - ver: '0.3.38' - }, - widgets_values: [ - 'clip_l.safetensors', - 't5xxl_fp8_e4m3fn_scaled.safetensors', - 'flux', - 'default' - ], - color: '#223', - bgcolor: '#335', - index: 9 - }, - { - id: -1, - type: 'VAELoader', - pos: [2960, -370], - size: [270, 58], - flags: {}, - order: 7, - mode: 0, - inputs: [ - { - localized_name: 'vae_name', - name: 'vae_name', - type: 'COMBO', - widget: { name: 'vae_name' }, - link: null - } - ], - outputs: [ - { - localized_name: 'VAE', - name: 'VAE', - type: 'VAE', - links: [] - } - ], - properties: { - 'Node name for S&R': 'VAELoader', - cnr_id: 'comfy-core', - ver: '0.3.38' - }, - widgets_values: ['ae.safetensors'], - color: '#223', - bgcolor: '#335', - index: 10 - } - ], - links: [ - [6, 0, 1, 0, 72, 'CONDITIONING'], - [0, 0, 1, 1, 66, '*'], - [3, 0, 2, 0, 69, 'LATENT'], - [10, 0, 2, 1, 76, 'VAE'], - [8, 0, 3, 0, 74, 'MODEL'], - [4, 0, 3, 1, 70, 'CONDITIONING'], - [7, 0, 3, 2, 73, 'CONDITIONING'], - [0, 0, 3, 3, 66, '*'], - [1, 0, 4, 0, 67, 'CONDITIONING'], - [2, 0, 5, 0, 68, 'IMAGE'], - [9, 0, 6, 0, 75, 'CLIP'], - [9, 0, 7, 0, 75, 'CLIP'] - ], - external: [], - config: { - '0': {}, - '1': {}, - '2': { output: { '0': { visible: true } } }, - '3': { - output: { '0': { visible: true } }, - input: { - denoise: { visible: false }, - cfg: { visible: false } - } - }, - '4': {}, - '5': {}, - '6': {}, - '7': { input: { text: { visible: false } } }, - '8': { input: { weight_dtype: { visible: false } } }, - '9': { input: { type: { visible: false }, device: { visible: false } } }, - '10': {} - } -} - -export async function ensureGraphHasFluxKontextGroupNode( - graph: LGraph & { extra: { groupNodes?: Record } } -) { - graph.extra ??= {} - graph.extra.groupNodes ??= {} - if (graph.extra.groupNodes['FLUX.1 Kontext Image Edit']) return - - graph.extra.groupNodes['FLUX.1 Kontext Image Edit'] = - structuredClone(fluxKontextGroupNode) - - // Lazy import to avoid circular dependency issues - const { GroupNodeConfig } = await import('@/extensions/core/groupNode') - await GroupNodeConfig.registerFromWorkflow( - { - 'FLUX.1 Kontext Image Edit': - graph.extra.groupNodes['FLUX.1 Kontext Image Edit'] - }, - [] - ) -} - -export async function addFluxKontextGroupNode(fromNode: LGraphNode) { - const { canvas } = app - const { graph } = canvas - if (!graph) throw new TypeError('Graph is not initialized') - await ensureGraphHasFluxKontextGroupNode(graph) - - const node = LiteGraph.createNode('workflow>FLUX.1 Kontext Image Edit') - if (!node) throw new TypeError('Failed to create node') - - const pos = getPosToRightOfNode(fromNode) - - graph.add(node) - node.pos = pos - app.canvas.processSelect(node, undefined) - - connectPreviousLatent(fromNode, node) - - const symb = Object.getOwnPropertySymbols(node)[0] - // @ts-expect-error It's there -- promise. - node[symb].populateWidgets() - - setWidgetValues(node) -} - -function setWidgetValues(node: LGraphNode) { - const seedInput = node.widgets?.find((x) => x.name === 'seed') - if (!seedInput) throw new TypeError('Seed input not found') - seedInput.value = Math.floor(Math.random() * 1_125_899_906_842_624) - - const firstClip = node.widgets?.find((x) => x.name === 'clip_name1') - setPreferredValue('t5xxl_fp8_e4m3fn_scaled.safetensors', 't5xxl', firstClip) - - const secondClip = node.widgets?.find((x) => x.name === 'clip_name2') - setPreferredValue('clip_l.safetensors', 'clip_l', secondClip) - - const unet = node.widgets?.find((x) => x.name === 'unet_name') - setPreferredValue('flux1-dev-kontext_fp8_scaled.safetensors', 'kontext', unet) - - const vae = node.widgets?.find((x) => x.name === 'vae_name') - setPreferredValue('ae.safetensors', 'ae.s', vae) -} - -function setPreferredValue( - preferred: string, - match: string, - widget: IBaseWidget | undefined -): void { - if (!widget) throw new TypeError('Widget not found') - - const { values } = widget.options - if (!Array.isArray(values)) return - - // Match against filename portion only - const mapped = values.map((x) => parseFilePath(x).filename) - const value = - mapped.find((x) => x === preferred) ?? - mapped.find((x) => x.includes?.(match)) - widget.value = value ?? preferred -} - -function getPosToRightOfNode(fromNode: LGraphNode) { - const nodes = app.canvas.graph?.nodes - if (!nodes) throw new TypeError('Could not get graph nodes') - - const pos = [ - fromNode.pos[0] + fromNode.size[0] + 100, - fromNode.pos[1] - ] satisfies Point - - while (nodes.find((x) => isPointTooClose(x.pos, pos))) { - pos[0] += 20 - pos[1] += 20 - } - - return pos -} - -function connectPreviousLatent(fromNode: LGraphNode, toEditNode: LGraphNode) { - const { canvas } = app - const { graph } = canvas - if (!graph) throw new TypeError('Graph is not initialized') - - const l = findNearestOutputOfType([fromNode], 'LATENT') - if (!l) { - const imageOutput = findNearestOutputOfType([fromNode], 'IMAGE') - if (!imageOutput) throw new TypeError('No image output found') - - const vaeEncode = LiteGraph.createNode('VAEEncode') - if (!vaeEncode) throw new TypeError('Failed to create node') - - const { node: imageNode, index: imageIndex } = imageOutput - graph.add(vaeEncode) - vaeEncode.pos = getPosToRightOfNode(fromNode) - vaeEncode.pos[1] -= 200 - - vaeEncode.connect(0, toEditNode, 0) - imageNode.connect(imageIndex, vaeEncode, 0) - return - } - - const { node, index } = l - - node.connect(index, toEditNode, 0) -} - -function getInputNodes(node: LGraphNode): LGraphNode[] { - return node.inputs - .map((x) => LLink.resolve(x.link, app.graph)?.outputNode) - .filter((x) => !!x) -} - -function getOutputOfType( - node: LGraphNode, - type: string -): { - output: INodeOutputSlot - index: number -} { - const index = node.outputs.findIndex((x) => x.type === type) - const output = node.outputs[index] - return { output, index } -} - -function findNearestOutputOfType( - nodes: Iterable, - type: string = 'LATENT', - depth: number = 0 -): { node: LGraphNode; index: number } | undefined { - for (const node of nodes) { - const { output, index } = getOutputOfType(node, type) - if (output) return { node, index } - } - - if (depth < 3) { - const closestNodes = new Set([...nodes].flatMap((x) => getInputNodes(x))) - const res = findNearestOutputOfType(closestNodes, type, depth + 1) - if (res) return res - } -} - -function isPointTooClose(a: Point, b: Point, precision: number = 5) { - return Math.abs(a[0] - b[0]) < precision && Math.abs(a[1] - b[1]) < precision -} From 5f349ed3cd409685d62e67df18982e063b49587d Mon Sep 17 00:00:00 2001 From: Jin Yi Date: Thu, 21 Aug 2025 00:15:43 +0900 Subject: [PATCH 09/21] chore: storybook-doc added (#5122) --- .storybook/README.md | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/.storybook/README.md b/.storybook/README.md index f35753d27..eff19a67d 100644 --- a/.storybook/README.md +++ b/.storybook/README.md @@ -93,6 +93,44 @@ export const WithVariant: Story = { ## Development Tips +## ComfyUI Storybook Guidelines + +### Scope – When to Create Stories +- **PrimeVue components**: + No need to create stories. Just refer to the official PrimeVue documentation. +- **Custom shared components (design system components)**: + Always create stories. These components are built in collaboration with designers, and Storybook serves as both documentation and a communication tool. +- **Container components (logic-heavy)**: + Do not create stories. Only the underlying pure UI components should be included in Storybook. + +### Maintenance Philosophy +- Stories are lightweight and generally stable. + Once created, they rarely need updates unless: + - The design changes + - New props (e.g. size, color variants) are introduced +- For existing usage patterns, simply copy real code examples into Storybook to create stories. + +### File Placement +- Keep `*.stories.ts` files at the **same level as the component** (similar to test files). +- This makes it easier to check usage examples without navigating to another directory. + +### Developer/Designer Workflow +- **UI vs Container**: Separate pure UI components from container components. + Only UI components should live in Storybook. +- **Communication Tool**: Storybook is not just about code quality—it enables designers and developers to see: + - Which props exist + - What cases are covered + - How variants (e.g. size, colors) look in isolation +- **Example**: + `PackActionButton.vue` wraps a PrimeVue button with additional logic. + → Only create a story for the base UI button, not for the wrapper. + +### Suggested Workflow +1. Use PrimeVue docs for standard components +2. Use Storybook for **shared/custom components** that define our design system +3. Keep story files alongside components +4. When in doubt, focus on components reused across the app or those that need to be showcased to designers + ### Best Practices 1. **Keep Stories Simple**: Each story should demonstrate one specific use case @@ -135,6 +173,7 @@ export const WithLongText: Story = { - **`main.ts`**: Core Storybook configuration and Vite integration - **`preview.ts`**: Global decorators, parameters, and Vue app setup - **`manager.ts`**: Storybook UI customization (if needed) +- **`preview-head.html`**: Injects custom HTML into the `` of every Storybook iframe (used for global styles, fonts, or fixes for iframe-specific issues) ## Chromatic Visual Testing @@ -170,4 +209,4 @@ This Storybook setup includes: - PrimeVue component library integration - Proper alias resolution for `@/` imports -For component-specific examples, see the NodePreview stories in `src/components/node/`. \ No newline at end of file +For component-specific examples, see the NodePreview stories in `src/components/node/`. From 240774842563b303cfe332a89e2677ee0081a8a9 Mon Sep 17 00:00:00 2001 From: Jin Yi Date: Thu, 21 Aug 2025 01:36:19 +0900 Subject: [PATCH 10/21] [feat] Add enhanced filter UI components with search and clear functionality (#5119) * [feat] Add enhanced filter UI components with search and clear functionality - Add SearchBox, clear all button, and item count to MultiSelect header - Add 'fit-content' size option to button types for flexible sizing - Update SingleSelect and ModelSelector components for consistency - Add localization strings for item selection and clear all functionality Co-Authored-By: Claude * Update locales [skip ci] --------- Co-authored-by: Claude Co-authored-by: github-actions --- src/components/input/MultiSelect.vue | 88 ++++++++++++++++++++----- src/components/input/SearchBox.vue | 21 ++++-- src/components/input/SingleSelect.vue | 2 +- src/components/widget/ModelSelector.vue | 4 ++ src/locales/ar/main.json | 3 + src/locales/en/main.json | 3 + src/locales/es/main.json | 3 + src/locales/fr/main.json | 3 + src/locales/ja/main.json | 3 + src/locales/ko/main.json | 3 + src/locales/ru/main.json | 3 + src/locales/zh-TW/main.json | 3 + src/locales/zh/main.json | 3 + src/types/buttonTypes.ts | 4 +- 14 files changed, 121 insertions(+), 25 deletions(-) diff --git a/src/components/input/MultiSelect.vue b/src/components/input/MultiSelect.vue index 2dd01dcb5..d1d02a4f1 100644 --- a/src/components/input/MultiSelect.vue +++ b/src/components/input/MultiSelect.vue @@ -9,6 +9,41 @@ :max-selected-labels="0" :pt="pt" > + + - +
() +import SearchBox from '@/components/input/SearchBox.vue' -const selectedItems = defineModel<{ name: string; value: string }[]>({ +import TextButton from '../button/TextButton.vue' + +type Option = { name: string; value: string } + +interface Props { + /** Input label shown on the trigger button */ + label?: string + /** Static options for the multiselect (when not using async search) */ + options: Option[] + /** Show search box in the panel header */ + hasSearchBox?: boolean + /** Show selected count text in the panel header */ + showSelectedCount?: boolean + /** Show "Clear all" action in the panel header */ + hasClearButton?: boolean + /** Placeholder for the search input */ + searchPlaceholder?: string +} +const { + label, + options, + hasSearchBox = false, + showSelectedCount = false, + hasClearButton = false, + searchPlaceholder = 'Search...' +} = defineProps() + +const selectedItems = defineModel({ required: true }) - +const searchQuery = defineModel('searchQuery') const selectedCount = computed(() => selectedItems.value.length) -/** - * Pure unstyled mode using only the PrimeVue PT API. - * All PrimeVue built-in checkboxes/headers are hidden via PT (no :deep hacks). - * Visual output matches the previous version exactly. - */ const pt = computed(() => ({ root: ({ props }: MultiSelectPassThroughMethodOptions) => ({ class: [ @@ -97,19 +151,19 @@ const pt = computed(() => ({ dropdown: { class: 'flex shrink-0 cursor-pointer items-center justify-center px-3' }, - header: { class: 'hidden' }, - + header: () => ({ + class: + hasSearchBox || showSelectedCount || hasClearButton ? 'block' : 'hidden' + }), // Overlay & list visuals unchanged overlay: - 'mt-2 bg-white dark-theme:bg-zinc-800 text-neutral dark-theme:text-white rounded-lg border border-solid border-zinc-100', + 'mt-2 bg-white dark-theme:bg-zinc-800 text-neutral dark-theme:text-white rounded-lg border border-solid border-zinc-100 dark-theme:border-zinc-700', list: { class: 'flex flex-col gap-1 p-0 list-none border-none text-xs' }, - // Option row hover tone identical option: 'flex gap-1 items-center p-2 hover:bg-neutral-100/50 dark-theme:hover:bg-zinc-700/50', - // Hide built-in checkboxes entirely via PT (no :deep) pcHeaderCheckbox: { root: { class: 'hidden' }, diff --git a/src/components/input/SearchBox.vue b/src/components/input/SearchBox.vue index 08075b18b..462668a12 100644 --- a/src/components/input/SearchBox.vue +++ b/src/components/input/SearchBox.vue @@ -1,8 +1,6 @@