Merge branch 'main' into sno-storybook--settings-panel
67
.github/actions/setup-frontend/action.yml
vendored
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
name: Setup Frontend
|
||||||
|
description: 'Setup ComfyUI frontend development environment'
|
||||||
|
inputs:
|
||||||
|
extra_server_params:
|
||||||
|
description: 'Additional parameters to pass to ComfyUI server'
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
runs:
|
||||||
|
using: 'composite'
|
||||||
|
steps:
|
||||||
|
- name: Checkout ComfyUI
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: 'comfyanonymous/ComfyUI'
|
||||||
|
path: 'ComfyUI'
|
||||||
|
|
||||||
|
- name: Checkout ComfyUI_frontend
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: 'Comfy-Org/ComfyUI_frontend'
|
||||||
|
path: 'ComfyUI_frontend'
|
||||||
|
|
||||||
|
- name: Copy ComfyUI_devtools from frontend repo
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
mkdir -p ComfyUI/custom_nodes/ComfyUI_devtools
|
||||||
|
cp -r ComfyUI_frontend/tools/devtools/* ComfyUI/custom_nodes/ComfyUI_devtools/
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 'lts/*'
|
||||||
|
cache: 'pnpm'
|
||||||
|
cache-dependency-path: 'ComfyUI_frontend/pnpm-lock.yaml'
|
||||||
|
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.10'
|
||||||
|
|
||||||
|
- name: Install Python requirements
|
||||||
|
shell: bash
|
||||||
|
working-directory: ComfyUI
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
|
||||||
|
pip install -r requirements.txt
|
||||||
|
pip install wait-for-it
|
||||||
|
|
||||||
|
- name: Build & Install ComfyUI_frontend
|
||||||
|
shell: bash
|
||||||
|
working-directory: ComfyUI_frontend
|
||||||
|
run: |
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
pnpm build
|
||||||
|
|
||||||
|
- name: Start ComfyUI server
|
||||||
|
shell: bash
|
||||||
|
working-directory: ComfyUI
|
||||||
|
run: |
|
||||||
|
python main.py --cpu --multi-user --front-end-root ../ComfyUI_frontend/dist ${{ inputs.extra_server_params }} &
|
||||||
|
wait-for-it --service 127.0.0.1:8188 -t 600
|
||||||
@@ -60,7 +60,7 @@ jobs:
|
|||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
57
.github/workflows/chromatic.yaml
vendored
@@ -1,57 +0,0 @@
|
|||||||
name: 'Chromatic'
|
|
||||||
|
|
||||||
# - [Automate Chromatic with GitHub Actions • Chromatic docs]( https://www.chromatic.com/docs/github-actions/ )
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch: # Allow manual triggering
|
|
||||||
pull_request:
|
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
chromatic-deployment:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
# Only run for PRs from version-bump-* branches or manual triggers
|
|
||||||
if: github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'version-bump-')
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0 # Required for Chromatic baseline
|
|
||||||
|
|
||||||
- name: Install pnpm
|
|
||||||
uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
version: 10
|
|
||||||
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: '20'
|
|
||||||
cache: 'pnpm'
|
|
||||||
|
|
||||||
|
|
||||||
- name: Cache tool outputs
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
.cache
|
|
||||||
storybook-static
|
|
||||||
tsconfig.tsbuildinfo
|
|
||||||
key: storybook-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('src/**/*.{ts,vue,js}', '*.config.*', '.storybook/**/*') }}
|
|
||||||
restore-keys: |
|
|
||||||
storybook-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-
|
|
||||||
storybook-cache-${{ runner.os }}-
|
|
||||||
storybook-tools-cache-${{ runner.os }}-
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install --frozen-lockfile
|
|
||||||
|
|
||||||
- name: Build Storybook and run Chromatic
|
|
||||||
id: chromatic
|
|
||||||
uses: chromaui/action@latest
|
|
||||||
with:
|
|
||||||
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
|
||||||
buildScriptName: build-storybook
|
|
||||||
autoAcceptChanges: 'main' # Auto-accept changes on main branch
|
|
||||||
exitOnceUploaded: true # Don't wait for UI tests to complete
|
|
||||||
|
|
||||||
13
.github/workflows/claude-pr-review.yml
vendored
@@ -29,11 +29,9 @@ jobs:
|
|||||||
- name: Check if we should proceed
|
- name: Check if we should proceed
|
||||||
id: check-status
|
id: check-status
|
||||||
run: |
|
run: |
|
||||||
# Get all check runs for this commit
|
CHECK_RUNS=$(gh api repos/${{ github.repository }}/commits/${{ github.event.pull_request.head.sha }}/check-runs --jq '.check_runs[] | select(.name | test("lint-and-format")) | {name, conclusion}')
|
||||||
CHECK_RUNS=$(gh api repos/${{ github.repository }}/commits/${{ github.event.pull_request.head.sha }}/check-runs --jq '.check_runs[] | select(.name | test("lint-and-format|test|playwright-tests")) | {name, conclusion}')
|
|
||||||
|
if echo "$CHECK_RUNS" | grep -Eq '"conclusion": "(failure|cancelled|timed_out|action_required)"'; then
|
||||||
# Check if any required checks failed
|
|
||||||
if echo "$CHECK_RUNS" | grep -q '"conclusion": "failure"'; then
|
|
||||||
echo "Some CI checks failed - skipping Claude review"
|
echo "Some CI checks failed - skipping Claude review"
|
||||||
echo "proceed=false" >> $GITHUB_OUTPUT
|
echo "proceed=false" >> $GITHUB_OUTPUT
|
||||||
else
|
else
|
||||||
@@ -50,9 +48,10 @@ jobs:
|
|||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
ref: refs/pull/${{ github.event.pull_request.number }}/head
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
@@ -86,4 +85,4 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
|
COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
|
||||||
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||||
REPOSITORY: ${{ github.repository }}
|
REPOSITORY: ${{ github.repository }}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ jobs:
|
|||||||
version: ${{ steps.current_version.outputs.version }}
|
version: ${{ steps.current_version.outputs.version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
@@ -62,7 +62,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
- name: Download dist artifact
|
- name: Download dist artifact
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
@@ -18,7 +18,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
token: ${{ secrets.PR_GH_TOKEN || secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.PR_GH_TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
@@ -15,7 +15,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
2
.github/workflows/lint-and-format.yaml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout PR
|
- name: Checkout PR
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
ref: ${{ github.event.pull_request.head.ref }}
|
ref: ${{ github.event.pull_request.head.ref }}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ jobs:
|
|||||||
echo "Is forked: ${{ github.event.workflow_run.head_repository.full_name != github.event.workflow_run.repository.full_name }}"
|
echo "Is forked: ${{ github.event.workflow_run.head_repository.full_name != github.event.workflow_run.repository.full_name }}"
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Get PR Number
|
- name: Get PR Number
|
||||||
id: pr
|
id: pr
|
||||||
126
.github/workflows/pr-storybook-comment.yaml
vendored
@@ -1,126 +0,0 @@
|
|||||||
name: PR Storybook Comment
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_run:
|
|
||||||
workflows: ['Chromatic']
|
|
||||||
types: [requested, completed]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
comment-storybook:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: >-
|
|
||||||
github.repository == 'Comfy-Org/ComfyUI_frontend'
|
|
||||||
&& github.event.workflow_run.event == 'pull_request'
|
|
||||||
&& startsWith(github.event.workflow_run.head_branch, 'version-bump-')
|
|
||||||
permissions:
|
|
||||||
pull-requests: write
|
|
||||||
actions: read
|
|
||||||
steps:
|
|
||||||
- name: Get PR number
|
|
||||||
id: pr
|
|
||||||
uses: actions/github-script@v7
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const { data: pullRequests } = await github.rest.pulls.list({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
state: 'open',
|
|
||||||
head: `${context.repo.owner}:${context.payload.workflow_run.head_branch}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (pullRequests.length === 0) {
|
|
||||||
console.log('No open PR found for this branch');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return pullRequests[0].number;
|
|
||||||
|
|
||||||
- name: Log when no PR found
|
|
||||||
if: steps.pr.outputs.result == 'null'
|
|
||||||
run: |
|
|
||||||
echo "⚠️ No open PR found for branch: ${{ github.event.workflow_run.head_branch }}"
|
|
||||||
echo "Workflow run ID: ${{ github.event.workflow_run.id }}"
|
|
||||||
echo "Repository: ${{ github.event.workflow_run.repository.full_name }}"
|
|
||||||
echo "Event: ${{ github.event.workflow_run.event }}"
|
|
||||||
|
|
||||||
- name: Get workflow run details
|
|
||||||
if: steps.pr.outputs.result != 'null' && github.event.action == 'completed'
|
|
||||||
id: workflow-run
|
|
||||||
uses: actions/github-script@v7
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const run = await github.rest.actions.getWorkflowRun({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
run_id: context.payload.workflow_run.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
conclusion: run.data.conclusion,
|
|
||||||
html_url: run.data.html_url
|
|
||||||
};
|
|
||||||
|
|
||||||
- name: Get completion time
|
|
||||||
id: completion-time
|
|
||||||
run: echo "time=$(date -u '+%m/%d/%Y, %I:%M:%S %p')" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Comment PR - Storybook Started
|
|
||||||
if: steps.pr.outputs.result != 'null' && github.event.action == 'requested'
|
|
||||||
uses: edumserrano/find-create-or-update-comment@82880b65c8a3a6e4c70aa05a204995b6c9696f53 # v3.0.0
|
|
||||||
with:
|
|
||||||
issue-number: ${{ steps.pr.outputs.result }}
|
|
||||||
body-includes: '<!-- STORYBOOK_BUILD_STATUS -->'
|
|
||||||
comment-author: 'github-actions[bot]'
|
|
||||||
edit-mode: replace
|
|
||||||
body: |
|
|
||||||
<!-- STORYBOOK_BUILD_STATUS -->
|
|
||||||
## 🎨 Storybook Build Status
|
|
||||||
|
|
||||||
<img alt='comfy-loading-gif' src='https://github.com/user-attachments/assets/755c86ee-e445-4ea8-bc2c-cca85df48686' width='14px' height='14px' style='vertical-align: middle; margin-right: 4px;' /> **Build is starting...**
|
|
||||||
|
|
||||||
⏰ Started at: ${{ steps.completion-time.outputs.time }} UTC
|
|
||||||
|
|
||||||
### 🚀 Building Storybook
|
|
||||||
- 📦 Installing dependencies...
|
|
||||||
- 🔧 Building Storybook components...
|
|
||||||
- 🎨 Running Chromatic visual tests...
|
|
||||||
|
|
||||||
---
|
|
||||||
⏱️ Please wait while the Storybook build is in progress...
|
|
||||||
|
|
||||||
- name: Comment PR - Storybook Complete
|
|
||||||
if: steps.pr.outputs.result != 'null' && github.event.action == 'completed'
|
|
||||||
uses: edumserrano/find-create-or-update-comment@82880b65c8a3a6e4c70aa05a204995b6c9696f53 # v3.0.0
|
|
||||||
with:
|
|
||||||
issue-number: ${{ steps.pr.outputs.result }}
|
|
||||||
body-includes: '<!-- STORYBOOK_BUILD_STATUS -->'
|
|
||||||
comment-author: 'github-actions[bot]'
|
|
||||||
edit-mode: replace
|
|
||||||
body: |
|
|
||||||
<!-- STORYBOOK_BUILD_STATUS -->
|
|
||||||
## 🎨 Storybook Build Status
|
|
||||||
|
|
||||||
${{
|
|
||||||
fromJSON(steps.workflow-run.outputs.result).conclusion == 'success' && '✅'
|
|
||||||
|| fromJSON(steps.workflow-run.outputs.result).conclusion == 'skipped' && '⏭️'
|
|
||||||
|| fromJSON(steps.workflow-run.outputs.result).conclusion == 'cancelled' && '🚫'
|
|
||||||
|| '❌'
|
|
||||||
}} **${{
|
|
||||||
fromJSON(steps.workflow-run.outputs.result).conclusion == 'success' && 'Build completed successfully!'
|
|
||||||
|| fromJSON(steps.workflow-run.outputs.result).conclusion == 'skipped' && 'Build skipped.'
|
|
||||||
|| fromJSON(steps.workflow-run.outputs.result).conclusion == 'cancelled' && 'Build cancelled.'
|
|
||||||
|| 'Build failed!'
|
|
||||||
}}**
|
|
||||||
|
|
||||||
⏰ Completed at: ${{ steps.completion-time.outputs.time }} UTC
|
|
||||||
|
|
||||||
### 🔗 Links
|
|
||||||
- [📊 View Workflow Run](${{ fromJSON(steps.workflow-run.outputs.result).html_url }})
|
|
||||||
|
|
||||||
---
|
|
||||||
${{
|
|
||||||
fromJSON(steps.workflow-run.outputs.result).conclusion == 'success' && '🎉 Your Storybook is ready for review!'
|
|
||||||
|| fromJSON(steps.workflow-run.outputs.result).conclusion == 'skipped' && 'ℹ️ Chromatic was skipped for this PR.'
|
|
||||||
|| fromJSON(steps.workflow-run.outputs.result).conclusion == 'cancelled' && 'ℹ️ The Chromatic run was cancelled.'
|
|
||||||
|| '⚠️ Please check the workflow logs for error details.'
|
|
||||||
}}
|
|
||||||
90
.github/workflows/pr-storybook-deploy-forks.yaml
vendored
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
name: PR Storybook Deploy (Forks)
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_run:
|
||||||
|
workflows: ['Storybook and Chromatic CI']
|
||||||
|
types: [requested, completed]
|
||||||
|
|
||||||
|
env:
|
||||||
|
DATE_FORMAT: '+%m/%d/%Y, %I:%M:%S %p'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy-and-comment-forked-pr:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: |
|
||||||
|
github.repository == 'Comfy-Org/ComfyUI_frontend' &&
|
||||||
|
github.event.workflow_run.event == 'pull_request' &&
|
||||||
|
github.event.workflow_run.head_repository != null &&
|
||||||
|
github.event.workflow_run.repository != null &&
|
||||||
|
github.event.workflow_run.head_repository.full_name != github.event.workflow_run.repository.full_name
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
actions: read
|
||||||
|
steps:
|
||||||
|
- name: Log workflow trigger info
|
||||||
|
run: |
|
||||||
|
echo "Repository: ${{ github.repository }}"
|
||||||
|
echo "Event: ${{ github.event.workflow_run.event }}"
|
||||||
|
echo "Head repo: ${{ github.event.workflow_run.head_repository.full_name || 'null' }}"
|
||||||
|
echo "Base repo: ${{ github.event.workflow_run.repository.full_name || 'null' }}"
|
||||||
|
echo "Is forked: ${{ github.event.workflow_run.head_repository.full_name != github.event.workflow_run.repository.full_name }}"
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- name: Get PR Number
|
||||||
|
id: pr
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const { data: prs } = await github.rest.pulls.list({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
state: 'open',
|
||||||
|
});
|
||||||
|
|
||||||
|
const pr = prs.find(p => p.head.sha === context.payload.workflow_run.head_sha);
|
||||||
|
|
||||||
|
if (!pr) {
|
||||||
|
console.log('No PR found for SHA:', context.payload.workflow_run.head_sha);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Found PR #${pr.number} from fork: ${context.payload.workflow_run.head_repository.full_name}`);
|
||||||
|
return pr.number;
|
||||||
|
|
||||||
|
- name: Handle Storybook Start
|
||||||
|
if: steps.pr.outputs.result != 'null' && github.event.action == 'requested'
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ github.token }}
|
||||||
|
run: |
|
||||||
|
chmod +x scripts/cicd/pr-storybook-deploy-and-comment.sh
|
||||||
|
./scripts/cicd/pr-storybook-deploy-and-comment.sh \
|
||||||
|
"${{ steps.pr.outputs.result }}" \
|
||||||
|
"${{ github.event.workflow_run.head_branch }}" \
|
||||||
|
"starting" \
|
||||||
|
"$(date -u '${{ env.DATE_FORMAT }}')"
|
||||||
|
|
||||||
|
- name: Download and Deploy Storybook
|
||||||
|
if: steps.pr.outputs.result != 'null' && github.event.action == 'completed' && github.event.workflow_run.conclusion == 'success'
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run-id: ${{ github.event.workflow_run.id }}
|
||||||
|
name: storybook-static
|
||||||
|
path: storybook-static
|
||||||
|
|
||||||
|
- name: Handle Storybook Completion
|
||||||
|
if: steps.pr.outputs.result != 'null' && github.event.action == 'completed'
|
||||||
|
env:
|
||||||
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
|
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
|
GITHUB_TOKEN: ${{ github.token }}
|
||||||
|
WORKFLOW_CONCLUSION: ${{ github.event.workflow_run.conclusion }}
|
||||||
|
WORKFLOW_URL: ${{ github.event.workflow_run.html_url }}
|
||||||
|
run: |
|
||||||
|
chmod +x scripts/cicd/pr-storybook-deploy-and-comment.sh
|
||||||
|
./scripts/cicd/pr-storybook-deploy-and-comment.sh \
|
||||||
|
"${{ steps.pr.outputs.result }}" \
|
||||||
|
"${{ github.event.workflow_run.head_branch }}" \
|
||||||
|
"completed"
|
||||||
231
.github/workflows/storybook-and-chromatic-ci.yaml
vendored
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
name: Storybook and Chromatic CI
|
||||||
|
|
||||||
|
# - [Automate Chromatic with GitHub Actions • Chromatic docs]( https://www.chromatic.com/docs/github-actions/ )
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch: # Allow manual triggering
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# Post starting comment for non-forked PRs
|
||||||
|
comment-on-pr-start:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- name: Post starting comment
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ github.token }}
|
||||||
|
run: |
|
||||||
|
chmod +x scripts/cicd/pr-storybook-deploy-and-comment.sh
|
||||||
|
./scripts/cicd/pr-storybook-deploy-and-comment.sh \
|
||||||
|
"${{ github.event.pull_request.number }}" \
|
||||||
|
"${{ github.head_ref }}" \
|
||||||
|
"starting" \
|
||||||
|
"$(date -u '+%m/%d/%Y, %I:%M:%S %p')"
|
||||||
|
|
||||||
|
# Build Storybook for all PRs (free Cloudflare deployment)
|
||||||
|
storybook-build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
outputs:
|
||||||
|
conclusion: ${{ steps.job-status.outputs.conclusion }}
|
||||||
|
workflow-url: ${{ steps.workflow-url.outputs.url }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Cache tool outputs
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
.cache
|
||||||
|
storybook-static
|
||||||
|
tsconfig.tsbuildinfo
|
||||||
|
key: storybook-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('src/**/*.{ts,vue,js}', '*.config.*', '.storybook/**/*') }}
|
||||||
|
restore-keys: |
|
||||||
|
storybook-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-
|
||||||
|
storybook-cache-${{ runner.os }}-
|
||||||
|
storybook-tools-cache-${{ runner.os }}-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build Storybook
|
||||||
|
run: pnpm build-storybook
|
||||||
|
|
||||||
|
- name: Set job status
|
||||||
|
id: job-status
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
echo "conclusion=${{ job.status }}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Get workflow URL
|
||||||
|
id: workflow-url
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
echo "url=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Upload Storybook build
|
||||||
|
if: success() && github.event.pull_request.head.repo.fork == false
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: storybook-static
|
||||||
|
path: storybook-static/
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
# Chromatic deployment only for version-bump-* branches or manual triggers
|
||||||
|
chromatic-deployment:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && startsWith(github.head_ref, 'version-bump-'))
|
||||||
|
outputs:
|
||||||
|
conclusion: ${{ steps.job-status.outputs.conclusion }}
|
||||||
|
workflow-url: ${{ steps.workflow-url.outputs.url }}
|
||||||
|
chromatic-build-url: ${{ steps.chromatic.outputs.buildUrl }}
|
||||||
|
chromatic-storybook-url: ${{ steps.chromatic.outputs.storybookUrl }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # Required for Chromatic baseline
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Cache tool outputs
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
.cache
|
||||||
|
storybook-static
|
||||||
|
tsconfig.tsbuildinfo
|
||||||
|
key: storybook-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('src/**/*.{ts,vue,js}', '*.config.*', '.storybook/**/*') }}
|
||||||
|
restore-keys: |
|
||||||
|
storybook-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-
|
||||||
|
storybook-cache-${{ runner.os }}-
|
||||||
|
storybook-tools-cache-${{ runner.os }}-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build Storybook and run Chromatic
|
||||||
|
id: chromatic
|
||||||
|
uses: chromaui/action@latest
|
||||||
|
with:
|
||||||
|
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||||
|
buildScriptName: build-storybook
|
||||||
|
autoAcceptChanges: 'main' # Auto-accept changes on main branch
|
||||||
|
exitOnceUploaded: true # Don't wait for UI tests to complete
|
||||||
|
onlyChanged: true # Only capture changed stories
|
||||||
|
|
||||||
|
- name: Set job status
|
||||||
|
id: job-status
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
echo "conclusion=${{ job.status }}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Get workflow URL
|
||||||
|
id: workflow-url
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
echo "url=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
# Deploy and comment for non-forked PRs only
|
||||||
|
deploy-and-comment:
|
||||||
|
needs: [storybook-build]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false && always()
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- name: Download Storybook build
|
||||||
|
if: needs.storybook-build.outputs.conclusion == 'success'
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: storybook-static
|
||||||
|
path: storybook-static
|
||||||
|
|
||||||
|
- name: Make deployment script executable
|
||||||
|
run: chmod +x scripts/cicd/pr-storybook-deploy-and-comment.sh
|
||||||
|
|
||||||
|
- name: Deploy Storybook and comment on PR
|
||||||
|
env:
|
||||||
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
|
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
|
GITHUB_TOKEN: ${{ github.token }}
|
||||||
|
WORKFLOW_CONCLUSION: ${{ needs.storybook-build.outputs.conclusion }}
|
||||||
|
WORKFLOW_URL: ${{ needs.storybook-build.outputs.workflow-url }}
|
||||||
|
run: |
|
||||||
|
./scripts/cicd/pr-storybook-deploy-and-comment.sh \
|
||||||
|
"${{ github.event.pull_request.number }}" \
|
||||||
|
"${{ github.head_ref }}" \
|
||||||
|
"completed"
|
||||||
|
|
||||||
|
# Update comment with Chromatic URLs for version-bump branches
|
||||||
|
update-comment-with-chromatic:
|
||||||
|
needs: [chromatic-deployment, deploy-and-comment]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false && startsWith(github.head_ref, 'version-bump-') && needs.chromatic-deployment.outputs.chromatic-build-url != ''
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
steps:
|
||||||
|
- name: Update comment with Chromatic URLs
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const buildUrl = '${{ needs.chromatic-deployment.outputs.chromatic-build-url }}';
|
||||||
|
const storybookUrl = '${{ needs.chromatic-deployment.outputs.chromatic-storybook-url }}';
|
||||||
|
|
||||||
|
// Find the existing Storybook comment
|
||||||
|
const { data: comments } = await github.rest.issues.listComments({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: ${{ github.event.pull_request.number }}
|
||||||
|
});
|
||||||
|
|
||||||
|
const storybookComment = comments.find(comment =>
|
||||||
|
comment.body.includes('<!-- STORYBOOK_BUILD_STATUS -->')
|
||||||
|
);
|
||||||
|
|
||||||
|
if (storybookComment && buildUrl && storybookUrl) {
|
||||||
|
// Append Chromatic info to existing comment
|
||||||
|
const updatedBody = storybookComment.body.replace(
|
||||||
|
/---\n(.*)$/s,
|
||||||
|
`---\n### 🎨 Chromatic Visual Tests\n- 📊 [View Chromatic Build](${buildUrl})\n- 📚 [View Chromatic Storybook](${storybookUrl})\n\n$1`
|
||||||
|
);
|
||||||
|
|
||||||
|
await github.rest.issues.updateComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
comment_id: storybookComment.id,
|
||||||
|
body: updatedBody
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -15,14 +15,14 @@ jobs:
|
|||||||
playwright-version: ${{ steps.playwright-version.outputs.PLAYWRIGHT_VERSION }}
|
playwright-version: ${{ steps.playwright-version.outputs.PLAYWRIGHT_VERSION }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout ComfyUI
|
- name: Checkout ComfyUI
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
repository: 'comfyanonymous/ComfyUI'
|
repository: 'comfyanonymous/ComfyUI'
|
||||||
path: 'ComfyUI'
|
path: 'ComfyUI'
|
||||||
ref: master
|
ref: master
|
||||||
|
|
||||||
- name: Checkout ComfyUI_frontend
|
- name: Checkout ComfyUI_frontend
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
repository: 'Comfy-Org/ComfyUI_frontend'
|
repository: 'Comfy-Org/ComfyUI_frontend'
|
||||||
path: 'ComfyUI_frontend'
|
path: 'ComfyUI_frontend'
|
||||||
@@ -250,7 +250,7 @@ jobs:
|
|||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout ComfyUI_frontend
|
- name: Checkout ComfyUI_frontend
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
repository: 'Comfy-Org/ComfyUI_frontend'
|
repository: 'Comfy-Org/ComfyUI_frontend'
|
||||||
path: 'ComfyUI_frontend'
|
path: 'ComfyUI_frontend'
|
||||||
@@ -306,7 +306,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Get start time
|
- name: Get start time
|
||||||
id: start-time
|
id: start-time
|
||||||
@@ -333,7 +333,7 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Download all playwright reports
|
- name: Download all playwright reports
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
@@ -16,7 +16,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
@@ -50,7 +50,7 @@ jobs:
|
|||||||
comfy-api-repo-${{ runner.os }}-
|
comfy-api-repo-${{ runner.os }}-
|
||||||
|
|
||||||
- name: Checkout comfy-api repository
|
- name: Checkout comfy-api repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
repository: Comfy-Org/comfy-api
|
repository: Comfy-Org/comfy-api
|
||||||
path: comfy-api
|
path: comfy-api
|
||||||
@@ -17,7 +17,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
@@ -51,7 +51,7 @@ jobs:
|
|||||||
comfyui-manager-repo-${{ runner.os }}-
|
comfyui-manager-repo-${{ runner.os }}-
|
||||||
|
|
||||||
- name: Checkout ComfyUI-Manager repository
|
- name: Checkout ComfyUI-Manager repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
repository: Comfy-Org/ComfyUI-Manager
|
repository: Comfy-Org/ComfyUI-Manager
|
||||||
path: ComfyUI-Manager
|
path: ComfyUI-Manager
|
||||||
2
.github/workflows/update-electron-types.yaml
vendored
@@ -12,7 +12,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
|
|||||||
@@ -22,13 +22,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout ComfyUI
|
- name: Checkout ComfyUI
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
repository: comfyanonymous/ComfyUI
|
repository: comfyanonymous/ComfyUI
|
||||||
path: ComfyUI
|
path: ComfyUI
|
||||||
ref: master
|
ref: master
|
||||||
- name: Checkout ComfyUI_frontend
|
- name: Checkout ComfyUI_frontend
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
repository: Comfy-Org/ComfyUI_frontend
|
repository: Comfy-Org/ComfyUI_frontend
|
||||||
path: ComfyUI_frontend
|
path: ComfyUI_frontend
|
||||||
@@ -37,7 +37,7 @@ jobs:
|
|||||||
mkdir -p ComfyUI/custom_nodes/ComfyUI_devtools
|
mkdir -p ComfyUI/custom_nodes/ComfyUI_devtools
|
||||||
cp -r ComfyUI_frontend/tools/devtools/* ComfyUI/custom_nodes/ComfyUI_devtools/
|
cp -r ComfyUI_frontend/tools/devtools/* ComfyUI/custom_nodes/ComfyUI_devtools/
|
||||||
- name: Checkout custom node repository
|
- name: Checkout custom node repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
repository: ${{ inputs.owner }}/${{ inputs.repository }}
|
repository: ${{ inputs.owner }}/${{ inputs.repository }}
|
||||||
path: 'ComfyUI/custom_nodes/${{ inputs.repository }}'
|
path: 'ComfyUI/custom_nodes/${{ inputs.repository }}'
|
||||||
@@ -14,7 +14,8 @@ jobs:
|
|||||||
if: github.event_name == 'workflow_dispatch' || (github.event.pull_request.head.repo.full_name == github.repository && startsWith(github.head_ref, 'version-bump-'))
|
if: github.event_name == 'workflow_dispatch' || (github.event.pull_request.head.repo.full_name == github.repository && startsWith(github.head_ref, 'version-bump-'))
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v3
|
- name: Setup Frontend
|
||||||
|
uses: ./.github/actions/setup-frontend
|
||||||
|
|
||||||
- name: Cache tool outputs
|
- name: Cache tool outputs
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
@@ -13,7 +13,8 @@ jobs:
|
|||||||
update-locales:
|
update-locales:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v3
|
- name: Setup Frontend
|
||||||
|
uses: ./.github/actions/setup-frontend
|
||||||
- name: Install Playwright Browsers
|
- name: Install Playwright Browsers
|
||||||
run: pnpm exec playwright install chromium --with-deps
|
run: pnpm exec playwright install chromium --with-deps
|
||||||
working-directory: ComfyUI_frontend
|
working-directory: ComfyUI_frontend
|
||||||
@@ -10,7 +10,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event.label.name == 'New Browser Test Expectations'
|
if: github.event.label.name == 'New Browser Test Expectations'
|
||||||
steps:
|
steps:
|
||||||
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v3
|
- name: Checkout workflow repo
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
- name: Setup Frontend
|
||||||
|
uses: ./.github/actions/setup-frontend
|
||||||
- name: Cache Playwright browsers
|
- name: Cache Playwright browsers
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
@@ -10,6 +10,6 @@ jobs:
|
|||||||
json-lint:
|
json-lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- name: Validate JSON syntax
|
- name: Validate JSON syntax
|
||||||
run: ./scripts/cicd/check-json.sh
|
run: ./scripts/cicd/check-json.sh
|
||||||
2
.github/workflows/version-bump.yaml
vendored
@@ -24,7 +24,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
1
.npmrc
@@ -1 +1,2 @@
|
|||||||
ignore-workspace-root-check=true
|
ignore-workspace-root-check=true
|
||||||
|
catalog-mode=prefer
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ Without this flag, parallel tests will conflict and fail randomly.
|
|||||||
|
|
||||||
### ComfyUI devtools
|
### ComfyUI devtools
|
||||||
|
|
||||||
ComfyUI_devtools is now included in this repository under `tools/devtools/`. During CI/CD, these files are automatically copied to the `custom_nodes` directory.
|
ComfyUI_devtools is included in this repository under `tools/devtools/`. During CI/CD, these files are automatically copied to the `custom_nodes` directory.
|
||||||
_ComfyUI_devtools adds additional API endpoints and nodes to ComfyUI for browser testing._
|
_ComfyUI_devtools adds additional API endpoints and nodes to ComfyUI for browser testing._
|
||||||
|
|
||||||
For local development, copy the devtools files to your ComfyUI installation:
|
For local development, copy the devtools files to your ComfyUI installation:
|
||||||
@@ -84,7 +84,7 @@ UI mode features:
|
|||||||
- **Console/Network Tabs**: View logs and API calls at each step
|
- **Console/Network Tabs**: View logs and API calls at each step
|
||||||
- **Attachments Tab**: View all snapshots with expected and actual images
|
- **Attachments Tab**: View all snapshots with expected and actual images
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
For CI or headless testing:
|
For CI or headless testing:
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 68 KiB |
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@comfyorg/comfyui-frontend",
|
"name": "@comfyorg/comfyui-frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.28.3",
|
"version": "1.28.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
|
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
|
||||||
"homepage": "https://comfy.org",
|
"homepage": "https://comfy.org",
|
||||||
|
|||||||
@@ -2,6 +2,114 @@ packages:
|
|||||||
- apps/**
|
- apps/**
|
||||||
- packages/**
|
- packages/**
|
||||||
|
|
||||||
|
catalog:
|
||||||
|
# Core frameworks
|
||||||
|
typescript: ^5.9.2
|
||||||
|
vue: ^3.5.13
|
||||||
|
|
||||||
|
# Build tools
|
||||||
|
'@nx/eslint': 21.4.1
|
||||||
|
'@nx/playwright': 21.4.1
|
||||||
|
'@nx/storybook': 21.4.1
|
||||||
|
'@nx/vite': 21.4.1
|
||||||
|
nx: 21.4.1
|
||||||
|
tsx: ^4.15.6
|
||||||
|
vite: ^5.4.19
|
||||||
|
'@vitejs/plugin-vue': ^5.1.4
|
||||||
|
'vite-plugin-dts': ^4.5.4
|
||||||
|
vue-tsc: ^3.0.7
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
'happy-dom': ^15.11.0
|
||||||
|
jsdom: ^26.1.0
|
||||||
|
'@pinia/testing': ^0.1.5
|
||||||
|
'@playwright/test': ^1.52.0
|
||||||
|
'@vitest/coverage-v8': ^3.2.4
|
||||||
|
'@vitest/ui': ^3.0.0
|
||||||
|
vitest: ^3.2.4
|
||||||
|
'@vue/test-utils': ^2.4.6
|
||||||
|
|
||||||
|
# Linting & Formatting
|
||||||
|
'@eslint/js': ^9.35.0
|
||||||
|
eslint: ^9.34.0
|
||||||
|
'eslint-config-prettier': ^10.1.8
|
||||||
|
'eslint-plugin-prettier': ^5.5.4
|
||||||
|
'eslint-plugin-storybook': ^9.1.6
|
||||||
|
'eslint-plugin-unused-imports': ^4.2.0
|
||||||
|
'eslint-plugin-vue': ^10.4.0
|
||||||
|
globals: ^15.9.0
|
||||||
|
'@intlify/eslint-plugin-vue-i18n': ^4.1.0
|
||||||
|
prettier: ^3.3.2
|
||||||
|
'typescript-eslint': ^8.44.0
|
||||||
|
'vue-eslint-parser': ^10.2.0
|
||||||
|
|
||||||
|
# Vue ecosystem
|
||||||
|
'@sentry/vue': ^8.48.0
|
||||||
|
'@vueuse/core': ^11.0.0
|
||||||
|
'@vueuse/integrations': ^13.9.0
|
||||||
|
'vite-plugin-html': ^3.2.2
|
||||||
|
'vite-plugin-vue-devtools': ^7.7.6
|
||||||
|
pinia: ^2.1.7
|
||||||
|
'vue-i18n': ^9.14.3
|
||||||
|
'vue-router': ^4.4.3
|
||||||
|
vuefire: ^3.2.1
|
||||||
|
|
||||||
|
# PrimeVue UI framework
|
||||||
|
'@primeuix/forms': 0.0.2
|
||||||
|
'@primeuix/styled': 0.3.2
|
||||||
|
'@primeuix/utils': ^0.3.2
|
||||||
|
'@primevue/core': ^4.2.5
|
||||||
|
'@primevue/forms': ^4.2.5
|
||||||
|
'@primevue/icons': 4.2.5
|
||||||
|
'@primevue/themes': ^4.2.5
|
||||||
|
primeicons: ^7.0.0
|
||||||
|
primevue: ^4.2.5
|
||||||
|
|
||||||
|
# Tailwind CSS and design
|
||||||
|
'@iconify/json': ^2.2.380
|
||||||
|
'@iconify-json/lucide': ^1.1.178
|
||||||
|
'@iconify/tailwind': ^1.1.3
|
||||||
|
'@tailwindcss/vite': ^4.1.12
|
||||||
|
tailwindcss: ^4.1.12
|
||||||
|
'tailwindcss-primeui': ^0.6.1
|
||||||
|
'tw-animate-css': ^1.3.8
|
||||||
|
'unplugin-icons': ^0.22.0
|
||||||
|
'unplugin-vue-components': ^0.28.0
|
||||||
|
|
||||||
|
# Storybook
|
||||||
|
'@storybook/addon-docs': ^9.1.1
|
||||||
|
storybook: ^9.1.6
|
||||||
|
'@storybook/vue3': ^9.1.1
|
||||||
|
'@storybook/vue3-vite': ^9.1.1
|
||||||
|
|
||||||
|
# Data and validation
|
||||||
|
algoliasearch: ^5.21.0
|
||||||
|
firebase: ^11.6.0
|
||||||
|
yjs: ^13.6.27
|
||||||
|
zod: ^3.23.8
|
||||||
|
'zod-validation-error': ^3.3.0
|
||||||
|
|
||||||
|
# Dev tools
|
||||||
|
dotenv: ^16.4.5
|
||||||
|
husky: ^9.0.11
|
||||||
|
jiti: 2.4.2
|
||||||
|
knip: ^5.62.0
|
||||||
|
'lint-staged': ^15.2.7
|
||||||
|
|
||||||
|
# Type definitions
|
||||||
|
'@types/fs-extra': ^11.0.4
|
||||||
|
'@types/jsdom': ^21.1.7
|
||||||
|
'@types/node': ^20.14.8
|
||||||
|
'@types/semver': ^7.7.0
|
||||||
|
'@types/three': ^0.169.0
|
||||||
|
'vue-component-type-helpers': ^3.0.7
|
||||||
|
'zod-to-json-schema': ^3.24.1
|
||||||
|
|
||||||
|
# i18n
|
||||||
|
'@alloc/quick-lru': ^5.2.0
|
||||||
|
'@lobehub/i18n-cli': ^1.25.1
|
||||||
|
'@trivago/prettier-plugin-sort-imports': ^5.2.0
|
||||||
|
|
||||||
ignoredBuiltDependencies:
|
ignoredBuiltDependencies:
|
||||||
- '@firebase/util'
|
- '@firebase/util'
|
||||||
- protobufjs
|
- protobufjs
|
||||||
|
|||||||
247
scripts/cicd/pr-storybook-deploy-and-comment.sh
Executable file
@@ -0,0 +1,247 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Deploy Storybook to Cloudflare Pages and comment on PR
|
||||||
|
# Usage: ./pr-storybook-deploy-and-comment.sh <pr_number> <branch_name> <status> [start_time]
|
||||||
|
|
||||||
|
# Input validation
|
||||||
|
# Validate PR number is numeric
|
||||||
|
case "$1" in
|
||||||
|
''|*[!0-9]*)
|
||||||
|
echo "Error: PR_NUMBER must be numeric" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
PR_NUMBER="$1"
|
||||||
|
|
||||||
|
# Sanitize and validate branch name (allow alphanumeric, dots, dashes, underscores, slashes)
|
||||||
|
BRANCH_NAME=$(echo "$2" | sed 's/[^a-zA-Z0-9._/-]//g')
|
||||||
|
if [ -z "$BRANCH_NAME" ]; then
|
||||||
|
echo "Error: Invalid or empty branch name" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate status parameter
|
||||||
|
STATUS="${3:-completed}"
|
||||||
|
case "$STATUS" in
|
||||||
|
starting|completed) ;;
|
||||||
|
*)
|
||||||
|
echo "Error: STATUS must be 'starting' or 'completed'" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
START_TIME="${4:-$(date -u '+%m/%d/%Y, %I:%M:%S %p')}"
|
||||||
|
|
||||||
|
# Required environment variables
|
||||||
|
: "${GITHUB_TOKEN:?GITHUB_TOKEN is required}"
|
||||||
|
: "${GITHUB_REPOSITORY:?GITHUB_REPOSITORY is required}"
|
||||||
|
|
||||||
|
# Cloudflare variables only required for deployment
|
||||||
|
if [ "$STATUS" = "completed" ]; then
|
||||||
|
: "${CLOUDFLARE_API_TOKEN:?CLOUDFLARE_API_TOKEN is required for deployment}"
|
||||||
|
: "${CLOUDFLARE_ACCOUNT_ID:?CLOUDFLARE_ACCOUNT_ID is required for deployment}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
COMMENT_MARKER="<!-- STORYBOOK_BUILD_STATUS -->"
|
||||||
|
|
||||||
|
# Install wrangler if not available (output to stderr for debugging)
|
||||||
|
if ! command -v wrangler > /dev/null 2>&1; then
|
||||||
|
echo "Installing wrangler v4..." >&2
|
||||||
|
npm install -g wrangler@^4.0.0 >&2 || {
|
||||||
|
echo "Failed to install wrangler" >&2
|
||||||
|
echo "failed"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Deploy Storybook report, WARN: ensure inputs are sanitized before calling this function
|
||||||
|
deploy_storybook() {
|
||||||
|
dir="$1"
|
||||||
|
branch="$2"
|
||||||
|
|
||||||
|
[ ! -d "$dir" ] && echo "failed" && return
|
||||||
|
|
||||||
|
project="comfy-storybook"
|
||||||
|
|
||||||
|
echo "Deploying Storybook to project $project on branch $branch..." >&2
|
||||||
|
|
||||||
|
# Try deployment up to 3 times
|
||||||
|
i=1
|
||||||
|
while [ $i -le 3 ]; do
|
||||||
|
echo "Deployment attempt $i of 3..." >&2
|
||||||
|
# Branch is already sanitized, use it directly
|
||||||
|
if output=$(wrangler pages deploy "$dir" \
|
||||||
|
--project-name="$project" \
|
||||||
|
--branch="$branch" 2>&1); then
|
||||||
|
|
||||||
|
# Extract URL from output (improved regex for valid URL characters)
|
||||||
|
url=$(echo "$output" | grep -oE 'https://[a-zA-Z0-9.-]+\.pages\.dev\S*' | head -1)
|
||||||
|
result="${url:-https://${branch}.${project}.pages.dev}"
|
||||||
|
echo "Success! URL: $result" >&2
|
||||||
|
echo "$result" # Only this goes to stdout for capture
|
||||||
|
return
|
||||||
|
else
|
||||||
|
echo "Deployment failed on attempt $i: $output" >&2
|
||||||
|
fi
|
||||||
|
[ $i -lt 3 ] && sleep 10
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "failed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Post or update GitHub comment
|
||||||
|
post_comment() {
|
||||||
|
body="$1"
|
||||||
|
temp_file=$(mktemp)
|
||||||
|
echo "$body" > "$temp_file"
|
||||||
|
|
||||||
|
if command -v gh > /dev/null 2>&1; then
|
||||||
|
# Find existing comment ID
|
||||||
|
existing=$(gh api "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" \
|
||||||
|
--jq ".[] | select(.body | contains(\"$COMMENT_MARKER\")) | .id" | head -1)
|
||||||
|
|
||||||
|
if [ -n "$existing" ]; then
|
||||||
|
# Update specific comment by ID
|
||||||
|
gh api --method PATCH "repos/$GITHUB_REPOSITORY/issues/comments/$existing" \
|
||||||
|
--field body="$(cat "$temp_file")"
|
||||||
|
else
|
||||||
|
gh pr comment "$PR_NUMBER" --body-file "$temp_file"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "GitHub CLI not available, outputting comment:"
|
||||||
|
cat "$temp_file"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f "$temp_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main execution
|
||||||
|
if [ "$STATUS" = "starting" ]; then
|
||||||
|
# Check if this is a version-bump branch
|
||||||
|
IS_VERSION_BUMP="false"
|
||||||
|
if echo "$BRANCH_NAME" | grep -q "^version-bump-"; then
|
||||||
|
IS_VERSION_BUMP="true"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Post starting comment with appropriate message
|
||||||
|
if [ "$IS_VERSION_BUMP" = "true" ]; then
|
||||||
|
comment=$(cat <<EOF
|
||||||
|
$COMMENT_MARKER
|
||||||
|
## 🎨 Storybook Build Status
|
||||||
|
|
||||||
|
<img alt='loading' src='https://github.com/user-attachments/assets/755c86ee-e445-4ea8-bc2c-cca85df48686' width='14px' height='14px'/> **Build is starting...**
|
||||||
|
|
||||||
|
⏰ Started at: $START_TIME UTC
|
||||||
|
|
||||||
|
### 🚀 Building Storybook
|
||||||
|
- 📦 Installing dependencies...
|
||||||
|
- 🔧 Building Storybook components...
|
||||||
|
- 🎨 Running Chromatic visual tests...
|
||||||
|
|
||||||
|
---
|
||||||
|
⏱️ Please wait while the Storybook build is in progress...
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
else
|
||||||
|
comment=$(cat <<EOF
|
||||||
|
$COMMENT_MARKER
|
||||||
|
## 🎨 Storybook Build Status
|
||||||
|
|
||||||
|
<img alt='loading' src='https://github.com/user-attachments/assets/755c86ee-e445-4ea8-bc2c-cca85df48686' width='14px' height='14px'/> **Build is starting...**
|
||||||
|
|
||||||
|
⏰ Started at: $START_TIME UTC
|
||||||
|
|
||||||
|
### 🚀 Building Storybook
|
||||||
|
- 📦 Installing dependencies...
|
||||||
|
- 🔧 Building Storybook components...
|
||||||
|
- 🌐 Preparing deployment to Cloudflare Pages...
|
||||||
|
|
||||||
|
---
|
||||||
|
⏱️ Please wait while the Storybook build is in progress...
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
post_comment "$comment"
|
||||||
|
|
||||||
|
elif [ "$STATUS" = "completed" ]; then
|
||||||
|
# Deploy and post completion comment
|
||||||
|
# Convert branch name to Cloudflare-compatible format (lowercase, only alphanumeric and dashes)
|
||||||
|
cloudflare_branch=$(echo "$BRANCH_NAME" | tr '[:upper:]' '[:lower:]' | \
|
||||||
|
sed 's/[^a-z0-9-]/-/g' | sed 's/--*/-/g' | sed 's/^-\|-$//g')
|
||||||
|
|
||||||
|
echo "Looking for Storybook build in: $(pwd)/storybook-static"
|
||||||
|
|
||||||
|
# Deploy Storybook if build exists
|
||||||
|
deployment_url="Not deployed"
|
||||||
|
if [ -d "storybook-static" ]; then
|
||||||
|
echo "Found Storybook build, deploying..."
|
||||||
|
url=$(deploy_storybook "storybook-static" "$cloudflare_branch")
|
||||||
|
if [ "$url" != "failed" ] && [ -n "$url" ]; then
|
||||||
|
deployment_url="[View Storybook]($url)"
|
||||||
|
else
|
||||||
|
deployment_url="Deployment failed"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Storybook build not found at storybook-static"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get workflow conclusion from environment or default to success
|
||||||
|
WORKFLOW_CONCLUSION="${WORKFLOW_CONCLUSION:-success}"
|
||||||
|
WORKFLOW_URL="${WORKFLOW_URL:-}"
|
||||||
|
|
||||||
|
# Generate completion comment based on conclusion
|
||||||
|
if [ "$WORKFLOW_CONCLUSION" = "success" ]; then
|
||||||
|
status_icon="✅"
|
||||||
|
status_text="Build completed successfully!"
|
||||||
|
footer_text="🎉 Your Storybook is ready for review!"
|
||||||
|
elif [ "$WORKFLOW_CONCLUSION" = "skipped" ]; then
|
||||||
|
status_icon="⏭️"
|
||||||
|
status_text="Build skipped."
|
||||||
|
footer_text="ℹ️ Chromatic was skipped for this PR."
|
||||||
|
elif [ "$WORKFLOW_CONCLUSION" = "cancelled" ]; then
|
||||||
|
status_icon="🚫"
|
||||||
|
status_text="Build cancelled."
|
||||||
|
footer_text="ℹ️ The Chromatic run was cancelled."
|
||||||
|
else
|
||||||
|
status_icon="❌"
|
||||||
|
status_text="Build failed!"
|
||||||
|
footer_text="⚠️ Please check the workflow logs for error details."
|
||||||
|
fi
|
||||||
|
|
||||||
|
comment="$COMMENT_MARKER
|
||||||
|
## 🎨 Storybook Build Status
|
||||||
|
|
||||||
|
$status_icon **$status_text**
|
||||||
|
|
||||||
|
⏰ Completed at: $(date -u '+%m/%d/%Y, %I:%M:%S %p') UTC
|
||||||
|
|
||||||
|
### 🔗 Links
|
||||||
|
- [📊 View Workflow Run]($WORKFLOW_URL)"
|
||||||
|
|
||||||
|
# Add deployment status
|
||||||
|
if [ "$deployment_url" != "Not deployed" ]; then
|
||||||
|
if [ "$deployment_url" = "Deployment failed" ]; then
|
||||||
|
comment="$comment
|
||||||
|
- ❌ Storybook deployment failed"
|
||||||
|
elif [ "$WORKFLOW_CONCLUSION" = "success" ]; then
|
||||||
|
comment="$comment
|
||||||
|
- 🎨 $deployment_url"
|
||||||
|
else
|
||||||
|
comment="$comment
|
||||||
|
- ⚠️ Build failed - $deployment_url"
|
||||||
|
fi
|
||||||
|
elif [ "$WORKFLOW_CONCLUSION" != "success" ]; then
|
||||||
|
comment="$comment
|
||||||
|
- ⏭️ Storybook deployment skipped (build did not succeed)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
comment="$comment
|
||||||
|
|
||||||
|
---
|
||||||
|
$footer_text"
|
||||||
|
|
||||||
|
post_comment "$comment"
|
||||||
|
fi
|
||||||
@@ -104,7 +104,7 @@ export function useWorkflowPersistence() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const paths = openWorkflows.value
|
const paths = openWorkflows.value
|
||||||
.filter((workflow) => workflow?.isPersisted && !workflow.isModified)
|
.filter((workflow) => workflow?.isPersisted)
|
||||||
.map((workflow) => workflow.path)
|
.map((workflow) => workflow.path)
|
||||||
const activeIndex = openWorkflows.value.findIndex(
|
const activeIndex = openWorkflows.value.findIndex(
|
||||||
(workflow) => workflow.path === activeWorkflow.value?.path
|
(workflow) => workflow.path === activeWorkflow.value?.path
|
||||||
|
|||||||
@@ -30,6 +30,15 @@ export function useCanvasInteractions() {
|
|||||||
* when appropriate (e.g., Ctrl+wheel for zoom in standard mode)
|
* when appropriate (e.g., Ctrl+wheel for zoom in standard mode)
|
||||||
*/
|
*/
|
||||||
const handleWheel = (event: WheelEvent) => {
|
const handleWheel = (event: WheelEvent) => {
|
||||||
|
// Check if the wheel event is from an element that wants to capture wheel events
|
||||||
|
const target = event.target as HTMLElement
|
||||||
|
const captureElement = target?.closest('[data-capture-wheel="true"]')
|
||||||
|
|
||||||
|
if (captureElement) {
|
||||||
|
// Element wants to capture wheel events, don't forward to canvas
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// In standard mode, Ctrl+wheel should go to canvas for zoom
|
// In standard mode, Ctrl+wheel should go to canvas for zoom
|
||||||
if (isStandardNavMode.value && (event.ctrlKey || event.metaKey)) {
|
if (isStandardNavMode.value && (event.ctrlKey || event.metaKey)) {
|
||||||
forwardEventToCanvas(event)
|
forwardEventToCanvas(event)
|
||||||
@@ -72,6 +81,15 @@ export function useCanvasInteractions() {
|
|||||||
const forwardEventToCanvas = (
|
const forwardEventToCanvas = (
|
||||||
event: WheelEvent | PointerEvent | MouseEvent
|
event: WheelEvent | PointerEvent | MouseEvent
|
||||||
) => {
|
) => {
|
||||||
|
// Check if the wheel event is from an element that wants to capture wheel events
|
||||||
|
const target = event.target as HTMLElement
|
||||||
|
const captureElement = target?.closest('[data-capture-wheel="true"]')
|
||||||
|
|
||||||
|
if (captureElement) {
|
||||||
|
// Element wants to capture wheel events, don't forward to canvas
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const canvasEl = app.canvas?.canvas
|
const canvasEl = app.canvas?.canvas
|
||||||
if (!canvasEl) return
|
if (!canvasEl) return
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|||||||
@@ -211,7 +211,8 @@ const isSelected = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Use execution state composable
|
// Use execution state composable
|
||||||
const { executing, progress } = useNodeExecutionState(() => nodeData.id)
|
const nodeLocatorId = computed(() => getLocatorIdFromNodeData(nodeData))
|
||||||
|
const { executing, progress } = useNodeExecutionState(nodeLocatorId)
|
||||||
|
|
||||||
// Direct access to execution store for error state
|
// Direct access to execution store for error state
|
||||||
const executionStore = useExecutionStore()
|
const executionStore = useExecutionStore()
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="lg-node-header p-4 rounded-t-2xl w-full cursor-move"
|
class="lg-node-header p-4 rounded-t-2xl cursor-move"
|
||||||
:style="headerStyle"
|
:style="headerStyle"
|
||||||
:data-testid="`node-header-${nodeData?.id || ''}`"
|
:data-testid="`node-header-${nodeData?.id || ''}`"
|
||||||
@dblclick="handleDoubleClick"
|
@dblclick="handleDoubleClick"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between relative">
|
<div class="flex items-center justify-between gap-2.5 relative">
|
||||||
<!-- Collapse/Expand Button -->
|
<!-- Collapse/Expand Button -->
|
||||||
<button
|
<button
|
||||||
v-show="!readonly"
|
v-show="!readonly"
|
||||||
@@ -43,24 +43,22 @@
|
|||||||
data-testid="node-pin-indicator"
|
data-testid="node-pin-indicator"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="!readonly" class="flex items-center lod-toggle shrink-0">
|
||||||
|
<IconButton
|
||||||
|
v-if="isSubgraphNode"
|
||||||
|
size="sm"
|
||||||
|
type="transparent"
|
||||||
|
class="text-stone-200 dark-theme:text-slate-300"
|
||||||
|
data-testid="subgraph-enter-button"
|
||||||
|
title="Enter Subgraph"
|
||||||
|
@click.stop="handleEnterSubgraph"
|
||||||
|
@dblclick.stop
|
||||||
|
>
|
||||||
|
<i class="pi pi-external-link"></i>
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
<LODFallback />
|
<LODFallback />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Title Buttons -->
|
|
||||||
<div v-if="!readonly" class="flex items-center lod-toggle">
|
|
||||||
<IconButton
|
|
||||||
v-if="isSubgraphNode"
|
|
||||||
size="sm"
|
|
||||||
type="transparent"
|
|
||||||
class="text-stone-200 dark-theme:text-slate-300"
|
|
||||||
data-testid="subgraph-enter-button"
|
|
||||||
title="Enter Subgraph"
|
|
||||||
@click.stop="handleEnterSubgraph"
|
|
||||||
@dblclick.stop
|
|
||||||
>
|
|
||||||
<i class="pi pi-external-link"></i>
|
|
||||||
</IconButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,77 @@
|
|||||||
import type { TooltipDirectivePassThroughOptions } from 'primevue'
|
import type { TooltipDirectivePassThroughOptions } from 'primevue'
|
||||||
import { type MaybeRef, type Ref, computed, unref } from 'vue'
|
import { type MaybeRef, type Ref, computed, ref, unref } from 'vue'
|
||||||
|
|
||||||
import type { SafeWidgetData } from '@/composables/graph/useGraphNodeManager'
|
import type { SafeWidgetData } from '@/composables/graph/useGraphNodeManager'
|
||||||
import { st } from '@/i18n'
|
import { st } from '@/i18n'
|
||||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||||
import { useNodeDefStore } from '@/stores/nodeDefStore'
|
import { useNodeDefStore } from '@/stores/nodeDefStore'
|
||||||
import { normalizeI18nKey } from '@/utils/formatUtil'
|
import { normalizeI18nKey } from '@/utils/formatUtil'
|
||||||
|
import { cn } from '@/utils/tailwindUtil'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide all visible tooltips by dispatching mouseleave events
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* IMPORTANT: this escape is needed for many reason due to primevue's directive tooltip system.
|
||||||
|
* We cannot use PT to conditonally render the tooltips because the entire PT object only run
|
||||||
|
* once during the intialization of the directive not every mount/unmount.
|
||||||
|
* Once the directive is constructed its no longer reactive in the traditional sense.
|
||||||
|
* We have to use something non destructive like mouseevents to dismiss the tooltip.
|
||||||
|
*
|
||||||
|
* TODO: use a better tooltip component like RekaUI for vue nodes specifically.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const tooltipsTemporarilyDisabled = ref(false)
|
||||||
|
|
||||||
|
const hideTooltipsGlobally = () => {
|
||||||
|
// Get all visible tooltip elements
|
||||||
|
const tooltips = document.querySelectorAll('.p-tooltip')
|
||||||
|
|
||||||
|
// Early return if no tooltips are visible
|
||||||
|
if (tooltips.length === 0) return
|
||||||
|
|
||||||
|
tooltips.forEach((tooltipEl) => {
|
||||||
|
const tooltipId = tooltipEl.id
|
||||||
|
if (!tooltipId) return
|
||||||
|
|
||||||
|
// Find the target element that owns this tooltip
|
||||||
|
const targetElements = document.querySelectorAll('[data-pd-tooltip="true"]')
|
||||||
|
for (const targetEl of targetElements) {
|
||||||
|
if ((targetEl as any).$_ptooltipId === tooltipId) {
|
||||||
|
;(targetEl as HTMLElement).dispatchEvent(
|
||||||
|
new MouseEvent('mouseleave', { bubbles: true })
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Disable tooltips temporarily after hiding (for drag operations)
|
||||||
|
tooltipsTemporarilyDisabled.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-enable tooltips after pointer interaction ends
|
||||||
|
*/
|
||||||
|
const handlePointerUp = () => {
|
||||||
|
tooltipsTemporarilyDisabled.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global tooltip hiding system
|
||||||
|
const globalTooltipState = { listenersSetup: false }
|
||||||
|
|
||||||
|
function setupGlobalTooltipHiding() {
|
||||||
|
if (globalTooltipState.listenersSetup) return
|
||||||
|
|
||||||
|
document.addEventListener('pointerdown', hideTooltipsGlobally)
|
||||||
|
document.addEventListener('pointerup', handlePointerUp)
|
||||||
|
window.addEventListener('wheel', hideTooltipsGlobally, {
|
||||||
|
capture: true, //Need this to bypass the event layer from Litegraph
|
||||||
|
passive: true
|
||||||
|
})
|
||||||
|
|
||||||
|
globalTooltipState.listenersSetup = true
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Composable for managing Vue node tooltips
|
* Composable for managing Vue node tooltips
|
||||||
@@ -18,6 +84,9 @@ export function useNodeTooltips(
|
|||||||
const nodeDefStore = useNodeDefStore()
|
const nodeDefStore = useNodeDefStore()
|
||||||
const settingsStore = useSettingStore()
|
const settingsStore = useSettingStore()
|
||||||
|
|
||||||
|
// Setup global pointerdown listener once
|
||||||
|
setupGlobalTooltipHiding()
|
||||||
|
|
||||||
// Check if tooltips are globally enabled
|
// Check if tooltips are globally enabled
|
||||||
const tooltipsEnabled = computed(() =>
|
const tooltipsEnabled = computed(() =>
|
||||||
settingsStore.get('Comfy.EnableTooltips')
|
settingsStore.get('Comfy.EnableTooltips')
|
||||||
@@ -76,6 +145,7 @@ export function useNodeTooltips(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create tooltip configuration object for v-tooltip directive
|
* Create tooltip configuration object for v-tooltip directive
|
||||||
|
* Components wrap this in computed() for reactivity
|
||||||
*/
|
*/
|
||||||
const createTooltipConfig = (text: string) => {
|
const createTooltipConfig = (text: string) => {
|
||||||
const tooltipDelay = settingsStore.get('LiteGraph.Node.TooltipDelay')
|
const tooltipDelay = settingsStore.get('LiteGraph.Node.TooltipDelay')
|
||||||
@@ -84,21 +154,33 @@ export function useNodeTooltips(
|
|||||||
const config: {
|
const config: {
|
||||||
value: string
|
value: string
|
||||||
showDelay: number
|
showDelay: number
|
||||||
|
hideDelay: number
|
||||||
disabled: boolean
|
disabled: boolean
|
||||||
appendTo?: HTMLElement
|
appendTo?: HTMLElement
|
||||||
pt?: TooltipDirectivePassThroughOptions
|
pt?: TooltipDirectivePassThroughOptions
|
||||||
} = {
|
} = {
|
||||||
value: tooltipText,
|
value: tooltipText,
|
||||||
showDelay: tooltipDelay as number,
|
showDelay: tooltipDelay as number,
|
||||||
disabled: !tooltipsEnabled.value || !tooltipText,
|
hideDelay: 0, // Immediate hiding
|
||||||
|
disabled:
|
||||||
|
!tooltipsEnabled.value ||
|
||||||
|
!tooltipText ||
|
||||||
|
tooltipsTemporarilyDisabled.value, // this reactive value works but only on next mount,
|
||||||
|
// so if the tooltip is already visible changing this will not hide it
|
||||||
pt: {
|
pt: {
|
||||||
text: {
|
text: {
|
||||||
class:
|
class:
|
||||||
'bg-pure-white dark-theme:bg-charcoal-800 border dark-theme:border-slate-300 rounded-md px-4 py-2 text-charcoal-700 dark-theme:text-pure-white text-sm font-normal leading-tight max-w-75 shadow-none'
|
'border-sand-100 bg-pure-white dark-theme:bg-charcoal-800 border dark-theme:border-slate-300 rounded-md px-4 py-2 text-charcoal-700 dark-theme:text-pure-white text-sm font-normal leading-tight max-w-75 shadow-none'
|
||||||
},
|
},
|
||||||
arrow: {
|
arrow: ({ context }) => ({
|
||||||
class: 'before:border-slate-300'
|
class: cn(
|
||||||
}
|
context?.top && 'border-t-sand-100 dark-theme:border-t-slate-300',
|
||||||
|
context?.bottom &&
|
||||||
|
'border-b-sand-100 dark-theme:border-b-slate-300',
|
||||||
|
context?.left && 'border-l-sand-100 dark-theme:border-l-slate-300',
|
||||||
|
context?.right && 'border-r-sand-100 dark-theme:border-r-slate-300'
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,27 +9,27 @@ import { useExecutionStore } from '@/stores/executionStore'
|
|||||||
* Provides reactive access to execution state and progress for a specific node
|
* Provides reactive access to execution state and progress for a specific node
|
||||||
* by injecting execution data from the parent GraphCanvas provider.
|
* by injecting execution data from the parent GraphCanvas provider.
|
||||||
*
|
*
|
||||||
* @param nodeIdMaybe - The ID of the node to track execution state for
|
* @param nodeLocatorIdMaybe - Locator ID (root or subgraph scoped) of the node to track
|
||||||
* @returns Object containing reactive execution state and progress
|
* @returns Object containing reactive execution state and progress
|
||||||
*/
|
*/
|
||||||
export const useNodeExecutionState = (
|
export const useNodeExecutionState = (
|
||||||
nodeIdMaybe: MaybeRefOrGetter<string>
|
nodeLocatorIdMaybe: MaybeRefOrGetter<string | undefined>
|
||||||
) => {
|
) => {
|
||||||
const nodeId = toValue(nodeIdMaybe)
|
const locatorId = computed(() => toValue(nodeLocatorIdMaybe) ?? '')
|
||||||
const { uniqueExecutingNodeIdStrings, nodeProgressStates } =
|
const { nodeLocationProgressStates } = storeToRefs(useExecutionStore())
|
||||||
storeToRefs(useExecutionStore())
|
|
||||||
|
|
||||||
const executing = computed(() => {
|
const progressState = computed(() => {
|
||||||
return uniqueExecutingNodeIdStrings.value.has(nodeId)
|
const id = locatorId.value
|
||||||
|
return id ? nodeLocationProgressStates.value[id] : undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const executing = computed(() => progressState.value?.state === 'running')
|
||||||
|
|
||||||
const progress = computed(() => {
|
const progress = computed(() => {
|
||||||
const state = nodeProgressStates.value[nodeId]
|
const state = progressState.value
|
||||||
return state?.max > 0 ? state.value / state.max : undefined
|
return state && state.max > 0 ? state.value / state.max : undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
const progressState = computed(() => nodeProgressStates.value[nodeId])
|
|
||||||
|
|
||||||
const progressPercentage = computed(() => {
|
const progressPercentage = computed(() => {
|
||||||
const prog = progress.value
|
const prog = progress.value
|
||||||
return prog !== undefined ? Math.round(prog * 100) : undefined
|
return prog !== undefined ? Math.round(prog * 100) : undefined
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
onBlur: handleBlur
|
onBlur: handleBlur
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
|
data-capture-wheel="true"
|
||||||
@update:model-value="onChange"
|
@update:model-value="onChange"
|
||||||
@click.stop
|
@click.stop
|
||||||
@keydown.stop
|
@keydown.stop
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
:pt="{
|
:pt="{
|
||||||
option: 'text-xs'
|
option: 'text-xs'
|
||||||
}"
|
}"
|
||||||
|
data-capture-wheel="true"
|
||||||
@update:model-value="onChange"
|
@update:model-value="onChange"
|
||||||
/>
|
/>
|
||||||
</WidgetLayoutField>
|
</WidgetLayoutField>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
:placeholder="placeholder || widget.name || ''"
|
:placeholder="placeholder || widget.name || ''"
|
||||||
size="small"
|
size="small"
|
||||||
rows="3"
|
rows="3"
|
||||||
|
data-capture-wheel="true"
|
||||||
@update:model-value="onChange"
|
@update:model-value="onChange"
|
||||||
/>
|
/>
|
||||||
<LODFallback />
|
<LODFallback />
|
||||||
|
|||||||
6
src/types/vue-shim.d.ts
vendored
@@ -1,6 +0,0 @@
|
|||||||
// vue-shim.d.ts
|
|
||||||
declare module '*.vue' {
|
|
||||||
import { DefineComponent } from 'vue'
|
|
||||||
const component: DefineComponent<{}, {}, any>
|
|
||||||
export default component
|
|
||||||
}
|
|
||||||