Merge branch 'main' into sno-license-check
@@ -1,5 +1,5 @@
|
|||||||
|
# Description: When upstream electron API is updated, click dispatch to update the TypeScript type definitions in this repo
|
||||||
name: 'Api: Update Electron API Types'
|
name: 'Api: Update Electron API Types'
|
||||||
description: 'When upstream electron API is updated, click dispatch to update the TypeScript type definitions in this repo'
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
# Description: When upstream ComfyUI-Manager API is updated, click dispatch to update the TypeScript type definitions in this repo
|
||||||
name: 'Api: Update Manager API Types'
|
name: 'Api: Update Manager API Types'
|
||||||
description: 'When upstream ComfyUI-Manager API is updated, click dispatch to update the TypeScript type definitions in this repo'
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
# Manual trigger
|
# Manual trigger
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
# Description: When upstream comfy-api is updated, click dispatch to update the TypeScript type definitions in this repo
|
||||||
name: 'Api: Update Registry API Types'
|
name: 'Api: Update Registry API Types'
|
||||||
description: 'When upstream comfy-api is updated, click dispatch to update the TypeScript type definitions in this repo'
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
# Manual trigger
|
# Manual trigger
|
||||||
|
|||||||
2
.github/workflows/ci-json-validation.yaml
vendored
@@ -1,5 +1,5 @@
|
|||||||
|
# Description: Validates JSON syntax in all tracked .json files (excluding tsconfig*.json) using jq
|
||||||
name: "CI: JSON Validation"
|
name: "CI: JSON Validation"
|
||||||
description: "Validates JSON syntax in all tracked .json files (excluding tsconfig*.json) using jq"
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|||||||
2
.github/workflows/ci-lint-format.yaml
vendored
@@ -1,5 +1,5 @@
|
|||||||
|
# Description: Linting and code formatting validation for pull requests
|
||||||
name: "CI: Lint Format"
|
name: "CI: Lint Format"
|
||||||
description: "Linting and code formatting validation for pull requests"
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|||||||
2
.github/workflows/ci-python-validation.yaml
vendored
@@ -1,5 +1,5 @@
|
|||||||
|
# Description: Validates Python code in tools/devtools directory
|
||||||
name: "CI: Python Validation"
|
name: "CI: Python Validation"
|
||||||
description: "Validates Python code in tools/devtools directory"
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|||||||
2
.github/workflows/ci-tests-e2e-forks.yaml
vendored
@@ -1,5 +1,5 @@
|
|||||||
|
# Description: Deploys test results from forked PRs (forks can't access deployment secrets)
|
||||||
name: "CI: Tests E2E (Deploy for Forks)"
|
name: "CI: Tests E2E (Deploy for Forks)"
|
||||||
description: "Deploys test results from forked PRs (forks can't access deployment secrets)"
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_run:
|
workflow_run:
|
||||||
|
|||||||
2
.github/workflows/ci-tests-e2e.yaml
vendored
@@ -1,5 +1,5 @@
|
|||||||
|
# Description: End-to-end testing with Playwright across multiple browsers, deploys test reports to Cloudflare Pages
|
||||||
name: "CI: Tests E2E"
|
name: "CI: Tests E2E"
|
||||||
description: "End-to-end testing with Playwright across multiple browsers, deploys test reports to Cloudflare Pages"
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
# Description: Deploys Storybook previews from forked PRs (forks can't access deployment secrets)
|
||||||
name: "CI: Tests Storybook (Deploy for Forks)"
|
name: "CI: Tests Storybook (Deploy for Forks)"
|
||||||
description: "Deploys Storybook previews from forked PRs (forks can't access deployment secrets)"
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_run:
|
workflow_run:
|
||||||
|
|||||||
3
.github/workflows/ci-tests-storybook.yaml
vendored
@@ -1,10 +1,9 @@
|
|||||||
|
# Description: Builds Storybook and runs visual regression testing via Chromatic, deploys previews to Cloudflare Pages
|
||||||
name: "CI: Tests Storybook"
|
name: "CI: Tests Storybook"
|
||||||
description: "Builds Storybook and runs visual regression testing via Chromatic, deploys previews to Cloudflare Pages"
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch: # Allow manual triggering
|
workflow_dispatch: # Allow manual triggering
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# Post starting comment for non-forked PRs
|
# Post starting comment for non-forked PRs
|
||||||
|
|||||||
2
.github/workflows/ci-tests-unit.yaml
vendored
@@ -1,5 +1,5 @@
|
|||||||
|
# Description: Unit and component testing with Vitest
|
||||||
name: "CI: Tests Unit"
|
name: "CI: Tests Unit"
|
||||||
description: "Unit and component testing with Vitest"
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|||||||
2
.github/workflows/ci-yaml-validation.yaml
vendored
@@ -1,5 +1,5 @@
|
|||||||
|
# Description: Validates YAML syntax and style using yamllint with relaxed rules
|
||||||
name: "CI: YAML Validation"
|
name: "CI: YAML Validation"
|
||||||
description: "Validates YAML syntax and style using yamllint with relaxed rules"
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|||||||
69
.github/workflows/cloud-backport-tag.yaml
vendored
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
---
|
||||||
|
name: Cloud Backport Tag
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: ['closed']
|
||||||
|
branches: [cloud/*]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
create-tag:
|
||||||
|
if: >
|
||||||
|
github.event.pull_request.merged == true &&
|
||||||
|
contains(github.event.pull_request.labels.*.name, 'backport')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: read
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout merge commit
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v5
|
||||||
|
with:
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
|
|
||||||
|
- name: Create tag for cloud backport
|
||||||
|
id: tag
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
BRANCH="${{ github.event.pull_request.base.ref }}"
|
||||||
|
if [[ ! "$BRANCH" =~ ^cloud/([0-9]+)\.([0-9]+)$ ]]; then
|
||||||
|
echo "::error::Base branch '$BRANCH' is not a cloud/x.y branch"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
MAJOR="${BASH_REMATCH[1]}"
|
||||||
|
MINOR="${BASH_REMATCH[2]}"
|
||||||
|
|
||||||
|
VERSION=$(node -p "require('./package.json').version")
|
||||||
|
if [[ "$VERSION" =~ ^${MAJOR}\.${MINOR}\.([0-9]+)(-.+)?$ ]]; then
|
||||||
|
PATCH="${BASH_REMATCH[1]}"
|
||||||
|
SUFFIX="${BASH_REMATCH[2]:-}"
|
||||||
|
else
|
||||||
|
echo "::error::Version '${VERSION}' does not match cloud branch '${BRANCH}'"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
TAG="cloud/v${VERSION}"
|
||||||
|
|
||||||
|
if git ls-remote --tags origin "${TAG}" | grep -Fq "refs/tags/${TAG}"; then
|
||||||
|
echo "::notice::Tag ${TAG} already exists; skipping"
|
||||||
|
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
git tag "${TAG}" "${{ github.event.pull_request.merge_commit_sha }}"
|
||||||
|
git push origin "${TAG}"
|
||||||
|
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
||||||
|
{
|
||||||
|
echo "Created tag: ${TAG}"
|
||||||
|
echo "Branch: ${BRANCH}"
|
||||||
|
echo "Version: ${VERSION}"
|
||||||
|
echo "Commit: ${{ github.event.pull_request.merge_commit_sha }}"
|
||||||
|
} >> "$GITHUB_STEP_SUMMARY"
|
||||||
2
.github/workflows/i18n-update-core.yaml
vendored
@@ -1,5 +1,5 @@
|
|||||||
|
# Description: Generates and updates translations for core ComfyUI components using OpenAI
|
||||||
name: "i18n: Update Core"
|
name: "i18n: Update Core"
|
||||||
description: "Generates and updates translations for core ComfyUI components using OpenAI"
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
# Manual dispatch for urgent translation updates
|
# Manual dispatch for urgent translation updates
|
||||||
|
|||||||
17
.github/workflows/pr-backport.yaml
vendored
@@ -78,8 +78,7 @@ jobs:
|
|||||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||||
LABELS=$(gh pr view ${{ inputs.pr_number }} --json labels | jq -r '.labels[].name')
|
LABELS=$(gh pr view ${{ inputs.pr_number }} --json labels | jq -r '.labels[].name')
|
||||||
else
|
else
|
||||||
LABELS='${{ toJSON(github.event.pull_request.labels) }}'
|
LABELS=$(jq -r '.pull_request.labels[].name' "$GITHUB_EVENT_PATH")
|
||||||
LABELS=$(echo "$LABELS" | jq -r '.[].name')
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
add_target() {
|
add_target() {
|
||||||
@@ -237,8 +236,8 @@ jobs:
|
|||||||
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title')
|
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title')
|
||||||
MERGE_COMMIT=$(echo "$PR_DATA" | jq -r '.mergeCommit.oid')
|
MERGE_COMMIT=$(echo "$PR_DATA" | jq -r '.mergeCommit.oid')
|
||||||
else
|
else
|
||||||
PR_TITLE="${{ github.event.pull_request.title }}"
|
PR_TITLE=$(jq -r '.pull_request.title' "$GITHUB_EVENT_PATH")
|
||||||
MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}"
|
MERGE_COMMIT=$(jq -r '.pull_request.merge_commit_sha' "$GITHUB_EVENT_PATH")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for target in ${{ steps.filter-targets.outputs.pending-targets }}; do
|
for target in ${{ steps.filter-targets.outputs.pending-targets }}; do
|
||||||
@@ -327,8 +326,8 @@ jobs:
|
|||||||
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title')
|
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title')
|
||||||
PR_AUTHOR=$(echo "$PR_DATA" | jq -r '.author.login')
|
PR_AUTHOR=$(echo "$PR_DATA" | jq -r '.author.login')
|
||||||
else
|
else
|
||||||
PR_TITLE="${{ github.event.pull_request.title }}"
|
PR_TITLE=$(jq -r '.pull_request.title' "$GITHUB_EVENT_PATH")
|
||||||
PR_AUTHOR="${{ github.event.pull_request.user.login }}"
|
PR_AUTHOR=$(jq -r '.pull_request.user.login' "$GITHUB_EVENT_PATH")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for backport in ${{ steps.backport.outputs.success }}; do
|
for backport in ${{ steps.backport.outputs.success }}; do
|
||||||
@@ -365,9 +364,9 @@ jobs:
|
|||||||
PR_AUTHOR=$(echo "$PR_DATA" | jq -r '.author.login')
|
PR_AUTHOR=$(echo "$PR_DATA" | jq -r '.author.login')
|
||||||
MERGE_COMMIT=$(echo "$PR_DATA" | jq -r '.mergeCommit.oid')
|
MERGE_COMMIT=$(echo "$PR_DATA" | jq -r '.mergeCommit.oid')
|
||||||
else
|
else
|
||||||
PR_NUMBER="${{ github.event.pull_request.number }}"
|
PR_NUMBER=$(jq -r '.pull_request.number' "$GITHUB_EVENT_PATH")
|
||||||
PR_AUTHOR="${{ github.event.pull_request.user.login }}"
|
PR_AUTHOR=$(jq -r '.pull_request.user.login' "$GITHUB_EVENT_PATH")
|
||||||
MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}"
|
MERGE_COMMIT=$(jq -r '.pull_request.merge_commit_sha' "$GITHUB_EVENT_PATH")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for failure in ${{ steps.backport.outputs.failed }}; do
|
for failure in ${{ steps.backport.outputs.failed }}; do
|
||||||
|
|||||||
2
.github/workflows/pr-claude-review.yaml
vendored
@@ -1,5 +1,5 @@
|
|||||||
|
# Description: AI-powered code review triggered by adding the 'claude-review' label to a PR
|
||||||
name: "PR: Claude Review"
|
name: "PR: Claude Review"
|
||||||
description: "AI-powered code review triggered by adding the 'claude-review' label to a PR"
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|||||||
4
.github/workflows/release-branch-create.yaml
vendored
@@ -148,10 +148,10 @@ jobs:
|
|||||||
done
|
done
|
||||||
|
|
||||||
{
|
{
|
||||||
echo "results<<'EOF'"
|
echo "results<<EOF"
|
||||||
cat "$RESULTS_FILE"
|
cat "$RESULTS_FILE"
|
||||||
echo "EOF"
|
echo "EOF"
|
||||||
} >> $GITHUB_OUTPUT
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Ensure release labels
|
- name: Ensure release labels
|
||||||
if: steps.check_version.outputs.is_minor_bump == 'true'
|
if: steps.check_version.outputs.is_minor_bump == 'true'
|
||||||
|
|||||||
2
.github/workflows/release-version-bump.yaml
vendored
@@ -1,5 +1,5 @@
|
|||||||
|
# Description: Manual workflow to increment package version with semantic versioning support
|
||||||
name: "Release: Version Bump"
|
name: "Release: Version Bump"
|
||||||
description: "Manual workflow to increment package version with semantic versioning support"
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|||||||
292
.github/workflows/release-weekly-comfyui.yaml
vendored
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
# Automated weekly workflow to bump ComfyUI frontend RC releases
|
||||||
|
name: "Release: Weekly ComfyUI"
|
||||||
|
|
||||||
|
on:
|
||||||
|
# Schedule for Monday at 12:00 PM PST (20:00 UTC)
|
||||||
|
schedule:
|
||||||
|
- cron: '0 20 * * 1'
|
||||||
|
|
||||||
|
# Allow manual triggering
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
comfyui_fork:
|
||||||
|
description: 'ComfyUI fork to use for PR (e.g., Comfy-Org/ComfyUI)'
|
||||||
|
required: false
|
||||||
|
default: 'Comfy-Org/ComfyUI'
|
||||||
|
type: string
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
resolve-version:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
current_version: ${{ steps.resolve.outputs.current_version }}
|
||||||
|
target_version: ${{ steps.resolve.outputs.target_version }}
|
||||||
|
target_minor: ${{ steps.resolve.outputs.target_minor }}
|
||||||
|
target_branch: ${{ steps.resolve.outputs.target_branch }}
|
||||||
|
needs_release: ${{ steps.resolve.outputs.needs_release }}
|
||||||
|
diff_url: ${{ steps.resolve.outputs.diff_url }}
|
||||||
|
latest_patch_tag: ${{ steps.resolve.outputs.latest_patch_tag }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout ComfyUI_frontend
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
path: frontend
|
||||||
|
|
||||||
|
- name: Checkout ComfyUI (sparse)
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
repository: comfyanonymous/ComfyUI
|
||||||
|
sparse-checkout: |
|
||||||
|
requirements.txt
|
||||||
|
path: comfyui
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 10
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: lts/*
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
working-directory: frontend
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Resolve release information
|
||||||
|
id: resolve
|
||||||
|
working-directory: frontend
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Run the resolver script
|
||||||
|
if ! RESULT=$(pnpm exec tsx scripts/cicd/resolve-comfyui-release.ts ../comfyui .); then
|
||||||
|
echo "Failed to resolve release information"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Resolver output:"
|
||||||
|
echo "$RESULT"
|
||||||
|
|
||||||
|
# Validate JSON output
|
||||||
|
if ! echo "$RESULT" | jq empty 2>/dev/null; then
|
||||||
|
echo "Invalid JSON output from resolver"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Parse JSON output and set outputs
|
||||||
|
echo "current_version=$(echo "$RESULT" | jq -r '.current_version')" >> $GITHUB_OUTPUT
|
||||||
|
echo "target_version=$(echo "$RESULT" | jq -r '.target_version')" >> $GITHUB_OUTPUT
|
||||||
|
echo "target_minor=$(echo "$RESULT" | jq -r '.target_minor')" >> $GITHUB_OUTPUT
|
||||||
|
echo "target_branch=$(echo "$RESULT" | jq -r '.target_branch')" >> $GITHUB_OUTPUT
|
||||||
|
echo "needs_release=$(echo "$RESULT" | jq -r '.needs_release')" >> $GITHUB_OUTPUT
|
||||||
|
echo "diff_url=$(echo "$RESULT" | jq -r '.diff_url')" >> $GITHUB_OUTPUT
|
||||||
|
echo "latest_patch_tag=$(echo "$RESULT" | jq -r '.latest_patch_tag')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Summary
|
||||||
|
run: |
|
||||||
|
echo "## Release Information" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- Current version: ${{ steps.resolve.outputs.current_version }}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- Target version: ${{ steps.resolve.outputs.target_version }}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- Target branch: ${{ steps.resolve.outputs.target_branch }}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- Needs release: ${{ steps.resolve.outputs.needs_release }}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- Diff: [${{ steps.resolve.outputs.current_version }}...${{ steps.resolve.outputs.target_version }}](${{ steps.resolve.outputs.diff_url }})" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
trigger-release-if-needed:
|
||||||
|
needs: resolve-version
|
||||||
|
if: needs.resolve-version.outputs.needs_release == 'true'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Trigger release workflow
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.PR_GH_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "Triggering release workflow for branch ${{ needs.resolve-version.outputs.target_branch }}"
|
||||||
|
|
||||||
|
# Trigger the release-version-bump workflow
|
||||||
|
if ! gh workflow run release-version-bump.yaml \
|
||||||
|
--repo Comfy-Org/ComfyUI_frontend \
|
||||||
|
--ref main \
|
||||||
|
--field version_type=patch \
|
||||||
|
--field branch=${{ needs.resolve-version.outputs.target_branch }}; then
|
||||||
|
echo "Failed to trigger release workflow"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Release workflow triggered successfully for ${{ needs.resolve-version.outputs.target_branch }}"
|
||||||
|
|
||||||
|
- name: Summary
|
||||||
|
run: |
|
||||||
|
echo "## Release Workflow Triggered" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- Branch: ${{ needs.resolve-version.outputs.target_branch }}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- Target version: ${{ needs.resolve-version.outputs.target_version }}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- [View workflow runs](https://github.com/Comfy-Org/ComfyUI_frontend/actions/workflows/release-version-bump.yaml)" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
create-comfyui-pr:
|
||||||
|
needs: [resolve-version, trigger-release-if-needed]
|
||||||
|
if: always() && needs.resolve-version.result == 'success'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout ComfyUI fork
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
repository: ${{ inputs.comfyui_fork || 'Comfy-Org/ComfyUI' }}
|
||||||
|
token: ${{ secrets.PR_GH_TOKEN }}
|
||||||
|
path: comfyui
|
||||||
|
|
||||||
|
- name: Sync with upstream
|
||||||
|
working-directory: comfyui
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Fetch latest upstream to base our branch on fresh code
|
||||||
|
# Note: This only affects the local checkout, NOT the fork's master branch
|
||||||
|
# We only push the automation branch, leaving the fork's master untouched
|
||||||
|
echo "Fetching upstream master..."
|
||||||
|
if ! git fetch https://github.com/comfyanonymous/ComfyUI.git master; then
|
||||||
|
echo "Failed to fetch upstream master"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Checking out upstream master..."
|
||||||
|
if ! git checkout FETCH_HEAD; then
|
||||||
|
echo "Failed to checkout FETCH_HEAD"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Successfully synced with upstream master"
|
||||||
|
|
||||||
|
- name: Update requirements.txt
|
||||||
|
working-directory: comfyui
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
TARGET_VERSION="${{ needs.resolve-version.outputs.target_version }}"
|
||||||
|
echo "Updating comfyui-frontend-package to ${TARGET_VERSION}"
|
||||||
|
|
||||||
|
# Update the comfyui-frontend-package version (POSIX-compatible)
|
||||||
|
sed -i.bak "s/comfyui-frontend-package==[0-9.][0-9.]*/comfyui-frontend-package==${TARGET_VERSION}/" requirements.txt
|
||||||
|
rm requirements.txt.bak
|
||||||
|
|
||||||
|
# Verify the change was made
|
||||||
|
if ! grep -q "comfyui-frontend-package==${TARGET_VERSION}" requirements.txt; then
|
||||||
|
echo "Failed to update requirements.txt"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Updated requirements.txt:"
|
||||||
|
grep comfyui-frontend-package requirements.txt
|
||||||
|
|
||||||
|
- name: Build PR description
|
||||||
|
id: pr-body
|
||||||
|
run: |
|
||||||
|
BODY=$(cat <<'EOF'
|
||||||
|
Bumps frontend to ${{ needs.resolve-version.outputs.target_version }}
|
||||||
|
|
||||||
|
Test quickly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python main.py --front-end-version Comfy-Org/ComfyUI_frontend@${{ needs.resolve-version.outputs.target_version }}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Diff: [v${{ needs.resolve-version.outputs.current_version }}...v${{ needs.resolve-version.outputs.target_version }}](${{ needs.resolve-version.outputs.diff_url }})
|
||||||
|
- PyPI: https://pypi.org/project/comfyui-frontend-package/${{ needs.resolve-version.outputs.target_version }}/
|
||||||
|
- npm: https://www.npmjs.com/package/@comfyorg/comfyui-frontend-types/v/${{ needs.resolve-version.outputs.target_version }}
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add release PR note if release was triggered
|
||||||
|
if [ "${{ needs.resolve-version.outputs.needs_release }}" = "true" ]; then
|
||||||
|
RELEASE_NOTE="⚠️ **Release PR must be merged first** - check [release workflow runs](https://github.com/Comfy-Org/ComfyUI_frontend/actions/workflows/release-version-bump.yaml)"
|
||||||
|
BODY=$''"${RELEASE_NOTE}"$'\n\n'"${BODY}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Save to file for later use
|
||||||
|
printf '%s\n' "$BODY" > pr-body.txt
|
||||||
|
cat pr-body.txt
|
||||||
|
|
||||||
|
- name: Create PR to ComfyUI
|
||||||
|
working-directory: comfyui
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.PR_GH_TOKEN }}
|
||||||
|
COMFYUI_FORK: ${{ inputs.comfyui_fork || 'Comfy-Org/ComfyUI' }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Extract fork owner from repository name
|
||||||
|
FORK_OWNER=$(echo "$COMFYUI_FORK" | cut -d'/' -f1)
|
||||||
|
|
||||||
|
echo "Creating PR from ${COMFYUI_FORK} to comfyanonymous/ComfyUI"
|
||||||
|
|
||||||
|
# Configure git
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
|
||||||
|
# Create/update branch (reuse same branch name each week)
|
||||||
|
BRANCH="automation/comfyui-frontend-bump"
|
||||||
|
git checkout -B "$BRANCH"
|
||||||
|
git add requirements.txt
|
||||||
|
|
||||||
|
if ! git diff --cached --quiet; then
|
||||||
|
git commit -m "Bump comfyui-frontend-package to ${{ needs.resolve-version.outputs.target_version }}"
|
||||||
|
else
|
||||||
|
echo "No changes to commit"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Force push to fork (overwrites previous week's branch)
|
||||||
|
# Note: This intentionally destroys branch history to maintain a single PR
|
||||||
|
# Any review comments or manual commits will need to be re-applied
|
||||||
|
if ! git push -f origin "$BRANCH"; then
|
||||||
|
echo "Failed to push branch to fork"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create draft PR from fork to upstream
|
||||||
|
PR_BODY=$(cat ../pr-body.txt)
|
||||||
|
|
||||||
|
# Try to create PR, ignore error if it already exists
|
||||||
|
if ! gh pr create \
|
||||||
|
--repo comfyanonymous/ComfyUI \
|
||||||
|
--head "${FORK_OWNER}:${BRANCH}" \
|
||||||
|
--base master \
|
||||||
|
--title "Bump comfyui-frontend-package to ${{ needs.resolve-version.outputs.target_version }}" \
|
||||||
|
--body "$PR_BODY" \
|
||||||
|
--draft 2>&1; then
|
||||||
|
|
||||||
|
# Check if PR already exists
|
||||||
|
set +e
|
||||||
|
EXISTING_PR=$(gh pr list --repo comfyanonymous/ComfyUI --head "${FORK_OWNER}:${BRANCH}" --json number --jq '.[0].number' 2>&1)
|
||||||
|
PR_LIST_EXIT=$?
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ $PR_LIST_EXIT -ne 0 ]; then
|
||||||
|
echo "Failed to check for existing PR: $EXISTING_PR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$EXISTING_PR" ] && [ "$EXISTING_PR" != "null" ]; then
|
||||||
|
echo "PR already exists (#${EXISTING_PR}), updating branch will update the PR"
|
||||||
|
else
|
||||||
|
echo "Failed to create PR and no existing PR found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Summary
|
||||||
|
run: |
|
||||||
|
echo "## ComfyUI PR Created" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "Draft PR created in comfyanonymous/ComfyUI" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "### PR Body:" >> $GITHUB_STEP_SUMMARY
|
||||||
|
cat pr-body.txt >> $GITHUB_STEP_SUMMARY
|
||||||
2
.github/workflows/weekly-docs-check.yaml
vendored
@@ -1,5 +1,5 @@
|
|||||||
|
# Description: Automated weekly documentation accuracy check and update via Claude
|
||||||
name: "Weekly Documentation Check"
|
name: "Weekly Documentation Check"
|
||||||
description: "Automated weekly documentation accuracy check and update via Claude"
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ module.exports = defineConfig({
|
|||||||
entry: 'src/locales/en',
|
entry: 'src/locales/en',
|
||||||
entryLocale: 'en',
|
entryLocale: 'en',
|
||||||
output: 'src/locales',
|
output: 'src/locales',
|
||||||
outputLocales: ['zh', 'zh-TW', 'ru', 'ja', 'ko', 'fr', 'es', 'ar', 'tr'],
|
outputLocales: ['zh', 'zh-TW', 'ru', 'ja', 'ko', 'fr', 'es', 'ar', 'tr', 'pt-BR'],
|
||||||
reference: `Special names to keep untranslated: flux, photomaker, clip, vae, cfg, stable audio, stable cascade, stable zero, controlnet, lora, HiDream.
|
reference: `Special names to keep untranslated: flux, photomaker, clip, vae, cfg, stable audio, stable cascade, stable zero, controlnet, lora, HiDream.
|
||||||
'latent' is the short form of 'latent space'.
|
'latent' is the short form of 'latent space'.
|
||||||
'mask' is in the context of image processing.
|
'mask' is in the context of image processing.
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
"typescript/no-redundant-type-constituents": "off",
|
"typescript/no-redundant-type-constituents": "off",
|
||||||
"typescript/restrict-template-expressions": "off",
|
"typescript/restrict-template-expressions": "off",
|
||||||
"typescript/unbound-method": "off",
|
"typescript/unbound-method": "off",
|
||||||
"typescript/no-floating-promises": "error"
|
"typescript/no-floating-promises": "error",
|
||||||
|
"vue/no-import-compiler-macros": "error"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,7 +64,6 @@ const config: StorybookConfig = {
|
|||||||
deep: true,
|
deep: true,
|
||||||
extensions: ['vue']
|
extensions: ['vue']
|
||||||
})
|
})
|
||||||
// Note: Explicitly NOT including generateImportMapPlugin to avoid externalization
|
|
||||||
],
|
],
|
||||||
server: {
|
server: {
|
||||||
allowedHosts: true
|
allowedHosts: true
|
||||||
|
|||||||
21
CODEOWNERS
@@ -1,8 +1,11 @@
|
|||||||
|
# Global Ownership
|
||||||
|
* @Comfy-org/comfy_frontend_devs
|
||||||
|
|
||||||
# Desktop/Electron
|
# Desktop/Electron
|
||||||
/apps/desktop-ui/ @webfiltered
|
/apps/desktop-ui/ @benceruleanlu
|
||||||
/src/stores/electronDownloadStore.ts @webfiltered
|
/src/stores/electronDownloadStore.ts @benceruleanlu
|
||||||
/src/extensions/core/electronAdapter.ts @webfiltered
|
/src/extensions/core/electronAdapter.ts @benceruleanlu
|
||||||
/vite.electron.config.mts @webfiltered
|
/vite.electron.config.mts @benceruleanlu
|
||||||
|
|
||||||
# Common UI Components
|
# Common UI Components
|
||||||
/src/components/chip/ @viva-jinyi
|
/src/components/chip/ @viva-jinyi
|
||||||
@@ -31,10 +34,7 @@
|
|||||||
/src/components/graph/selectionToolbox/ @Myestery
|
/src/components/graph/selectionToolbox/ @Myestery
|
||||||
|
|
||||||
# Minimap
|
# Minimap
|
||||||
/src/renderer/extensions/minimap/ @jtydhr88
|
/src/renderer/extensions/minimap/ @jtydhr88 @Myestery
|
||||||
|
|
||||||
# Assets
|
|
||||||
/src/platform/assets/ @arjansingh
|
|
||||||
|
|
||||||
# Workflow Templates
|
# Workflow Templates
|
||||||
/src/platform/workflow/templates/ @Myestery @christian-byrne @comfyui-wiki
|
/src/platform/workflow/templates/ @Myestery @christian-byrne @comfyui-wiki
|
||||||
@@ -53,11 +53,12 @@
|
|||||||
/src/workbench/extensions/manager/ @viva-jinyi @christian-byrne @ltdrdata
|
/src/workbench/extensions/manager/ @viva-jinyi @christian-byrne @ltdrdata
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
/src/locales/ @Yorha4D @KarryCharon @shinshin86 @Comfy-Org/comfy_maintainer
|
/src/locales/ @Yorha4D @KarryCharon @shinshin86 @Comfy-Org/comfy_maintainer @Comfy-org/comfy_frontend_devs
|
||||||
|
/src/locales/pt-BR/ @JonatanAtila @Yorha4D @KarryCharon @shinshin86
|
||||||
|
|
||||||
# LLM Instructions (blank on purpose)
|
# LLM Instructions (blank on purpose)
|
||||||
.claude/
|
.claude/
|
||||||
.cursor/
|
.cursor/
|
||||||
.cursorrules
|
.cursorrules
|
||||||
**/AGENTS.md
|
**/AGENTS.md
|
||||||
**/CLAUDE.md
|
**/CLAUDE.md
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@comfyorg/desktop-ui",
|
"name": "@comfyorg/desktop-ui",
|
||||||
"version": "0.0.3",
|
"version": "0.0.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"nx": {
|
"nx": {
|
||||||
"tags": [
|
"tags": [
|
||||||
|
|||||||
@@ -59,7 +59,8 @@ const LOCALES = [
|
|||||||
['fr', 'Français'],
|
['fr', 'Français'],
|
||||||
['es', 'Español'],
|
['es', 'Español'],
|
||||||
['ar', 'عربي'],
|
['ar', 'عربي'],
|
||||||
['tr', 'Türkçe']
|
['tr', 'Türkçe'],
|
||||||
|
['pt-BR', 'Português (BR)']
|
||||||
] as const satisfies ReadonlyArray<[string, string]>
|
] as const satisfies ReadonlyArray<[string, string]>
|
||||||
|
|
||||||
type SupportedLocale = (typeof LOCALES)[number][0]
|
type SupportedLocale = (typeof LOCALES)[number][0]
|
||||||
|
|||||||
@@ -22,7 +22,11 @@
|
|||||||
<h1 v-if="title" class="font-inter font-bold text-3xl text-neutral-300">
|
<h1 v-if="title" class="font-inter font-bold text-3xl text-neutral-300">
|
||||||
{{ title }}
|
{{ title }}
|
||||||
</h1>
|
</h1>
|
||||||
<p v-if="statusText" class="text-lg text-neutral-400">
|
<p
|
||||||
|
v-if="statusText"
|
||||||
|
class="text-lg text-neutral-400"
|
||||||
|
data-testid="startup-status-text"
|
||||||
|
>
|
||||||
{{ statusText }}
|
{{ statusText }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ const localeLoaders: Record<
|
|||||||
ru: () => import('@frontend-locales/ru/main.json'),
|
ru: () => import('@frontend-locales/ru/main.json'),
|
||||||
tr: () => import('@frontend-locales/tr/main.json'),
|
tr: () => import('@frontend-locales/tr/main.json'),
|
||||||
zh: () => import('@frontend-locales/zh/main.json'),
|
zh: () => import('@frontend-locales/zh/main.json'),
|
||||||
'zh-TW': () => import('@frontend-locales/zh-TW/main.json')
|
'zh-TW': () => import('@frontend-locales/zh-TW/main.json'),
|
||||||
|
'pt-BR': () => import('@frontend-locales/pt-BR/main.json')
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodeDefsLoaders: Record<
|
const nodeDefsLoaders: Record<
|
||||||
@@ -55,7 +56,8 @@ const nodeDefsLoaders: Record<
|
|||||||
ru: () => import('@frontend-locales/ru/nodeDefs.json'),
|
ru: () => import('@frontend-locales/ru/nodeDefs.json'),
|
||||||
tr: () => import('@frontend-locales/tr/nodeDefs.json'),
|
tr: () => import('@frontend-locales/tr/nodeDefs.json'),
|
||||||
zh: () => import('@frontend-locales/zh/nodeDefs.json'),
|
zh: () => import('@frontend-locales/zh/nodeDefs.json'),
|
||||||
'zh-TW': () => import('@frontend-locales/zh-TW/nodeDefs.json')
|
'zh-TW': () => import('@frontend-locales/zh-TW/nodeDefs.json'),
|
||||||
|
'pt-BR': () => import('@frontend-locales/pt-BR/nodeDefs.json')
|
||||||
}
|
}
|
||||||
|
|
||||||
const commandsLoaders: Record<
|
const commandsLoaders: Record<
|
||||||
@@ -70,7 +72,8 @@ const commandsLoaders: Record<
|
|||||||
ru: () => import('@frontend-locales/ru/commands.json'),
|
ru: () => import('@frontend-locales/ru/commands.json'),
|
||||||
tr: () => import('@frontend-locales/tr/commands.json'),
|
tr: () => import('@frontend-locales/tr/commands.json'),
|
||||||
zh: () => import('@frontend-locales/zh/commands.json'),
|
zh: () => import('@frontend-locales/zh/commands.json'),
|
||||||
'zh-TW': () => import('@frontend-locales/zh-TW/commands.json')
|
'zh-TW': () => import('@frontend-locales/zh-TW/commands.json'),
|
||||||
|
'pt-BR': () => import('@frontend-locales/pt-BR/commands.json')
|
||||||
}
|
}
|
||||||
|
|
||||||
const settingsLoaders: Record<
|
const settingsLoaders: Record<
|
||||||
@@ -85,7 +88,8 @@ const settingsLoaders: Record<
|
|||||||
ru: () => import('@frontend-locales/ru/settings.json'),
|
ru: () => import('@frontend-locales/ru/settings.json'),
|
||||||
tr: () => import('@frontend-locales/tr/settings.json'),
|
tr: () => import('@frontend-locales/tr/settings.json'),
|
||||||
zh: () => import('@frontend-locales/zh/settings.json'),
|
zh: () => import('@frontend-locales/zh/settings.json'),
|
||||||
'zh-TW': () => import('@frontend-locales/zh-TW/settings.json')
|
'zh-TW': () => import('@frontend-locales/zh-TW/settings.json'),
|
||||||
|
'pt-BR': () => import('@frontend-locales/pt-BR/settings.json')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track which locales have been loaded
|
// Track which locales have been loaded
|
||||||
|
|||||||
@@ -557,7 +557,7 @@ export class ComfyPage {
|
|||||||
async dragAndDrop(source: Position, target: Position) {
|
async dragAndDrop(source: Position, target: Position) {
|
||||||
await this.page.mouse.move(source.x, source.y)
|
await this.page.mouse.move(source.x, source.y)
|
||||||
await this.page.mouse.down()
|
await this.page.mouse.down()
|
||||||
await this.page.mouse.move(target.x, target.y)
|
await this.page.mouse.move(target.x, target.y, { steps: 100 })
|
||||||
await this.page.mouse.up()
|
await this.page.mouse.up()
|
||||||
await this.nextFrame()
|
await this.nextFrame()
|
||||||
}
|
}
|
||||||
@@ -1651,7 +1651,10 @@ export const comfyPageFixture = base.extend<{
|
|||||||
// Set tutorial completed to true to avoid loading the tutorial workflow.
|
// Set tutorial completed to true to avoid loading the tutorial workflow.
|
||||||
'Comfy.TutorialCompleted': true,
|
'Comfy.TutorialCompleted': true,
|
||||||
'Comfy.SnapToGrid.GridSize': testComfySnapToGridGridSize,
|
'Comfy.SnapToGrid.GridSize': testComfySnapToGridGridSize,
|
||||||
'Comfy.VueNodes.AutoScaleLayout': false
|
'Comfy.VueNodes.AutoScaleLayout': false,
|
||||||
|
// Disable toast warning about version compatibility, as they may or
|
||||||
|
// may not appear - depending on upstream ComfyUI dependencies
|
||||||
|
'Comfy.VersionCompatibility.DisableWarnings': true
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
|
|||||||
@@ -65,7 +65,9 @@ export class VueNodeHelpers {
|
|||||||
* Select a specific Vue node by ID
|
* Select a specific Vue node by ID
|
||||||
*/
|
*/
|
||||||
async selectNode(nodeId: string): Promise<void> {
|
async selectNode(nodeId: string): Promise<void> {
|
||||||
await this.page.locator(`[data-node-id="${nodeId}"]`).click()
|
await this.page
|
||||||
|
.locator(`[data-node-id="${nodeId}"] .lg-node-header`)
|
||||||
|
.click()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,11 +79,13 @@ export class VueNodeHelpers {
|
|||||||
// Select first node normally
|
// Select first node normally
|
||||||
await this.selectNode(nodeIds[0])
|
await this.selectNode(nodeIds[0])
|
||||||
|
|
||||||
// Add additional nodes with Ctrl+click
|
// Add additional nodes with Ctrl+click on header
|
||||||
for (let i = 1; i < nodeIds.length; i++) {
|
for (let i = 1; i < nodeIds.length; i++) {
|
||||||
await this.page.locator(`[data-node-id="${nodeIds[i]}"]`).click({
|
await this.page
|
||||||
modifiers: ['Control']
|
.locator(`[data-node-id="${nodeIds[i]}"] .lg-node-header`)
|
||||||
})
|
.click({
|
||||||
|
modifiers: ['Control']
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 36 KiB |
@@ -94,7 +94,7 @@ async function connectSlots(
|
|||||||
const fromLoc = slotLocator(page, from.nodeId, from.index, false)
|
const fromLoc = slotLocator(page, from.nodeId, from.index, false)
|
||||||
const toLoc = slotLocator(page, to.nodeId, to.index, true)
|
const toLoc = slotLocator(page, to.nodeId, to.index, true)
|
||||||
await expectVisibleAll(fromLoc, toLoc)
|
await expectVisibleAll(fromLoc, toLoc)
|
||||||
await fromLoc.dragTo(toLoc)
|
await fromLoc.dragTo(toLoc, { force: true })
|
||||||
await nextFrame()
|
await nextFrame()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +183,7 @@ test.describe('Vue Node Link Interaction', () => {
|
|||||||
const inputSlot = slotLocator(comfyPage.page, clipNode.id, 0, true)
|
const inputSlot = slotLocator(comfyPage.page, clipNode.id, 0, true)
|
||||||
await expectVisibleAll(outputSlot, inputSlot)
|
await expectVisibleAll(outputSlot, inputSlot)
|
||||||
|
|
||||||
await outputSlot.dragTo(inputSlot)
|
await outputSlot.dragTo(inputSlot, { force: true })
|
||||||
await comfyPage.nextFrame()
|
await comfyPage.nextFrame()
|
||||||
|
|
||||||
expect(await samplerOutput.getLinkCount()).toBe(0)
|
expect(await samplerOutput.getLinkCount()).toBe(0)
|
||||||
@@ -210,7 +210,7 @@ test.describe('Vue Node Link Interaction', () => {
|
|||||||
const inputSlot = slotLocator(comfyPage.page, samplerNode.id, 3, true)
|
const inputSlot = slotLocator(comfyPage.page, samplerNode.id, 3, true)
|
||||||
await expectVisibleAll(outputSlot, inputSlot)
|
await expectVisibleAll(outputSlot, inputSlot)
|
||||||
|
|
||||||
await outputSlot.dragTo(inputSlot)
|
await outputSlot.dragTo(inputSlot, { force: true })
|
||||||
await comfyPage.nextFrame()
|
await comfyPage.nextFrame()
|
||||||
|
|
||||||
expect(await samplerOutput.getLinkCount()).toBe(0)
|
expect(await samplerOutput.getLinkCount()).toBe(0)
|
||||||
@@ -828,55 +828,55 @@ test.describe('Vue Node Link Interaction', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test.describe('Release actions (Shift-drop)', () => {
|
test.describe('Release actions (Shift-drop)', () => {
|
||||||
test('Context menu opens and endpoint is pinned on Shift-drop', async ({
|
test.fixme(
|
||||||
comfyPage,
|
'Context menu opens and endpoint is pinned on Shift-drop',
|
||||||
comfyMouse
|
async ({ comfyPage, comfyMouse }) => {
|
||||||
}) => {
|
await comfyPage.setSetting(
|
||||||
await comfyPage.setSetting(
|
'Comfy.LinkRelease.ActionShift',
|
||||||
'Comfy.LinkRelease.ActionShift',
|
'context menu'
|
||||||
'context menu'
|
)
|
||||||
)
|
|
||||||
|
|
||||||
const samplerNode = (await comfyPage.getNodeRefsByType('KSampler'))[0]
|
const samplerNode = (await comfyPage.getNodeRefsByType('KSampler'))[0]
|
||||||
expect(samplerNode).toBeTruthy()
|
expect(samplerNode).toBeTruthy()
|
||||||
|
|
||||||
const outputCenter = await getSlotCenter(
|
const outputCenter = await getSlotCenter(
|
||||||
comfyPage.page,
|
comfyPage.page,
|
||||||
samplerNode.id,
|
samplerNode.id,
|
||||||
0,
|
0,
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
|
|
||||||
const dropPos = { x: outputCenter.x + 180, y: outputCenter.y - 140 }
|
const dropPos = { x: outputCenter.x + 180, y: outputCenter.y - 140 }
|
||||||
|
|
||||||
await comfyMouse.move(outputCenter)
|
await comfyMouse.move(outputCenter)
|
||||||
await comfyPage.page.keyboard.down('Shift')
|
await comfyPage.page.keyboard.down('Shift')
|
||||||
try {
|
try {
|
||||||
await comfyMouse.drag(dropPos)
|
await comfyMouse.drag(dropPos)
|
||||||
await comfyMouse.drop()
|
await comfyMouse.drop()
|
||||||
} finally {
|
} finally {
|
||||||
await comfyPage.page.keyboard.up('Shift').catch(() => {})
|
await comfyPage.page.keyboard.up('Shift').catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context menu should be visible
|
||||||
|
const contextMenu = comfyPage.page.locator('.litecontextmenu')
|
||||||
|
await expect(contextMenu).toBeVisible()
|
||||||
|
|
||||||
|
// Pinned endpoint should not change with mouse movement while menu is open
|
||||||
|
const before = await comfyPage.page.evaluate(() => {
|
||||||
|
const snap = window['app']?.canvas?.linkConnector?.state?.snapLinksPos
|
||||||
|
return Array.isArray(snap) ? [snap[0], snap[1]] : null
|
||||||
|
})
|
||||||
|
expect(before).not.toBeNull()
|
||||||
|
|
||||||
|
// Move mouse elsewhere and verify snap position is unchanged
|
||||||
|
await comfyMouse.move({ x: dropPos.x + 160, y: dropPos.y + 100 })
|
||||||
|
const after = await comfyPage.page.evaluate(() => {
|
||||||
|
const snap = window['app']?.canvas?.linkConnector?.state?.snapLinksPos
|
||||||
|
return Array.isArray(snap) ? [snap[0], snap[1]] : null
|
||||||
|
})
|
||||||
|
expect(after).toEqual(before)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
// Context menu should be visible
|
|
||||||
const contextMenu = comfyPage.page.locator('.litecontextmenu')
|
|
||||||
await expect(contextMenu).toBeVisible()
|
|
||||||
|
|
||||||
// Pinned endpoint should not change with mouse movement while menu is open
|
|
||||||
const before = await comfyPage.page.evaluate(() => {
|
|
||||||
const snap = window['app']?.canvas?.linkConnector?.state?.snapLinksPos
|
|
||||||
return Array.isArray(snap) ? [snap[0], snap[1]] : null
|
|
||||||
})
|
|
||||||
expect(before).not.toBeNull()
|
|
||||||
|
|
||||||
// Move mouse elsewhere and verify snap position is unchanged
|
|
||||||
await comfyMouse.move({ x: dropPos.x + 160, y: dropPos.y + 100 })
|
|
||||||
const after = await comfyPage.page.evaluate(() => {
|
|
||||||
const snap = window['app']?.canvas?.linkConnector?.state?.snapLinksPos
|
|
||||||
return Array.isArray(snap) ? [snap[0], snap[1]] : null
|
|
||||||
})
|
|
||||||
expect(after).toEqual(before)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Context menu -> Search pre-filters by link type and connects after selection', async ({
|
test('Context menu -> Search pre-filters by link type and connects after selection', async ({
|
||||||
comfyPage,
|
comfyPage,
|
||||||
@@ -897,7 +897,7 @@ test.describe('Vue Node Link Interaction', () => {
|
|||||||
0,
|
0,
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
const dropPos = { x: outputCenter.x + 200, y: outputCenter.y - 120 }
|
const dropPos = { x: outputCenter.x + 200, y: outputCenter.y - 100 }
|
||||||
|
|
||||||
await comfyMouse.move(outputCenter)
|
await comfyMouse.move(outputCenter)
|
||||||
await comfyPage.page.keyboard.down('Shift')
|
await comfyPage.page.keyboard.down('Shift')
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 38 KiB |
@@ -0,0 +1,54 @@
|
|||||||
|
import {
|
||||||
|
comfyExpect as expect,
|
||||||
|
comfyPageFixture as test
|
||||||
|
} from '../../../../fixtures/ComfyPage'
|
||||||
|
|
||||||
|
test.describe('Vue Node Resizing', () => {
|
||||||
|
test.beforeEach(async ({ comfyPage }) => {
|
||||||
|
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
|
||||||
|
await comfyPage.vueNodes.waitForNodes()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should resize node without position drift after selecting', async ({
|
||||||
|
comfyPage
|
||||||
|
}) => {
|
||||||
|
// Get a Vue node fixture
|
||||||
|
const node = await comfyPage.vueNodes.getFixtureByTitle('Load Checkpoint')
|
||||||
|
const initialBox = await node.boundingBox()
|
||||||
|
if (!initialBox) throw new Error('Node bounding box not found')
|
||||||
|
|
||||||
|
// Select the node first (this was causing the bug)
|
||||||
|
await node.header.click()
|
||||||
|
await comfyPage.page.waitForTimeout(100) // Brief pause after selection
|
||||||
|
|
||||||
|
// Get position after selection
|
||||||
|
const selectedBox = await node.boundingBox()
|
||||||
|
if (!selectedBox)
|
||||||
|
throw new Error('Node bounding box not found after select')
|
||||||
|
|
||||||
|
// Verify position unchanged after selection
|
||||||
|
expect(selectedBox.x).toBeCloseTo(initialBox.x, 1)
|
||||||
|
expect(selectedBox.y).toBeCloseTo(initialBox.y, 1)
|
||||||
|
|
||||||
|
// Now resize from bottom-right corner
|
||||||
|
const resizeStartX = selectedBox.x + selectedBox.width - 5
|
||||||
|
const resizeStartY = selectedBox.y + selectedBox.height - 5
|
||||||
|
|
||||||
|
await comfyPage.page.mouse.move(resizeStartX, resizeStartY)
|
||||||
|
await comfyPage.page.mouse.down()
|
||||||
|
await comfyPage.page.mouse.move(resizeStartX + 50, resizeStartY + 30)
|
||||||
|
await comfyPage.page.mouse.up()
|
||||||
|
|
||||||
|
// Get final position and size
|
||||||
|
const finalBox = await node.boundingBox()
|
||||||
|
if (!finalBox) throw new Error('Node bounding box not found after resize')
|
||||||
|
|
||||||
|
// Position should NOT have changed (the bug was position drift)
|
||||||
|
expect(finalBox.x).toBeCloseTo(initialBox.x, 1)
|
||||||
|
expect(finalBox.y).toBeCloseTo(initialBox.y, 1)
|
||||||
|
|
||||||
|
// Size should have increased
|
||||||
|
expect(finalBox.width).toBeGreaterThan(initialBox.width)
|
||||||
|
expect(finalBox.height).toBeGreaterThan(initialBox.height)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 123 KiB |
|
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 123 KiB |
|
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 143 KiB |
@@ -1,49 +0,0 @@
|
|||||||
import { expect } from '@playwright/test'
|
|
||||||
|
|
||||||
import { comfyPageFixture as test } from '../../../fixtures/ComfyPage'
|
|
||||||
|
|
||||||
test.beforeEach(async ({ comfyPage }) => {
|
|
||||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
|
||||||
})
|
|
||||||
|
|
||||||
test.describe('Vue Nodes - LOD', () => {
|
|
||||||
test.beforeEach(async ({ comfyPage }) => {
|
|
||||||
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
|
|
||||||
await comfyPage.setSetting('LiteGraph.Canvas.MinFontSizeForLOD', 8)
|
|
||||||
await comfyPage.setup()
|
|
||||||
await comfyPage.loadWorkflow('default')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('should toggle LOD based on zoom threshold', async ({ comfyPage }) => {
|
|
||||||
await comfyPage.vueNodes.waitForNodes()
|
|
||||||
|
|
||||||
const initialNodeCount = await comfyPage.vueNodes.getNodeCount()
|
|
||||||
expect(initialNodeCount).toBeGreaterThan(0)
|
|
||||||
|
|
||||||
await expect(comfyPage.canvas).toHaveScreenshot('vue-nodes-default.png')
|
|
||||||
|
|
||||||
const vueNodesContainer = comfyPage.vueNodes.nodes
|
|
||||||
const textboxesInNodes = vueNodesContainer.getByRole('textbox')
|
|
||||||
const comboboxesInNodes = vueNodesContainer.getByRole('combobox')
|
|
||||||
|
|
||||||
await expect(textboxesInNodes.first()).toBeVisible()
|
|
||||||
await expect(comboboxesInNodes.first()).toBeVisible()
|
|
||||||
|
|
||||||
await comfyPage.zoom(120, 10)
|
|
||||||
await comfyPage.nextFrame()
|
|
||||||
|
|
||||||
await expect(comfyPage.canvas).toHaveScreenshot('vue-nodes-lod-active.png')
|
|
||||||
|
|
||||||
await expect(textboxesInNodes.first()).toBeHidden()
|
|
||||||
await expect(comboboxesInNodes.first()).toBeHidden()
|
|
||||||
|
|
||||||
await comfyPage.zoom(-120, 10)
|
|
||||||
await comfyPage.nextFrame()
|
|
||||||
|
|
||||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
|
||||||
'vue-nodes-lod-inactive.png'
|
|
||||||
)
|
|
||||||
await expect(textboxesInNodes.first()).toBeVisible()
|
|
||||||
await expect(comboboxesInNodes.first()).toBeVisible()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 123 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 81 KiB |
@@ -88,12 +88,14 @@ export function comfyAPIPlugin(isDev: boolean): Plugin {
|
|||||||
|
|
||||||
if (result.exports.length > 0) {
|
if (result.exports.length > 0) {
|
||||||
const projectRoot = process.cwd()
|
const projectRoot = process.cwd()
|
||||||
const relativePath = path.relative(path.join(projectRoot, 'src'), id)
|
const relativePath = path
|
||||||
|
.relative(path.join(projectRoot, 'src'), id)
|
||||||
|
.replace(/\\/g, '/')
|
||||||
const shimFileName = relativePath.replace(/\.ts$/, '.js')
|
const shimFileName = relativePath.replace(/\.ts$/, '.js')
|
||||||
|
|
||||||
let shimContent = `// Shim for ${relativePath}\n`
|
let shimContent = `// Shim for ${relativePath}\n`
|
||||||
|
|
||||||
const fileKey = relativePath.replace(/\.ts$/, '').replace(/\\/g, '/')
|
const fileKey = relativePath.replace(/\.ts$/, '')
|
||||||
const warningMessage = getWarningMessage(fileKey, shimFileName)
|
const warningMessage = getWarningMessage(fileKey, shimFileName)
|
||||||
|
|
||||||
if (warningMessage) {
|
if (warningMessage) {
|
||||||
|
|||||||
@@ -1,154 +0,0 @@
|
|||||||
import glob from 'fast-glob'
|
|
||||||
import fs from 'fs-extra'
|
|
||||||
import { dirname, join } from 'node:path'
|
|
||||||
import { type HtmlTagDescriptor, type Plugin, normalizePath } from 'vite'
|
|
||||||
|
|
||||||
interface ImportMapSource {
|
|
||||||
name: string
|
|
||||||
pattern: string | RegExp
|
|
||||||
entry: string
|
|
||||||
recursiveDependence?: boolean
|
|
||||||
override?: Record<string, Partial<ImportMapSource>>
|
|
||||||
}
|
|
||||||
|
|
||||||
const parseDeps = (root: string, pkg: string) => {
|
|
||||||
const pkgPath = join(root, 'node_modules', pkg, 'package.json')
|
|
||||||
if (fs.existsSync(pkgPath)) {
|
|
||||||
const content = fs.readFileSync(pkgPath, 'utf-8')
|
|
||||||
const pkg = JSON.parse(content)
|
|
||||||
return Object.keys(pkg.dependencies || {})
|
|
||||||
}
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Vite plugin that generates an import map for vendor chunks.
|
|
||||||
*
|
|
||||||
* This plugin creates a browser-compatible import map that maps module specifiers
|
|
||||||
* (like 'vue' or 'primevue') to their actual file locations in the build output.
|
|
||||||
* This improves module loading in modern browsers and enables better caching.
|
|
||||||
*
|
|
||||||
* The plugin:
|
|
||||||
* 1. Tracks vendor chunks during bundle generation
|
|
||||||
* 2. Creates mappings between module names and their file paths
|
|
||||||
* 3. Injects an import map script tag into the HTML head
|
|
||||||
* 4. Configures manual chunk splitting for vendor libraries
|
|
||||||
*
|
|
||||||
* @param vendorLibraries - An array of vendor libraries to split into separate chunks
|
|
||||||
* @returns {Plugin} A Vite plugin that generates and injects an import map
|
|
||||||
*/
|
|
||||||
export function generateImportMapPlugin(
|
|
||||||
importMapSources: ImportMapSource[]
|
|
||||||
): Plugin {
|
|
||||||
const importMapEntries: Record<string, string> = {}
|
|
||||||
const resolvedImportMapSources: Map<string, ImportMapSource> = new Map()
|
|
||||||
const assetDir = 'assets/lib'
|
|
||||||
let root: string
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: 'generate-import-map-plugin',
|
|
||||||
|
|
||||||
// Configure manual chunks during the build process
|
|
||||||
configResolved(config) {
|
|
||||||
root = config.root
|
|
||||||
|
|
||||||
if (config.build) {
|
|
||||||
// Ensure rollupOptions exists
|
|
||||||
if (!config.build.rollupOptions) {
|
|
||||||
config.build.rollupOptions = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const source of importMapSources) {
|
|
||||||
resolvedImportMapSources.set(source.name, source)
|
|
||||||
if (source.recursiveDependence) {
|
|
||||||
const deps = parseDeps(root, source.name)
|
|
||||||
|
|
||||||
while (deps.length) {
|
|
||||||
const dep = deps.shift()!
|
|
||||||
const depSource = Object.assign({}, source, {
|
|
||||||
name: dep,
|
|
||||||
pattern: dep,
|
|
||||||
...source.override?.[dep]
|
|
||||||
})
|
|
||||||
resolvedImportMapSources.set(depSource.name, depSource)
|
|
||||||
|
|
||||||
const _deps = parseDeps(root, depSource.name)
|
|
||||||
deps.unshift(..._deps)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const external: (string | RegExp)[] = []
|
|
||||||
for (const [, source] of resolvedImportMapSources) {
|
|
||||||
external.push(source.pattern)
|
|
||||||
}
|
|
||||||
config.build.rollupOptions.external = external
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
generateBundle(_options) {
|
|
||||||
for (const [, source] of resolvedImportMapSources) {
|
|
||||||
if (source.entry) {
|
|
||||||
const moduleFile = join(source.name, source.entry)
|
|
||||||
const sourceFile = join(root, 'node_modules', moduleFile)
|
|
||||||
const targetFile = join(root, 'dist', assetDir, moduleFile)
|
|
||||||
|
|
||||||
importMapEntries[source.name] =
|
|
||||||
'./' + normalizePath(join(assetDir, moduleFile))
|
|
||||||
|
|
||||||
const targetDir = dirname(targetFile)
|
|
||||||
if (!fs.existsSync(targetDir)) {
|
|
||||||
fs.mkdirSync(targetDir, { recursive: true })
|
|
||||||
}
|
|
||||||
fs.copyFileSync(sourceFile, targetFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (source.recursiveDependence) {
|
|
||||||
const files = glob.sync(['**/*.{js,mjs}'], {
|
|
||||||
cwd: join(root, 'node_modules', source.name)
|
|
||||||
})
|
|
||||||
|
|
||||||
for (const file of files) {
|
|
||||||
const moduleFile = join(source.name, file)
|
|
||||||
const sourceFile = join(root, 'node_modules', moduleFile)
|
|
||||||
const targetFile = join(root, 'dist', assetDir, moduleFile)
|
|
||||||
|
|
||||||
importMapEntries[normalizePath(join(source.name, dirname(file)))] =
|
|
||||||
'./' + normalizePath(join(assetDir, moduleFile))
|
|
||||||
|
|
||||||
const targetDir = dirname(targetFile)
|
|
||||||
if (!fs.existsSync(targetDir)) {
|
|
||||||
fs.mkdirSync(targetDir, { recursive: true })
|
|
||||||
}
|
|
||||||
fs.copyFileSync(sourceFile, targetFile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
transformIndexHtml(html) {
|
|
||||||
if (Object.keys(importMapEntries).length === 0) {
|
|
||||||
console.warn(
|
|
||||||
'[ImportMap Plugin] No vendor chunks found to create import map.'
|
|
||||||
)
|
|
||||||
return html
|
|
||||||
}
|
|
||||||
|
|
||||||
const importMap = {
|
|
||||||
imports: importMapEntries
|
|
||||||
}
|
|
||||||
|
|
||||||
const importMapTag: HtmlTagDescriptor = {
|
|
||||||
tag: 'script',
|
|
||||||
attrs: { type: 'importmap' },
|
|
||||||
children: JSON.stringify(importMap, null, 2),
|
|
||||||
injectTo: 'head'
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
html,
|
|
||||||
tags: [importMapTag]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,2 +1 @@
|
|||||||
export { comfyAPIPlugin } from './comfyAPIPlugin'
|
export { comfyAPIPlugin } from './comfyAPIPlugin'
|
||||||
export { generateImportMapPlugin } from './generateImportMapPlugin'
|
|
||||||
|
|||||||
@@ -191,6 +191,19 @@ export default defineConfig([
|
|||||||
'@intlify/vue-i18n/no-raw-text': [
|
'@intlify/vue-i18n/no-raw-text': [
|
||||||
'error',
|
'error',
|
||||||
{
|
{
|
||||||
|
attributes: {
|
||||||
|
'/.+/': [
|
||||||
|
'aria-label',
|
||||||
|
'aria-placeholder',
|
||||||
|
'aria-roledescription',
|
||||||
|
'aria-valuetext',
|
||||||
|
'label',
|
||||||
|
'placeholder',
|
||||||
|
'title',
|
||||||
|
'v-tooltip'
|
||||||
|
],
|
||||||
|
img: ['alt']
|
||||||
|
},
|
||||||
// Ignore strings that are:
|
// Ignore strings that are:
|
||||||
// 1. Less than 2 characters
|
// 1. Less than 2 characters
|
||||||
// 2. Only symbols/numbers/whitespace (no letters)
|
// 2. Only symbols/numbers/whitespace (no letters)
|
||||||
@@ -200,24 +213,27 @@ export default defineConfig([
|
|||||||
ignoreNodes: ['md-icon', 'v-icon', 'pre', 'code', 'script', 'style'],
|
ignoreNodes: ['md-icon', 'v-icon', 'pre', 'code', 'script', 'style'],
|
||||||
// Brand names and technical terms that shouldn't be translated
|
// Brand names and technical terms that shouldn't be translated
|
||||||
ignoreText: [
|
ignoreText: [
|
||||||
'ComfyUI',
|
|
||||||
'GitHub',
|
|
||||||
'OpenAI',
|
|
||||||
'API',
|
'API',
|
||||||
'URL',
|
|
||||||
'JSON',
|
|
||||||
'YAML',
|
|
||||||
'GPU',
|
|
||||||
'CPU',
|
|
||||||
'RAM',
|
|
||||||
'GB',
|
|
||||||
'MB',
|
|
||||||
'KB',
|
|
||||||
'ms',
|
|
||||||
'fps',
|
|
||||||
'px',
|
|
||||||
'App Data:',
|
'App Data:',
|
||||||
'App Path:'
|
'App Path:',
|
||||||
|
'ComfyUI',
|
||||||
|
'CPU',
|
||||||
|
'fps',
|
||||||
|
'GB',
|
||||||
|
'GitHub',
|
||||||
|
'GPU',
|
||||||
|
'JSON',
|
||||||
|
'KB',
|
||||||
|
'LoRA',
|
||||||
|
'MB',
|
||||||
|
'ms',
|
||||||
|
'OpenAI',
|
||||||
|
'png',
|
||||||
|
'px',
|
||||||
|
'RAM',
|
||||||
|
'URL',
|
||||||
|
'YAML',
|
||||||
|
'1.2 MB'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const config: KnipConfig = {
|
|||||||
project: ['src/**/*.{js,ts}']
|
project: ['src/**/*.{js,ts}']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ignoreBinaries: ['python3'],
|
ignoreBinaries: ['python3', 'gh'],
|
||||||
ignoreDependencies: [
|
ignoreDependencies: [
|
||||||
// Weird importmap things
|
// Weird importmap things
|
||||||
'@iconify/json',
|
'@iconify/json',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@comfyorg/comfyui-frontend",
|
"name": "@comfyorg/comfyui-frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.33.2",
|
"version": "1.34.5",
|
||||||
"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",
|
||||||
@@ -79,6 +79,7 @@
|
|||||||
"@vitest/coverage-v8": "catalog:",
|
"@vitest/coverage-v8": "catalog:",
|
||||||
"@vitest/ui": "catalog:",
|
"@vitest/ui": "catalog:",
|
||||||
"@vue/test-utils": "catalog:",
|
"@vue/test-utils": "catalog:",
|
||||||
|
"@webgpu/types": "catalog:",
|
||||||
"cross-env": "catalog:",
|
"cross-env": "catalog:",
|
||||||
"eslint": "catalog:",
|
"eslint": "catalog:",
|
||||||
"eslint-config-prettier": "catalog:",
|
"eslint-config-prettier": "catalog:",
|
||||||
@@ -116,6 +117,7 @@
|
|||||||
"typescript": "catalog:",
|
"typescript": "catalog:",
|
||||||
"typescript-eslint": "catalog:",
|
"typescript-eslint": "catalog:",
|
||||||
"unplugin-icons": "catalog:",
|
"unplugin-icons": "catalog:",
|
||||||
|
"unplugin-typegpu": "catalog:",
|
||||||
"unplugin-vue-components": "catalog:",
|
"unplugin-vue-components": "catalog:",
|
||||||
"uuid": "^11.1.0",
|
"uuid": "^11.1.0",
|
||||||
"vite": "catalog:",
|
"vite": "catalog:",
|
||||||
@@ -166,7 +168,6 @@
|
|||||||
"es-toolkit": "^1.39.9",
|
"es-toolkit": "^1.39.9",
|
||||||
"extendable-media-recorder": "^9.2.27",
|
"extendable-media-recorder": "^9.2.27",
|
||||||
"extendable-media-recorder-wav-encoder": "^7.0.129",
|
"extendable-media-recorder-wav-encoder": "^7.0.129",
|
||||||
"fast-glob": "^3.3.3",
|
|
||||||
"firebase": "catalog:",
|
"firebase": "catalog:",
|
||||||
"fuse.js": "^7.0.0",
|
"fuse.js": "^7.0.0",
|
||||||
"glob": "^11.0.3",
|
"glob": "^11.0.3",
|
||||||
@@ -180,6 +181,7 @@
|
|||||||
"semver": "^7.7.2",
|
"semver": "^7.7.2",
|
||||||
"three": "^0.170.0",
|
"three": "^0.170.0",
|
||||||
"tiptap-markdown": "^0.8.10",
|
"tiptap-markdown": "^0.8.10",
|
||||||
|
"typegpu": "catalog:",
|
||||||
"vue": "catalog:",
|
"vue": "catalog:",
|
||||||
"vue-i18n": "catalog:",
|
"vue-i18n": "catalog:",
|
||||||
"vue-router": "catalog:",
|
"vue-router": "catalog:",
|
||||||
|
|||||||
@@ -194,7 +194,7 @@
|
|||||||
--node-component-executing: var(--color-blue-500);
|
--node-component-executing: var(--color-blue-500);
|
||||||
--node-component-header: var(--fg-color);
|
--node-component-header: var(--fg-color);
|
||||||
--node-component-header-icon: var(--color-ash-800);
|
--node-component-header-icon: var(--color-ash-800);
|
||||||
--node-component-header-surface: var(--color-white);
|
--node-component-header-surface: var(--color-smoke-400);
|
||||||
--node-component-outline: var(--color-black);
|
--node-component-outline: var(--color-black);
|
||||||
--node-component-ring: rgb(from var(--color-smoke-500) r g b / 50%);
|
--node-component-ring: rgb(from var(--color-smoke-500) r g b / 50%);
|
||||||
--node-component-slot-dot-outline-opacity-mult: 1;
|
--node-component-slot-dot-outline-opacity-mult: 1;
|
||||||
@@ -1190,24 +1190,19 @@ dialog::backdrop {
|
|||||||
.litegraph.litecontextmenu,
|
.litegraph.litecontextmenu,
|
||||||
.litegraph.litecontextmenu.dark {
|
.litegraph.litecontextmenu.dark {
|
||||||
z-index: 9999 !important;
|
z-index: 9999 !important;
|
||||||
background-color: var(--comfy-menu-bg) !important;
|
background-color: var(--comfy-menu-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.litegraph.litecontextmenu
|
|
||||||
.litemenu-entry:hover:not(.disabled):not(.separator) {
|
|
||||||
background-color: var(--comfy-menu-hover-bg, var(--border-color)) !important;
|
|
||||||
color: var(--fg-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.litegraph.litecontextmenu .litemenu-entry.submenu,
|
.litegraph.litecontextmenu .litemenu-entry.submenu,
|
||||||
.litegraph.litecontextmenu.dark .litemenu-entry.submenu {
|
.litegraph.litecontextmenu.dark .litemenu-entry.submenu {
|
||||||
background-color: var(--comfy-menu-bg) !important;
|
background-color: var(--comfy-menu-bg);
|
||||||
color: var(--input-text);
|
color: var(--fg-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.litegraph.litecontextmenu input {
|
.litegraph.litecontextmenu input {
|
||||||
background-color: var(--comfy-input-bg) !important;
|
background-color: var(--comfy-input-bg);
|
||||||
color: var(--input-text) !important;
|
color: var(--input-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.comfy-context-menu-filter {
|
.comfy-context-menu-filter {
|
||||||
@@ -1248,14 +1243,14 @@ dialog::backdrop {
|
|||||||
|
|
||||||
.litegraph.litesearchbox {
|
.litegraph.litesearchbox {
|
||||||
z-index: 9999 !important;
|
z-index: 9999 !important;
|
||||||
background-color: var(--comfy-menu-bg) !important;
|
background-color: var(--comfy-menu-bg);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.litegraph.litesearchbox input,
|
.litegraph.litesearchbox input,
|
||||||
.litegraph.litesearchbox select {
|
.litegraph.litesearchbox select {
|
||||||
background-color: var(--comfy-input-bg) !important;
|
background-color: var(--comfy-input-bg);
|
||||||
color: var(--input-text);
|
color: var(--input-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1320,66 +1315,6 @@ audio.comfy-audio.empty-audio-widget {
|
|||||||
font-size 0.1s ease;
|
font-size 0.1s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Performance optimization during canvas interaction */
|
|
||||||
.transform-pane--interacting .lg-node * {
|
|
||||||
transition: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.transform-pane--interacting .lg-node {
|
|
||||||
will-change: transform;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* START LOD specific styles */
|
|
||||||
/* LOD styles - Custom CSS avoids 100+ Tailwind selectors that would slow style recalculation when .isLOD toggles */
|
|
||||||
|
|
||||||
.isLOD .lg-node {
|
|
||||||
box-shadow: none;
|
|
||||||
filter: none;
|
|
||||||
backdrop-filter: none;
|
|
||||||
text-shadow: none;
|
|
||||||
mask-image: none;
|
|
||||||
clip-path: none;
|
|
||||||
background-image: none;
|
|
||||||
text-rendering: optimizeSpeed;
|
|
||||||
border-radius: 0;
|
|
||||||
contain: layout style;
|
|
||||||
transition: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.isLOD .lg-node-header {
|
|
||||||
border-radius: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.isLOD .lg-node-widgets {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lod-toggle {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
.isLOD .lod-toggle {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lod-fallback {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.isLOD .lod-fallback {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.isLOD .image-preview img {
|
|
||||||
image-rendering: pixelated;
|
|
||||||
}
|
|
||||||
|
|
||||||
.isLOD .slot-dot {
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
/* END LOD specific styles */
|
|
||||||
|
|
||||||
/* ===================== Mask Editor Styles ===================== */
|
/* ===================== Mask Editor Styles ===================== */
|
||||||
/* To be migrated to Tailwind later */
|
/* To be migrated to Tailwind later */
|
||||||
#maskEditor_brush {
|
#maskEditor_brush {
|
||||||
|
|||||||
2713
packages/registry-types/src/comfyRegistryTypes.ts
generated
@@ -75,6 +75,17 @@ export function formatSize(value?: number) {
|
|||||||
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`
|
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a commit hash by truncating long (40-char) hashes to 7 chars.
|
||||||
|
* Returns the original string if not a valid full commit hash.
|
||||||
|
*/
|
||||||
|
export function formatCommitHash(value: string): string {
|
||||||
|
if (/^[a-f0-9]{40}$/i.test(value)) {
|
||||||
|
return value.slice(0, 7)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns various filename components.
|
* Returns various filename components.
|
||||||
* Example:
|
* Example:
|
||||||
|
|||||||
103
pnpm-lock.yaml
generated
@@ -15,15 +15,9 @@ catalogs:
|
|||||||
'@eslint/js':
|
'@eslint/js':
|
||||||
specifier: ^9.35.0
|
specifier: ^9.35.0
|
||||||
version: 9.35.0
|
version: 9.35.0
|
||||||
'@iconify-json/lucide':
|
|
||||||
specifier: ^1.1.178
|
|
||||||
version: 1.2.66
|
|
||||||
'@iconify/json':
|
'@iconify/json':
|
||||||
specifier: ^2.2.380
|
specifier: ^2.2.380
|
||||||
version: 2.2.380
|
version: 2.2.380
|
||||||
'@iconify/tailwind':
|
|
||||||
specifier: ^1.1.3
|
|
||||||
version: 1.2.0
|
|
||||||
'@intlify/eslint-plugin-vue-i18n':
|
'@intlify/eslint-plugin-vue-i18n':
|
||||||
specifier: ^4.1.0
|
specifier: ^4.1.0
|
||||||
version: 4.1.0
|
version: 4.1.0
|
||||||
@@ -126,6 +120,9 @@ catalogs:
|
|||||||
'@vueuse/integrations':
|
'@vueuse/integrations':
|
||||||
specifier: ^13.9.0
|
specifier: ^13.9.0
|
||||||
version: 13.9.0
|
version: 13.9.0
|
||||||
|
'@webgpu/types':
|
||||||
|
specifier: ^0.1.66
|
||||||
|
version: 0.1.66
|
||||||
algoliasearch:
|
algoliasearch:
|
||||||
specifier: ^5.21.0
|
specifier: ^5.21.0
|
||||||
version: 5.21.0
|
version: 5.21.0
|
||||||
@@ -246,6 +243,9 @@ catalogs:
|
|||||||
tw-animate-css:
|
tw-animate-css:
|
||||||
specifier: ^1.3.8
|
specifier: ^1.3.8
|
||||||
version: 1.3.8
|
version: 1.3.8
|
||||||
|
typegpu:
|
||||||
|
specifier: ^0.8.2
|
||||||
|
version: 0.8.2
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.9.2
|
specifier: ^5.9.2
|
||||||
version: 5.9.2
|
version: 5.9.2
|
||||||
@@ -255,6 +255,9 @@ catalogs:
|
|||||||
unplugin-icons:
|
unplugin-icons:
|
||||||
specifier: ^0.22.0
|
specifier: ^0.22.0
|
||||||
version: 0.22.0
|
version: 0.22.0
|
||||||
|
unplugin-typegpu:
|
||||||
|
specifier: 0.8.0
|
||||||
|
version: 0.8.0
|
||||||
unplugin-vue-components:
|
unplugin-vue-components:
|
||||||
specifier: ^0.28.0
|
specifier: ^0.28.0
|
||||||
version: 0.28.0
|
version: 0.28.0
|
||||||
@@ -422,9 +425,6 @@ importers:
|
|||||||
extendable-media-recorder-wav-encoder:
|
extendable-media-recorder-wav-encoder:
|
||||||
specifier: ^7.0.129
|
specifier: ^7.0.129
|
||||||
version: 7.0.129
|
version: 7.0.129
|
||||||
fast-glob:
|
|
||||||
specifier: ^3.3.3
|
|
||||||
version: 3.3.3
|
|
||||||
firebase:
|
firebase:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 11.6.0
|
version: 11.6.0
|
||||||
@@ -464,6 +464,9 @@ importers:
|
|||||||
tiptap-markdown:
|
tiptap-markdown:
|
||||||
specifier: ^0.8.10
|
specifier: ^0.8.10
|
||||||
version: 0.8.10(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
|
version: 0.8.10(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
|
||||||
|
typegpu:
|
||||||
|
specifier: 'catalog:'
|
||||||
|
version: 0.8.2
|
||||||
vue:
|
vue:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 3.5.13(typescript@5.9.2)
|
version: 3.5.13(typescript@5.9.2)
|
||||||
@@ -561,6 +564,9 @@ importers:
|
|||||||
'@vue/test-utils':
|
'@vue/test-utils':
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 2.4.6
|
version: 2.4.6
|
||||||
|
'@webgpu/types':
|
||||||
|
specifier: 'catalog:'
|
||||||
|
version: 0.1.66
|
||||||
cross-env:
|
cross-env:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 10.1.0
|
version: 10.1.0
|
||||||
@@ -672,6 +678,9 @@ importers:
|
|||||||
unplugin-icons:
|
unplugin-icons:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 0.22.0(@vue/compiler-sfc@3.5.13)
|
version: 0.22.0(@vue/compiler-sfc@3.5.13)
|
||||||
|
unplugin-typegpu:
|
||||||
|
specifier: 'catalog:'
|
||||||
|
version: 0.8.0(typegpu@0.8.2)
|
||||||
unplugin-vue-components:
|
unplugin-vue-components:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 0.28.0(@babel/parser@7.28.4)(rollup@4.22.4)(vue@3.5.13(typescript@5.9.2))
|
version: 0.28.0(@babel/parser@7.28.4)(rollup@4.22.4)(vue@3.5.13(typescript@5.9.2))
|
||||||
@@ -1431,6 +1440,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
|
resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
|
'@babel/standalone@7.28.5':
|
||||||
|
resolution: {integrity: sha512-1DViPYJpRU50irpGMfLBQ9B4kyfQuL6X7SS7pwTeWeZX0mNkjzPi0XFqxCjSdddZXUQy4AhnQnnesA/ZHnvAdw==}
|
||||||
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@babel/template@7.27.2':
|
'@babel/template@7.27.2':
|
||||||
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
|
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
@@ -3790,8 +3803,8 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
vue: ^3.5.0
|
vue: ^3.5.0
|
||||||
|
|
||||||
'@webgpu/types@0.1.51':
|
'@webgpu/types@0.1.66':
|
||||||
resolution: {integrity: sha512-ktR3u64NPjwIViNCck+z9QeyN0iPkQCUOQ07ZCV1RzlkfP+olLTeEZ95O1QHS+v4w9vJeY9xj/uJuSphsHy5rQ==}
|
resolution: {integrity: sha512-YA2hLrwLpDsRueNDXIMqN9NTzD6bCDkuXbOSe0heS+f8YE8usA6Gbv1prj81pzVHrbaAma7zObnIC+I6/sXJgA==}
|
||||||
|
|
||||||
'@xstate/fsm@1.6.5':
|
'@xstate/fsm@1.6.5':
|
||||||
resolution: {integrity: sha512-b5o1I6aLNeYlU/3CPlj/Z91ybk1gUsKT+5NAJI+2W4UjvS5KLG28K9v5UvNoFVjHV8PajVZ00RH3vnjyQO7ZAw==}
|
resolution: {integrity: sha512-b5o1I6aLNeYlU/3CPlj/Z91ybk1gUsKT+5NAJI+2W4UjvS5KLG28K9v5UvNoFVjHV8PajVZ00RH3vnjyQO7ZAw==}
|
||||||
@@ -6038,6 +6051,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
|
resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
magic-string-ast@1.0.3:
|
||||||
|
resolution: {integrity: sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==}
|
||||||
|
engines: {node: '>=20.19.0'}
|
||||||
|
|
||||||
magic-string@0.30.19:
|
magic-string@0.30.19:
|
||||||
resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==}
|
resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==}
|
||||||
|
|
||||||
@@ -7411,6 +7428,14 @@ packages:
|
|||||||
tinybench@2.9.0:
|
tinybench@2.9.0:
|
||||||
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
||||||
|
|
||||||
|
tinyest-for-wgsl@0.1.3:
|
||||||
|
resolution: {integrity: sha512-Wm5ADG1UyDxykf42S1gLYP4U9e1QP/TdtJeovQi6y68zttpiFLKqQGioHmPs9Mjysh7YMSAr/Lpuk0cD2MVdGA==}
|
||||||
|
engines: {node: '>=12.20.0'}
|
||||||
|
|
||||||
|
tinyest@0.1.2:
|
||||||
|
resolution: {integrity: sha512-aHRmouyowIq1P5jrTF+YK6pGX+WuvFtSCLbqk91yHnU3SWQRIcNIamZLM5XF6lLqB13AWz0PGPXRff2QGDsxIg==}
|
||||||
|
engines: {node: '>=12.20.0'}
|
||||||
|
|
||||||
tinyexec@0.3.2:
|
tinyexec@0.3.2:
|
||||||
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
|
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
|
||||||
|
|
||||||
@@ -7537,6 +7562,13 @@ packages:
|
|||||||
resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
|
resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
typed-binary@4.3.2:
|
||||||
|
resolution: {integrity: sha512-HT3pIBM2njCZUmeczDaQUUErGiM6GXFCqMsHegE12HCoBtvHCkfR10JJni0TeGOTnLilTd6YFyj+YhflqQDrDQ==}
|
||||||
|
|
||||||
|
typegpu@0.8.2:
|
||||||
|
resolution: {integrity: sha512-wkMJWhJE0pSkw2G/FesjqjbtHkREyOKu1Zmyj19xfmaX5+65YFwgfQNKSK8CxqN4kJkP7JFelLDJTSYY536TYg==}
|
||||||
|
engines: {node: '>=12.20.0'}
|
||||||
|
|
||||||
typescript-eslint@8.44.0:
|
typescript-eslint@8.44.0:
|
||||||
resolution: {integrity: sha512-ib7mCkYuIzYonCq9XWF5XNw+fkj2zg629PSa9KNIQ47RXFF763S5BIX4wqz1+FLPogTZoiw8KmCiRPRa8bL3qw==}
|
resolution: {integrity: sha512-ib7mCkYuIzYonCq9XWF5XNw+fkj2zg629PSa9KNIQ47RXFF763S5BIX4wqz1+FLPogTZoiw8KmCiRPRa8bL3qw==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
@@ -7641,6 +7673,11 @@ packages:
|
|||||||
vue-template-es2015-compiler:
|
vue-template-es2015-compiler:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
unplugin-typegpu@0.8.0:
|
||||||
|
resolution: {integrity: sha512-VJHdXSXGOkAx0WhwFczhVUjAI6HyDkrQXk20HnwyuzIE3FdqE5l9sJTCYZzoVGo3z8i/IA5TMHCDzzP0Bc97Cw==}
|
||||||
|
peerDependencies:
|
||||||
|
typegpu: ^0.8.0
|
||||||
|
|
||||||
unplugin-vue-components@0.28.0:
|
unplugin-vue-components@0.28.0:
|
||||||
resolution: {integrity: sha512-jiTGtJ3JsRFBjgvyilfrX7yUoGKScFgbdNw+6p6kEXU+Spf/rhxzgvdfuMcvhCcLmflB/dY3pGQshYBVGOUx7Q==}
|
resolution: {integrity: sha512-jiTGtJ3JsRFBjgvyilfrX7yUoGKScFgbdNw+6p6kEXU+Spf/rhxzgvdfuMcvhCcLmflB/dY3pGQshYBVGOUx7Q==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
@@ -7831,8 +7868,8 @@ packages:
|
|||||||
vue-component-type-helpers@3.1.1:
|
vue-component-type-helpers@3.1.1:
|
||||||
resolution: {integrity: sha512-B0kHv7qX6E7+kdc5nsaqjdGZ1KwNKSUQDWGy7XkTYT7wFsOpkEyaJ1Vq79TjwrrtuLRgizrTV7PPuC4rRQo+vw==}
|
resolution: {integrity: sha512-B0kHv7qX6E7+kdc5nsaqjdGZ1KwNKSUQDWGy7XkTYT7wFsOpkEyaJ1Vq79TjwrrtuLRgizrTV7PPuC4rRQo+vw==}
|
||||||
|
|
||||||
vue-component-type-helpers@3.1.4:
|
vue-component-type-helpers@3.1.5:
|
||||||
resolution: {integrity: sha512-Uws7Ew1OzTTqHW8ZVl/qLl/HB+jf08M0NdFONbVWAx0N4gMLK8yfZDgeB77hDnBmaigWWEn5qP8T9BG59jIeyQ==}
|
resolution: {integrity: sha512-7V3yJuNWW7/1jxCcI1CswnpDsvs02Qcx/N43LkV+ZqhLj2PKj50slUflHAroNkN4UWiYfzMUUUXiNuv9khmSpQ==}
|
||||||
|
|
||||||
vue-demi@0.14.10:
|
vue-demi@0.14.10:
|
||||||
resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
|
resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
|
||||||
@@ -8969,6 +9006,8 @@ snapshots:
|
|||||||
|
|
||||||
'@babel/runtime@7.28.4': {}
|
'@babel/runtime@7.28.4': {}
|
||||||
|
|
||||||
|
'@babel/standalone@7.28.5': {}
|
||||||
|
|
||||||
'@babel/template@7.27.2':
|
'@babel/template@7.27.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/code-frame': 7.27.1
|
'@babel/code-frame': 7.27.1
|
||||||
@@ -10633,7 +10672,7 @@ snapshots:
|
|||||||
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
|
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
|
||||||
type-fest: 2.19.0
|
type-fest: 2.19.0
|
||||||
vue: 3.5.13(typescript@5.9.2)
|
vue: 3.5.13(typescript@5.9.2)
|
||||||
vue-component-type-helpers: 3.1.4
|
vue-component-type-helpers: 3.1.5
|
||||||
|
|
||||||
'@swc/helpers@0.5.17':
|
'@swc/helpers@0.5.17':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -11016,7 +11055,7 @@ snapshots:
|
|||||||
'@tweenjs/tween.js': 23.1.3
|
'@tweenjs/tween.js': 23.1.3
|
||||||
'@types/stats.js': 0.17.3
|
'@types/stats.js': 0.17.3
|
||||||
'@types/webxr': 0.5.20
|
'@types/webxr': 0.5.20
|
||||||
'@webgpu/types': 0.1.51
|
'@webgpu/types': 0.1.66
|
||||||
fflate: 0.8.2
|
fflate: 0.8.2
|
||||||
meshoptimizer: 0.18.1
|
meshoptimizer: 0.18.1
|
||||||
|
|
||||||
@@ -11519,7 +11558,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
vue: 3.5.13(typescript@5.9.2)
|
vue: 3.5.13(typescript@5.9.2)
|
||||||
|
|
||||||
'@webgpu/types@0.1.51': {}
|
'@webgpu/types@0.1.66': {}
|
||||||
|
|
||||||
'@xstate/fsm@1.6.5': {}
|
'@xstate/fsm@1.6.5': {}
|
||||||
|
|
||||||
@@ -14000,6 +14039,10 @@ snapshots:
|
|||||||
|
|
||||||
lz-string@1.5.0: {}
|
lz-string@1.5.0: {}
|
||||||
|
|
||||||
|
magic-string-ast@1.0.3:
|
||||||
|
dependencies:
|
||||||
|
magic-string: 0.30.19
|
||||||
|
|
||||||
magic-string@0.30.19:
|
magic-string@0.30.19:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
@@ -15864,6 +15907,12 @@ snapshots:
|
|||||||
|
|
||||||
tinybench@2.9.0: {}
|
tinybench@2.9.0: {}
|
||||||
|
|
||||||
|
tinyest-for-wgsl@0.1.3:
|
||||||
|
dependencies:
|
||||||
|
tinyest: 0.1.2
|
||||||
|
|
||||||
|
tinyest@0.1.2: {}
|
||||||
|
|
||||||
tinyexec@0.3.2: {}
|
tinyexec@0.3.2: {}
|
||||||
|
|
||||||
tinyexec@1.0.1: {}
|
tinyexec@1.0.1: {}
|
||||||
@@ -15995,6 +16044,13 @@ snapshots:
|
|||||||
reflect.getprototypeof: 1.0.10
|
reflect.getprototypeof: 1.0.10
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
typed-binary@4.3.2: {}
|
||||||
|
|
||||||
|
typegpu@0.8.2:
|
||||||
|
dependencies:
|
||||||
|
tinyest: 0.1.2
|
||||||
|
typed-binary: 4.3.2
|
||||||
|
|
||||||
typescript-eslint@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2):
|
typescript-eslint@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/eslint-plugin': 8.44.0(@typescript-eslint/parser@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2)
|
'@typescript-eslint/eslint-plugin': 8.44.0(@typescript-eslint/parser@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2)
|
||||||
@@ -16090,6 +16146,19 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
unplugin-typegpu@0.8.0(typegpu@0.8.2):
|
||||||
|
dependencies:
|
||||||
|
'@babel/standalone': 7.28.5
|
||||||
|
defu: 6.1.4
|
||||||
|
estree-walker: 3.0.3
|
||||||
|
magic-string-ast: 1.0.3
|
||||||
|
pathe: 2.0.3
|
||||||
|
picomatch: 4.0.3
|
||||||
|
tinyest: 0.1.2
|
||||||
|
tinyest-for-wgsl: 0.1.3
|
||||||
|
typegpu: 0.8.2
|
||||||
|
unplugin: 2.3.5
|
||||||
|
|
||||||
unplugin-vue-components@0.28.0(@babel/parser@7.28.4)(rollup@4.22.4)(vue@3.5.13(typescript@5.9.2)):
|
unplugin-vue-components@0.28.0(@babel/parser@7.28.4)(rollup@4.22.4)(vue@3.5.13(typescript@5.9.2)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@antfu/utils': 0.7.10
|
'@antfu/utils': 0.7.10
|
||||||
@@ -16370,7 +16439,7 @@ snapshots:
|
|||||||
|
|
||||||
vue-component-type-helpers@3.1.1: {}
|
vue-component-type-helpers@3.1.1: {}
|
||||||
|
|
||||||
vue-component-type-helpers@3.1.4: {}
|
vue-component-type-helpers@3.1.5: {}
|
||||||
|
|
||||||
vue-demi@0.14.10(vue@3.5.13(typescript@5.9.2)):
|
vue-demi@0.14.10(vue@3.5.13(typescript@5.9.2)):
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ catalog:
|
|||||||
'@vue/test-utils': ^2.4.6
|
'@vue/test-utils': ^2.4.6
|
||||||
'@vueuse/core': ^11.0.0
|
'@vueuse/core': ^11.0.0
|
||||||
'@vueuse/integrations': ^13.9.0
|
'@vueuse/integrations': ^13.9.0
|
||||||
|
'@webgpu/types': ^0.1.66
|
||||||
algoliasearch: ^5.21.0
|
algoliasearch: ^5.21.0
|
||||||
axios: ^1.8.2
|
axios: ^1.8.2
|
||||||
cross-env: ^10.1.0
|
cross-env: ^10.1.0
|
||||||
@@ -83,9 +84,11 @@ catalog:
|
|||||||
tailwindcss-primeui: ^0.6.1
|
tailwindcss-primeui: ^0.6.1
|
||||||
tsx: ^4.15.6
|
tsx: ^4.15.6
|
||||||
tw-animate-css: ^1.3.8
|
tw-animate-css: ^1.3.8
|
||||||
|
typegpu: ^0.8.2
|
||||||
typescript: ^5.9.2
|
typescript: ^5.9.2
|
||||||
typescript-eslint: ^8.44.0
|
typescript-eslint: ^8.44.0
|
||||||
unplugin-icons: ^0.22.0
|
unplugin-icons: ^0.22.0
|
||||||
|
unplugin-typegpu: 0.8.0
|
||||||
unplugin-vue-components: ^0.28.0
|
unplugin-vue-components: ^0.28.0
|
||||||
vite: ^5.4.19
|
vite: ^5.4.19
|
||||||
vite-plugin-dts: ^4.5.4
|
vite-plugin-dts: ^4.5.4
|
||||||
|
|||||||
@@ -1 +1,9 @@
|
|||||||
Thanks to OpenArt (https://openart.ai) for providing the sorted-custom-node-map data, captured in September 2024.
|
Node usage data merged from two sources:
|
||||||
|
- Mixpanel "app:node_search_result_selected" events (Nov 2025): 1,112 nodes, 46,514 selections
|
||||||
|
Reflects actual user search behavior - what users choose when searching.
|
||||||
|
- OpenArt workflow data (Sept 2024): 2,600 nodes, 118,676 uses
|
||||||
|
Reflects overall popularity - what's used in workflows.
|
||||||
|
|
||||||
|
Merge strategy: New data overwrites old for 514 overlapping nodes. Old data
|
||||||
|
normalized by 2.55x scale factor to match new data. Total: 3,198 nodes.
|
||||||
|
Search-selected nodes prioritized in ranking.
|
||||||
9
public/assets/images/civitai.svg
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
254
scripts/cicd/resolve-comfyui-release.ts
Executable file
@@ -0,0 +1,254 @@
|
|||||||
|
#!/usr/bin/env tsx
|
||||||
|
import { execSync } from 'child_process'
|
||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
interface ReleaseInfo {
|
||||||
|
current_version: string
|
||||||
|
target_minor: number
|
||||||
|
target_version: string
|
||||||
|
target_branch: string
|
||||||
|
needs_release: boolean
|
||||||
|
latest_patch_tag: string | null
|
||||||
|
branch_head_sha: string | null
|
||||||
|
tag_commit_sha: string | null
|
||||||
|
diff_url: string
|
||||||
|
release_pr_url: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a command and return stdout
|
||||||
|
*/
|
||||||
|
function exec(command: string, cwd?: string): string {
|
||||||
|
try {
|
||||||
|
return execSync(command, {
|
||||||
|
cwd,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
stdio: ['pipe', 'pipe', 'pipe']
|
||||||
|
}).trim()
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||||
|
const cwdInfo = cwd ? ` in directory: ${cwd}` : ''
|
||||||
|
console.error(
|
||||||
|
`Command failed: ${command}${cwdInfo}\nError: ${errorMessage}`
|
||||||
|
)
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse version from requirements.txt
|
||||||
|
* Handles formats: comfyui-frontend-package==1.2.3, comfyui-frontend-package>=1.2.3, etc.
|
||||||
|
*/
|
||||||
|
function parseRequirementsVersion(requirementsPath: string): string | null {
|
||||||
|
if (!fs.existsSync(requirementsPath)) {
|
||||||
|
console.error(`Requirements file not found: ${requirementsPath}`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = fs.readFileSync(requirementsPath, 'utf-8')
|
||||||
|
const match = content.match(
|
||||||
|
/comfyui-frontend-package\s*(?:==|>=|<=|~=|>|<)\s*([0-9]+\.[0-9]+\.[0-9]+)/
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
console.error(
|
||||||
|
'Could not find comfyui-frontend-package version in requirements.txt'
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return match[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate semantic version string
|
||||||
|
*/
|
||||||
|
function isValidSemver(version: string): boolean {
|
||||||
|
if (!version || typeof version !== 'string') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = version.split('.')
|
||||||
|
if (parts.length !== 3) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts.every((part) => {
|
||||||
|
const num = Number(part)
|
||||||
|
return Number.isFinite(num) && num >= 0 && String(num) === part
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the latest patch tag for a given minor version
|
||||||
|
*/
|
||||||
|
function getLatestPatchTag(repoPath: string, minor: number): string | null {
|
||||||
|
// Fetch all tags
|
||||||
|
exec('git fetch --tags', repoPath)
|
||||||
|
|
||||||
|
// Use git's native version sorting to get the latest tag
|
||||||
|
const latestTag = exec(
|
||||||
|
`git tag -l 'v1.${minor}.*' --sort=-version:refname | head -n 1`,
|
||||||
|
repoPath
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!latestTag) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the tag is a valid semver (vX.Y.Z format)
|
||||||
|
const validTagRegex = /^v\d+\.\d+\.\d+$/
|
||||||
|
if (!validTagRegex.test(latestTag)) {
|
||||||
|
console.error(
|
||||||
|
`Latest tag for minor version ${minor} is not valid semver: ${latestTag}`
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return latestTag
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the ComfyUI release information
|
||||||
|
*/
|
||||||
|
function resolveRelease(
|
||||||
|
comfyuiRepoPath: string,
|
||||||
|
frontendRepoPath: string
|
||||||
|
): ReleaseInfo | null {
|
||||||
|
// Parse current version from ComfyUI requirements.txt
|
||||||
|
const requirementsPath = path.join(comfyuiRepoPath, 'requirements.txt')
|
||||||
|
const currentVersion = parseRequirementsVersion(requirementsPath)
|
||||||
|
|
||||||
|
if (!currentVersion) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate version format
|
||||||
|
if (!isValidSemver(currentVersion)) {
|
||||||
|
console.error(
|
||||||
|
`Invalid semantic version format: ${currentVersion}. Expected format: X.Y.Z`
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const [major, currentMinor, patch] = currentVersion.split('.').map(Number)
|
||||||
|
|
||||||
|
// Calculate target minor version (next minor)
|
||||||
|
const targetMinor = currentMinor + 1
|
||||||
|
const targetBranch = `core/1.${targetMinor}`
|
||||||
|
|
||||||
|
// Check if target branch exists in frontend repo
|
||||||
|
exec('git fetch origin', frontendRepoPath)
|
||||||
|
const branchExists = exec(
|
||||||
|
`git rev-parse --verify origin/${targetBranch}`,
|
||||||
|
frontendRepoPath
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!branchExists) {
|
||||||
|
console.error(
|
||||||
|
`Target branch ${targetBranch} does not exist in frontend repo`
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get latest patch tag for target minor
|
||||||
|
const latestPatchTag = getLatestPatchTag(frontendRepoPath, targetMinor)
|
||||||
|
|
||||||
|
let needsRelease = false
|
||||||
|
let branchHeadSha: string | null = null
|
||||||
|
let tagCommitSha: string | null = null
|
||||||
|
let targetVersion = currentVersion
|
||||||
|
|
||||||
|
if (latestPatchTag) {
|
||||||
|
// Get commit SHA for the tag
|
||||||
|
tagCommitSha = exec(`git rev-list -n 1 ${latestPatchTag}`, frontendRepoPath)
|
||||||
|
|
||||||
|
// Get commit SHA for branch head
|
||||||
|
branchHeadSha = exec(
|
||||||
|
`git rev-parse origin/${targetBranch}`,
|
||||||
|
frontendRepoPath
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check if there are commits between tag and branch head
|
||||||
|
const commitsBetween = exec(
|
||||||
|
`git rev-list ${latestPatchTag}..origin/${targetBranch} --count`,
|
||||||
|
frontendRepoPath
|
||||||
|
)
|
||||||
|
|
||||||
|
const commitCount = parseInt(commitsBetween, 10)
|
||||||
|
needsRelease = !isNaN(commitCount) && commitCount > 0
|
||||||
|
|
||||||
|
// Parse existing patch number and increment if needed
|
||||||
|
const tagVersion = latestPatchTag.replace('v', '')
|
||||||
|
|
||||||
|
// Validate tag version format
|
||||||
|
if (!isValidSemver(tagVersion)) {
|
||||||
|
console.error(
|
||||||
|
`Invalid tag version format: ${tagVersion}. Expected format: X.Y.Z`
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const [, , existingPatch] = tagVersion.split('.').map(Number)
|
||||||
|
|
||||||
|
// Validate existingPatch is a valid number
|
||||||
|
if (!Number.isFinite(existingPatch) || existingPatch < 0) {
|
||||||
|
console.error(`Invalid patch number in tag: ${existingPatch}`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsRelease) {
|
||||||
|
targetVersion = `1.${targetMinor}.${existingPatch + 1}`
|
||||||
|
} else {
|
||||||
|
targetVersion = tagVersion
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No tags exist for this minor version, need to create v1.{targetMinor}.0
|
||||||
|
needsRelease = true
|
||||||
|
targetVersion = `1.${targetMinor}.0`
|
||||||
|
branchHeadSha = exec(
|
||||||
|
`git rev-parse origin/${targetBranch}`,
|
||||||
|
frontendRepoPath
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const diffUrl = `https://github.com/Comfy-Org/ComfyUI_frontend/compare/v${currentVersion}...v${targetVersion}`
|
||||||
|
|
||||||
|
return {
|
||||||
|
current_version: currentVersion,
|
||||||
|
target_minor: targetMinor,
|
||||||
|
target_version: targetVersion,
|
||||||
|
target_branch: targetBranch,
|
||||||
|
needs_release: needsRelease,
|
||||||
|
latest_patch_tag: latestPatchTag,
|
||||||
|
branch_head_sha: branchHeadSha,
|
||||||
|
tag_commit_sha: tagCommitSha,
|
||||||
|
diff_url: diffUrl,
|
||||||
|
release_pr_url: null // Will be populated by workflow if release is triggered
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main execution
|
||||||
|
const comfyuiRepoPath = process.argv[2]
|
||||||
|
const frontendRepoPath = process.argv[3] || process.cwd()
|
||||||
|
|
||||||
|
if (!comfyuiRepoPath) {
|
||||||
|
console.error(
|
||||||
|
'Usage: resolve-comfyui-release.ts <comfyui-repo-path> [frontend-repo-path]'
|
||||||
|
)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const releaseInfo = resolveRelease(comfyuiRepoPath, frontendRepoPath)
|
||||||
|
|
||||||
|
if (!releaseInfo) {
|
||||||
|
console.error('Failed to resolve release information')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output as JSON for GitHub Actions
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(JSON.stringify(releaseInfo, null, 2))
|
||||||
|
|
||||||
|
export { resolveRelease }
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
|
|
||||||
|
// Import Vite define shim to make __DISTRIBUTION__ and other define variables available
|
||||||
|
import './vite-define-shim'
|
||||||
|
|
||||||
import { DESKTOP_DIALOGS } from '../apps/desktop-ui/src/constants/desktopDialogs'
|
import { DESKTOP_DIALOGS } from '../apps/desktop-ui/src/constants/desktopDialogs'
|
||||||
import { comfyPageFixture as test } from '../browser_tests/fixtures/ComfyPage'
|
import { comfyPageFixture as test } from '../browser_tests/fixtures/ComfyPage'
|
||||||
import {
|
import {
|
||||||
|
|||||||
46
scripts/vite-define-shim.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* Shim for Vite define variables to make them available during Playwright test execution
|
||||||
|
* This file should be imported before any code that uses Vite define variables
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Define global constants that Vite would normally replace at build time
|
||||||
|
declare global {
|
||||||
|
const __COMFYUI_FRONTEND_VERSION__: string
|
||||||
|
const __SENTRY_ENABLED__: boolean
|
||||||
|
const __SENTRY_DSN__: string
|
||||||
|
const __ALGOLIA_APP_ID__: string
|
||||||
|
const __ALGOLIA_API_KEY__: string
|
||||||
|
const __USE_PROD_CONFIG__: boolean
|
||||||
|
const __DISTRIBUTION__: 'desktop' | 'localhost' | 'cloud'
|
||||||
|
}
|
||||||
|
|
||||||
|
type GlobalWithDefines = typeof globalThis & {
|
||||||
|
__COMFYUI_FRONTEND_VERSION__: string
|
||||||
|
__SENTRY_ENABLED__: boolean
|
||||||
|
__SENTRY_DSN__: string
|
||||||
|
__ALGOLIA_APP_ID__: string
|
||||||
|
__ALGOLIA_API_KEY__: string
|
||||||
|
__USE_PROD_CONFIG__: boolean
|
||||||
|
__DISTRIBUTION__: 'desktop' | 'localhost' | 'cloud'
|
||||||
|
window?: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
const globalWithDefines = globalThis as GlobalWithDefines
|
||||||
|
|
||||||
|
// Set default values for Playwright test environment
|
||||||
|
globalWithDefines.__COMFYUI_FRONTEND_VERSION__ =
|
||||||
|
process.env.npm_package_version || '1.0.0'
|
||||||
|
globalWithDefines.__SENTRY_ENABLED__ = false
|
||||||
|
globalWithDefines.__SENTRY_DSN__ = ''
|
||||||
|
globalWithDefines.__ALGOLIA_APP_ID__ = ''
|
||||||
|
globalWithDefines.__ALGOLIA_API_KEY__ = ''
|
||||||
|
globalWithDefines.__USE_PROD_CONFIG__ = false
|
||||||
|
globalWithDefines.__DISTRIBUTION__ = 'localhost'
|
||||||
|
|
||||||
|
// Provide a minimal window shim for Node environment
|
||||||
|
// This is needed for code that checks window existence during imports
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
globalWithDefines.window = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {}
|
||||||
@@ -14,10 +14,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Splitter
|
<Splitter
|
||||||
key="main-splitter-stable"
|
:key="splitterRefreshKey"
|
||||||
class="splitter-overlay flex-1 overflow-hidden"
|
class="splitter-overlay flex-1 overflow-hidden"
|
||||||
:pt:gutter="sidebarPanelVisible ? '' : 'hidden'"
|
:pt:gutter="getSplitterGutterClasses"
|
||||||
:state-key="sidebarStateKey || 'main-splitter'"
|
:state-key="sidebarStateKey"
|
||||||
state-storage="local"
|
state-storage="local"
|
||||||
>
|
>
|
||||||
<SplitterPanel
|
<SplitterPanel
|
||||||
@@ -80,6 +80,16 @@
|
|||||||
name="side-bar-panel"
|
name="side-bar-panel"
|
||||||
/>
|
/>
|
||||||
</SplitterPanel>
|
</SplitterPanel>
|
||||||
|
|
||||||
|
<!-- Right Side Panel - independent of sidebar -->
|
||||||
|
<SplitterPanel
|
||||||
|
v-if="rightSidePanelVisible"
|
||||||
|
class="right-side-panel pointer-events-auto"
|
||||||
|
:min-size="15"
|
||||||
|
:size="20"
|
||||||
|
>
|
||||||
|
<slot name="right-side-panel" />
|
||||||
|
</SplitterPanel>
|
||||||
</Splitter>
|
</Splitter>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -92,9 +102,11 @@ import { computed } from 'vue'
|
|||||||
|
|
||||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||||
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
|
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
|
||||||
|
import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'
|
||||||
import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore'
|
import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore'
|
||||||
|
|
||||||
const settingStore = useSettingStore()
|
const settingStore = useSettingStore()
|
||||||
|
const rightSidePanelStore = useRightSidePanelStore()
|
||||||
const sidebarLocation = computed<'left' | 'right'>(() =>
|
const sidebarLocation = computed<'left' | 'right'>(() =>
|
||||||
settingStore.get('Comfy.Sidebar.Location')
|
settingStore.get('Comfy.Sidebar.Location')
|
||||||
)
|
)
|
||||||
@@ -109,6 +121,7 @@ const sidebarPanelVisible = computed(
|
|||||||
const bottomPanelVisible = computed(
|
const bottomPanelVisible = computed(
|
||||||
() => useBottomPanelStore().bottomPanelVisible
|
() => useBottomPanelStore().bottomPanelVisible
|
||||||
)
|
)
|
||||||
|
const rightSidePanelVisible = computed(() => rightSidePanelStore.isOpen)
|
||||||
const activeSidebarTabId = computed(
|
const activeSidebarTabId = computed(
|
||||||
() => useSidebarTabStore().activeSidebarTabId
|
() => useSidebarTabStore().activeSidebarTabId
|
||||||
)
|
)
|
||||||
@@ -120,6 +133,21 @@ const sidebarStateKey = computed(() => {
|
|||||||
// When no tab is active, use a default key to maintain state
|
// When no tab is active, use a default key to maintain state
|
||||||
return activeSidebarTabId.value ?? 'default-sidebar'
|
return activeSidebarTabId.value ?? 'default-sidebar'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force refresh the splitter when right panel visibility changes to recalculate the width
|
||||||
|
*/
|
||||||
|
const splitterRefreshKey = computed(() => {
|
||||||
|
return rightSidePanelVisible.value
|
||||||
|
? 'main-splitter-with-right-panel'
|
||||||
|
: 'main-splitter'
|
||||||
|
})
|
||||||
|
|
||||||
|
// Gutter visibility should be controlled by CSS targeting specific gutters
|
||||||
|
const getSplitterGutterClasses = computed(() => {
|
||||||
|
// Empty string - let individual gutter styles handle visibility
|
||||||
|
return ''
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -135,10 +163,20 @@ const sidebarStateKey = computed(() => {
|
|||||||
background-color: var(--p-primary-color);
|
background-color: var(--p-primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Hide sidebar gutter when sidebar is not visible */
|
||||||
|
:deep(.side-bar-panel[style*='display: none'] + .p-splitter-gutter),
|
||||||
|
:deep(.p-splitter-gutter + .side-bar-panel[style*='display: none']) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.side-bar-panel {
|
.side-bar-panel {
|
||||||
background-color: var(--bg-color);
|
background-color: var(--bg-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.right-side-panel {
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
.bottom-panel {
|
.bottom-panel {
|
||||||
background-color: var(--comfy-menu-bg);
|
background-color: var(--comfy-menu-bg);
|
||||||
border: 1px solid var(--p-panel-border-color);
|
border: 1px solid var(--p-panel-border-color);
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="!workspaceStore.focusMode" class="ml-1 flex gap-x-0.5 pt-1">
|
<div
|
||||||
|
v-if="!workspaceStore.focusMode"
|
||||||
|
class="ml-1 flex gap-x-0.5 pt-1"
|
||||||
|
@mouseenter="isTopMenuHovered = true"
|
||||||
|
@mouseleave="isTopMenuHovered = false"
|
||||||
|
>
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
<SubgraphBreadcrumb />
|
<SubgraphBreadcrumb />
|
||||||
</div>
|
</div>
|
||||||
@@ -39,8 +44,25 @@
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
<CurrentUserButton v-if="isLoggedIn" class="shrink-0" />
|
<CurrentUserButton v-if="isLoggedIn" class="shrink-0" />
|
||||||
<LoginButton v-else-if="isDesktop" />
|
<LoginButton v-else-if="isDesktop" />
|
||||||
|
<IconButton
|
||||||
|
v-if="!isRightSidePanelOpen"
|
||||||
|
v-tooltip.bottom="rightSidePanelTooltipConfig"
|
||||||
|
type="transparent"
|
||||||
|
size="sm"
|
||||||
|
class="mr-2 transition-colors duration-200 ease-in-out hover:bg-secondary-background-hover focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-background"
|
||||||
|
:aria-pressed="isRightSidePanelOpen"
|
||||||
|
:aria-label="t('rightSidePanel.togglePanel')"
|
||||||
|
@click="toggleRightSidePanel"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="icon-[lucide--panel-right] block size-4 text-muted-foreground"
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
<QueueProgressOverlay v-model:expanded="isQueueOverlayExpanded" />
|
<QueueProgressOverlay
|
||||||
|
v-model:expanded="isQueueOverlayExpanded"
|
||||||
|
:menu-hovered="isTopMenuHovered"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -60,15 +82,18 @@ import { useCurrentUser } from '@/composables/auth/useCurrentUser'
|
|||||||
import { buildTooltipConfig } from '@/composables/useTooltipConfig'
|
import { buildTooltipConfig } from '@/composables/useTooltipConfig'
|
||||||
import { app } from '@/scripts/app'
|
import { app } from '@/scripts/app'
|
||||||
import { useQueueStore } from '@/stores/queueStore'
|
import { useQueueStore } from '@/stores/queueStore'
|
||||||
|
import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'
|
||||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||||
import { isElectron } from '@/utils/envUtil'
|
import { isElectron } from '@/utils/envUtil'
|
||||||
|
|
||||||
const workspaceStore = useWorkspaceStore()
|
const workspaceStore = useWorkspaceStore()
|
||||||
|
const rightSidePanelStore = useRightSidePanelStore()
|
||||||
const { isLoggedIn } = useCurrentUser()
|
const { isLoggedIn } = useCurrentUser()
|
||||||
const isDesktop = isElectron()
|
const isDesktop = isElectron()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const isQueueOverlayExpanded = ref(false)
|
const isQueueOverlayExpanded = ref(false)
|
||||||
const queueStore = useQueueStore()
|
const queueStore = useQueueStore()
|
||||||
|
const isTopMenuHovered = ref(false)
|
||||||
const queuedCount = computed(() => queueStore.pendingTasks.length)
|
const queuedCount = computed(() => queueStore.pendingTasks.length)
|
||||||
const queueHistoryTooltipConfig = computed(() =>
|
const queueHistoryTooltipConfig = computed(() =>
|
||||||
buildTooltipConfig(t('sideToolbar.queueProgressOverlay.viewJobHistory'))
|
buildTooltipConfig(t('sideToolbar.queueProgressOverlay.viewJobHistory'))
|
||||||
@@ -79,6 +104,16 @@ const queueHistoryButtonBackgroundClass = computed(() =>
|
|||||||
: 'bg-secondary-background'
|
: 'bg-secondary-background'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Right side panel toggle
|
||||||
|
const isRightSidePanelOpen = computed(() => rightSidePanelStore.isOpen)
|
||||||
|
const rightSidePanelTooltipConfig = computed(() =>
|
||||||
|
buildTooltipConfig(t('rightSidePanel.togglePanel'))
|
||||||
|
)
|
||||||
|
|
||||||
|
const toggleRightSidePanel = () => {
|
||||||
|
rightSidePanelStore.togglePanel()
|
||||||
|
}
|
||||||
|
|
||||||
// Maintain support for legacy topbar elements attached by custom scripts
|
// Maintain support for legacy topbar elements attached by custom scripts
|
||||||
const legacyCommandsContainerRef = ref<HTMLElement>()
|
const legacyCommandsContainerRef = ref<HTMLElement>()
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Panel
|
<Panel
|
||||||
class="pointer-events-auto z-1010"
|
class="pointer-events-auto"
|
||||||
:style="style"
|
:style="style"
|
||||||
:class="panelClass"
|
:class="panelClass"
|
||||||
:pt="{
|
:pt="{
|
||||||
@@ -66,12 +66,7 @@ const storedPosition = useLocalStorage('Comfy.MenuPosition.Floating', {
|
|||||||
x: 0,
|
x: 0,
|
||||||
y: 0
|
y: 0
|
||||||
})
|
})
|
||||||
const {
|
const { x, y, style, isDragging } = useDraggable(panelRef, {
|
||||||
x,
|
|
||||||
y,
|
|
||||||
style: style,
|
|
||||||
isDragging
|
|
||||||
} = useDraggable(panelRef, {
|
|
||||||
initialValue: { x: 0, y: 0 },
|
initialValue: { x: 0, y: 0 },
|
||||||
handle: dragHandleRef,
|
handle: dragHandleRef,
|
||||||
containerElement: document.body,
|
containerElement: document.body,
|
||||||
@@ -257,7 +252,7 @@ watch(isDragging, (dragging) => {
|
|||||||
})
|
})
|
||||||
const actionbarClass = computed(() =>
|
const actionbarClass = computed(() =>
|
||||||
cn(
|
cn(
|
||||||
'w-[265px] border-dashed border-blue-500 opacity-80',
|
'w-[200px] border-dashed border-blue-500 opacity-80',
|
||||||
'm-1.5 flex items-center justify-center self-stretch',
|
'm-1.5 flex items-center justify-center self-stretch',
|
||||||
'rounded-md before:w-50 before:-ml-50 before:h-full',
|
'rounded-md before:w-50 before:-ml-50 before:h-full',
|
||||||
'pointer-events-auto',
|
'pointer-events-auto',
|
||||||
@@ -267,7 +262,7 @@ const actionbarClass = computed(() =>
|
|||||||
)
|
)
|
||||||
const panelClass = computed(() =>
|
const panelClass = computed(() =>
|
||||||
cn(
|
cn(
|
||||||
'actionbar pointer-events-auto z1000',
|
'actionbar pointer-events-auto z-1300',
|
||||||
isDragging.value && 'select-none pointer-events-none',
|
isDragging.value && 'select-none pointer-events-none',
|
||||||
isDocked.value
|
isDocked.value
|
||||||
? 'p-0 static mr-2 border-none bg-transparent'
|
? 'p-0 static mr-2 border-none bg-transparent'
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
severity="primary"
|
severity="primary"
|
||||||
size="small"
|
size="small"
|
||||||
:model="queueModeMenuItems"
|
:model="queueModeMenuItems"
|
||||||
:disabled="hasMissingNodes"
|
|
||||||
data-testid="queue-button"
|
data-testid="queue-button"
|
||||||
@click="queuePrompt"
|
@click="queuePrompt"
|
||||||
>
|
>
|
||||||
@@ -45,17 +44,22 @@ import { useI18n } from 'vue-i18n'
|
|||||||
|
|
||||||
import { isCloud } from '@/platform/distribution/types'
|
import { isCloud } from '@/platform/distribution/types'
|
||||||
import { useTelemetry } from '@/platform/telemetry'
|
import { useTelemetry } from '@/platform/telemetry'
|
||||||
|
import { app } from '@/scripts/app'
|
||||||
import { useCommandStore } from '@/stores/commandStore'
|
import { useCommandStore } from '@/stores/commandStore'
|
||||||
|
import { useNodeDefStore } from '@/stores/nodeDefStore'
|
||||||
import { useQueueSettingsStore } from '@/stores/queueStore'
|
import { useQueueSettingsStore } from '@/stores/queueStore'
|
||||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||||
import { useMissingNodes } from '@/workbench/extensions/manager/composables/nodePack/useMissingNodes'
|
import { graphHasMissingNodes } from '@/workbench/extensions/manager/utils/graphHasMissingNodes'
|
||||||
|
|
||||||
import BatchCountEdit from '../BatchCountEdit.vue'
|
import BatchCountEdit from '../BatchCountEdit.vue'
|
||||||
|
|
||||||
const workspaceStore = useWorkspaceStore()
|
const workspaceStore = useWorkspaceStore()
|
||||||
const { mode: queueMode, batchCount } = storeToRefs(useQueueSettingsStore())
|
const { mode: queueMode, batchCount } = storeToRefs(useQueueSettingsStore())
|
||||||
|
|
||||||
const { hasMissingNodes } = useMissingNodes()
|
const nodeDefStore = useNodeDefStore()
|
||||||
|
const hasMissingNodes = computed(() =>
|
||||||
|
graphHasMissingNodes(app.graph, nodeDefStore.nodeDefsByName)
|
||||||
|
)
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const queueModeMenuItemLookup = computed(() => {
|
const queueModeMenuItemLookup = computed(() => {
|
||||||
|
|||||||
@@ -64,11 +64,13 @@ import {
|
|||||||
ComfyWorkflow,
|
ComfyWorkflow,
|
||||||
useWorkflowStore
|
useWorkflowStore
|
||||||
} from '@/platform/workflow/management/stores/workflowStore'
|
} from '@/platform/workflow/management/stores/workflowStore'
|
||||||
|
import { app } from '@/scripts/app'
|
||||||
import { useDialogService } from '@/services/dialogService'
|
import { useDialogService } from '@/services/dialogService'
|
||||||
import { useCommandStore } from '@/stores/commandStore'
|
import { useCommandStore } from '@/stores/commandStore'
|
||||||
|
import { useNodeDefStore } from '@/stores/nodeDefStore'
|
||||||
import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore'
|
import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore'
|
||||||
import { appendJsonExt } from '@/utils/formatUtil'
|
import { appendJsonExt } from '@/utils/formatUtil'
|
||||||
import { useMissingNodes } from '@/workbench/extensions/manager/composables/nodePack/useMissingNodes'
|
import { graphHasMissingNodes } from '@/workbench/extensions/manager/utils/graphHasMissingNodes'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
item: MenuItem
|
item: MenuItem
|
||||||
@@ -79,7 +81,10 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
isActive: false
|
isActive: false
|
||||||
})
|
})
|
||||||
|
|
||||||
const { hasMissingNodes } = useMissingNodes()
|
const nodeDefStore = useNodeDefStore()
|
||||||
|
const hasMissingNodes = computed(() =>
|
||||||
|
graphHasMissingNodes(app.graph, nodeDefStore.nodeDefsByName)
|
||||||
|
)
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const menu = ref<InstanceType<typeof Menu> & MenuState>()
|
const menu = ref<InstanceType<typeof Menu> & MenuState>()
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import {
|
|||||||
import { cn } from '@/utils/tailwindUtil'
|
import { cn } from '@/utils/tailwindUtil'
|
||||||
|
|
||||||
interface IconButtonProps extends BaseButtonProps {
|
interface IconButtonProps extends BaseButtonProps {
|
||||||
onClick: (event: Event) => void
|
onClick?: (event: MouseEvent) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :class="iconGroupClasses">
|
<div
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'flex justify-center items-center shrink-0 outline-hidden border-none p-0 rounded-lg bg-secondary-background shadow-sm transition-all duration-200 cursor-pointer'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { cn } from '@/utils/tailwindUtil'
|
import { cn } from '@/utils/tailwindUtil'
|
||||||
|
|
||||||
const iconGroupClasses = cn(
|
|
||||||
'flex justify-center items-center shrink-0',
|
|
||||||
'outline-hidden border-none p-0 rounded-lg',
|
|
||||||
'bg-secondary-background shadow-sm',
|
|
||||||
'transition-all duration-200',
|
|
||||||
'cursor-pointer'
|
|
||||||
)
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ const {
|
|||||||
} = defineProps<IconTextButtonProps>()
|
} = defineProps<IconTextButtonProps>()
|
||||||
|
|
||||||
const buttonStyle = computed(() => {
|
const buttonStyle = computed(() => {
|
||||||
const baseClasses = `${getBaseButtonClasses()} justify-start! gap-2`
|
const baseClasses = `${getBaseButtonClasses()} justify-start gap-2`
|
||||||
const sizeClasses = getButtonSizeClasses(size)
|
const sizeClasses = getButtonSizeClasses(size)
|
||||||
const typeClasses = border
|
const typeClasses = border
|
||||||
? getBorderButtonTypeClasses(type)
|
? getBorderButtonTypeClasses(type)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="relative inline-flex items-center">
|
<div class="relative inline-flex items-center">
|
||||||
<IconButton :size="size" :type="type" @click="toggle">
|
<IconButton :size="size" :type="type" @click="popover?.toggle">
|
||||||
<i v-if="!isVertical" class="icon-[lucide--ellipsis] text-sm" />
|
<i v-if="!isVertical" class="icon-[lucide--ellipsis] text-sm" />
|
||||||
<i v-else class="icon-[lucide--more-vertical] text-sm" />
|
<i v-else class="icon-[lucide--more-vertical] text-sm" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@@ -25,8 +25,18 @@
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@show="$emit('menuOpened')"
|
@show="
|
||||||
@hide="$emit('menuClosed')"
|
() => {
|
||||||
|
isOpen = true
|
||||||
|
$emit('menuOpened')
|
||||||
|
}
|
||||||
|
"
|
||||||
|
@hide="
|
||||||
|
() => {
|
||||||
|
isOpen = false
|
||||||
|
$emit('menuClosed')
|
||||||
|
}
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<div class="flex min-w-40 flex-col gap-2 p-2">
|
<div class="flex min-w-40 flex-col gap-2 p-2">
|
||||||
<slot :close="hide" />
|
<slot :close="hide" />
|
||||||
@@ -48,8 +58,6 @@ interface MoreButtonProps extends BaseButtonProps {
|
|||||||
isVertical?: boolean
|
isVertical?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const popover = ref<InstanceType<typeof Popover>>()
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
size = 'md',
|
size = 'md',
|
||||||
type = 'secondary',
|
type = 'secondary',
|
||||||
@@ -61,11 +69,15 @@ defineEmits<{
|
|||||||
menuClosed: []
|
menuClosed: []
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const toggle = (event: Event) => {
|
const isOpen = ref(false)
|
||||||
popover.value?.toggle(event)
|
const popover = ref<InstanceType<typeof Popover>>()
|
||||||
}
|
|
||||||
|
|
||||||
const hide = () => {
|
function hide() {
|
||||||
popover.value?.hide()
|
popover.value?.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
hide,
|
||||||
|
isOpen
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -18,8 +18,8 @@
|
|||||||
...inputAttrs
|
...inputAttrs
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@keyup.enter="blurInputElement"
|
@keyup.enter.capture.stop="blurInputElement"
|
||||||
@keyup.escape="cancelEditing"
|
@keyup.escape.stop="cancelEditing"
|
||||||
@click.stop
|
@click.stop
|
||||||
@pointerdown.stop.capture
|
@pointerdown.stop.capture
|
||||||
@pointermove.stop.capture
|
@pointermove.stop.capture
|
||||||
@@ -38,7 +38,7 @@ const {
|
|||||||
} = defineProps<{
|
} = defineProps<{
|
||||||
modelValue: string
|
modelValue: string
|
||||||
isEditing?: boolean
|
isEditing?: boolean
|
||||||
inputAttrs?: Record<string, any>
|
inputAttrs?: Record<string, string>
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue', 'edit', 'cancel'])
|
const emit = defineEmits(['update:modelValue', 'edit', 'cancel'])
|
||||||
|
|||||||
@@ -9,29 +9,31 @@
|
|||||||
<div class="font-medium">
|
<div class="font-medium">
|
||||||
{{ col.header }}
|
{{ col.header }}
|
||||||
</div>
|
</div>
|
||||||
<div>{{ formatValue(systemInfo[col.field], col.field) }}</div>
|
<div>{{ getDisplayValue(col) }}</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Divider />
|
<template v-if="hasDevices">
|
||||||
|
<Divider />
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2 class="mb-4 text-2xl font-semibold">
|
<h2 class="mb-4 text-2xl font-semibold">
|
||||||
{{ $t('g.devices') }}
|
{{ $t('g.devices') }}
|
||||||
</h2>
|
</h2>
|
||||||
<TabView v-if="props.stats.devices.length > 1">
|
<TabView v-if="props.stats.devices.length > 1">
|
||||||
<TabPanel
|
<TabPanel
|
||||||
v-for="device in props.stats.devices"
|
v-for="device in props.stats.devices"
|
||||||
:key="device.index"
|
:key="device.index"
|
||||||
:header="device.name"
|
:header="device.name"
|
||||||
:value="device.index"
|
:value="device.index"
|
||||||
>
|
>
|
||||||
<DeviceInfo :device="device" />
|
<DeviceInfo :device="device" />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabView>
|
</TabView>
|
||||||
<DeviceInfo v-else :device="props.stats.devices[0]" />
|
<DeviceInfo v-else :device="props.stats.devices[0]" />
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -42,8 +44,9 @@ import TabView from 'primevue/tabview'
|
|||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
import DeviceInfo from '@/components/common/DeviceInfo.vue'
|
import DeviceInfo from '@/components/common/DeviceInfo.vue'
|
||||||
|
import { isCloud } from '@/platform/distribution/types'
|
||||||
import type { SystemStats } from '@/schemas/apiSchema'
|
import type { SystemStats } from '@/schemas/apiSchema'
|
||||||
import { formatSize } from '@/utils/formatUtil'
|
import { formatCommitHash, formatSize } from '@/utils/formatUtil'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
stats: SystemStats
|
stats: SystemStats
|
||||||
@@ -54,20 +57,53 @@ const systemInfo = computed(() => ({
|
|||||||
argv: props.stats.system.argv.join(' ')
|
argv: props.stats.system.argv.join(' ')
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const systemColumns: { field: keyof SystemStats['system']; header: string }[] =
|
const hasDevices = computed(() => props.stats.devices.length > 0)
|
||||||
[
|
|
||||||
{ field: 'os', header: 'OS' },
|
|
||||||
{ field: 'python_version', header: 'Python Version' },
|
|
||||||
{ field: 'embedded_python', header: 'Embedded Python' },
|
|
||||||
{ field: 'pytorch_version', header: 'Pytorch Version' },
|
|
||||||
{ field: 'argv', header: 'Arguments' },
|
|
||||||
{ field: 'ram_total', header: 'RAM Total' },
|
|
||||||
{ field: 'ram_free', header: 'RAM Free' }
|
|
||||||
]
|
|
||||||
|
|
||||||
const formatValue = (value: any, field: string) => {
|
type SystemInfoKey = keyof SystemStats['system']
|
||||||
if (['ram_total', 'ram_free'].includes(field)) {
|
|
||||||
return formatSize(value)
|
type ColumnDef = {
|
||||||
|
field: SystemInfoKey
|
||||||
|
header: string
|
||||||
|
format?: (value: string) => string
|
||||||
|
formatNumber?: (value: number) => string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Columns for local distribution */
|
||||||
|
const localColumns: ColumnDef[] = [
|
||||||
|
{ field: 'os', header: 'OS' },
|
||||||
|
{ field: 'python_version', header: 'Python Version' },
|
||||||
|
{ field: 'embedded_python', header: 'Embedded Python' },
|
||||||
|
{ field: 'pytorch_version', header: 'Pytorch Version' },
|
||||||
|
{ field: 'argv', header: 'Arguments' },
|
||||||
|
{ field: 'ram_total', header: 'RAM Total', formatNumber: formatSize },
|
||||||
|
{ field: 'ram_free', header: 'RAM Free', formatNumber: formatSize }
|
||||||
|
]
|
||||||
|
|
||||||
|
/** Columns for cloud distribution */
|
||||||
|
const cloudColumns: ColumnDef[] = [
|
||||||
|
{ field: 'cloud_version', header: 'Cloud Version' },
|
||||||
|
{
|
||||||
|
field: 'comfyui_version',
|
||||||
|
header: 'ComfyUI Version',
|
||||||
|
format: formatCommitHash
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'comfyui_frontend_version',
|
||||||
|
header: 'Frontend Version',
|
||||||
|
format: formatCommitHash
|
||||||
|
},
|
||||||
|
{ field: 'workflow_templates_version', header: 'Templates Version' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const systemColumns = computed(() => (isCloud ? cloudColumns : localColumns))
|
||||||
|
|
||||||
|
const getDisplayValue = (column: ColumnDef) => {
|
||||||
|
const value = systemInfo.value[column.field]
|
||||||
|
if (column.formatNumber && typeof value === 'number') {
|
||||||
|
return column.formatNumber(value)
|
||||||
|
}
|
||||||
|
if (column.format && typeof value === 'string') {
|
||||||
|
return column.format(value)
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ import { ValidationState } from '@/utils/validationUtil'
|
|||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: string
|
modelValue: string
|
||||||
validateUrlFn?: (url: string) => Promise<boolean>
|
validateUrlFn?: (url: string) => Promise<boolean>
|
||||||
disableValidation?: boolean
|
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -102,8 +101,6 @@ const defaultValidateUrl = async (url: string): Promise<boolean> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const validateUrl = async (value: string) => {
|
const validateUrl = async (value: string) => {
|
||||||
if (props.disableValidation) return
|
|
||||||
|
|
||||||
if (validationState.value === ValidationState.LOADING) return
|
if (validationState.value === ValidationState.LOADING) return
|
||||||
|
|
||||||
const url = cleanInput(value)
|
const url = cleanInput(value)
|
||||||
|
|||||||
@@ -92,7 +92,7 @@
|
|||||||
class="w-62.5"
|
class="w-62.5"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<i class="icon-[lucide--arrow-up-down]" />
|
<i class="icon-[lucide--arrow-up-down] text-muted-foreground" />
|
||||||
</template>
|
</template>
|
||||||
</SingleSelect>
|
</SingleSelect>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
<component
|
<component
|
||||||
:is="item.headerComponent"
|
:is="item.headerComponent"
|
||||||
v-if="item.headerComponent"
|
v-if="item.headerComponent"
|
||||||
|
v-bind="item.headerProps"
|
||||||
:id="item.key"
|
:id="item.key"
|
||||||
/>
|
/>
|
||||||
<h3 v-else :id="item.key">
|
<h3 v-else :id="item.key">
|
||||||
|
|||||||
19
src/components/dialog/confirm/ConfirmBody.vue
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="flex flex-col px-4 py-2 text-sm text-muted-foreground border-t border-border-default"
|
||||||
|
>
|
||||||
|
<p v-if="promptTextReal">
|
||||||
|
{{ promptTextReal }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, toValue } from 'vue'
|
||||||
|
import type { MaybeRefOrGetter } from 'vue'
|
||||||
|
|
||||||
|
const { promptText } = defineProps<{
|
||||||
|
promptText?: MaybeRefOrGetter<string>
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const promptTextReal = computed(() => toValue(promptText))
|
||||||
|
</script>
|
||||||
43
src/components/dialog/confirm/ConfirmFooter.vue
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<template>
|
||||||
|
<section class="w-full flex gap-2 justify-end px-2 pb-2">
|
||||||
|
<TextButton
|
||||||
|
:label="cancelTextX"
|
||||||
|
:disabled
|
||||||
|
type="transparent"
|
||||||
|
autofocus
|
||||||
|
@click="$emit('cancel')"
|
||||||
|
/>
|
||||||
|
<TextButton
|
||||||
|
:label="confirmTextX"
|
||||||
|
:disabled
|
||||||
|
type="transparent"
|
||||||
|
:class="confirmClass"
|
||||||
|
@click="$emit('confirm')"
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, toValue } from 'vue'
|
||||||
|
import type { MaybeRefOrGetter } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
import TextButton from '@/components/button/TextButton.vue'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const { cancelText, confirmText, confirmClass, optionsDisabled } = defineProps<{
|
||||||
|
cancelText?: string
|
||||||
|
confirmText?: string
|
||||||
|
confirmClass?: string
|
||||||
|
optionsDisabled?: MaybeRefOrGetter<boolean>
|
||||||
|
}>()
|
||||||
|
|
||||||
|
defineEmits<{
|
||||||
|
cancel: []
|
||||||
|
confirm: []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const confirmTextX = computed(() => confirmText || t('g.confirm'))
|
||||||
|
const cancelTextX = computed(() => cancelText || t('g.cancel'))
|
||||||
|
const disabled = computed(() => toValue(optionsDisabled))
|
||||||
|
</script>
|
||||||
12
src/components/dialog/confirm/ConfirmHeader.vue
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="flex items-center gap-2 p-4 font-bold text-sm text-base-foreground font-inter"
|
||||||
|
>
|
||||||
|
<span v-if="title" class="flex-auto">{{ title }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
title?: string
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
31
src/components/dialog/confirm/confirmDialog.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import ConfirmBody from '@/components/dialog/confirm/ConfirmBody.vue'
|
||||||
|
import ConfirmFooter from '@/components/dialog/confirm/ConfirmFooter.vue'
|
||||||
|
import ConfirmHeader from '@/components/dialog/confirm/ConfirmHeader.vue'
|
||||||
|
import { useDialogStore } from '@/stores/dialogStore'
|
||||||
|
import type { ComponentAttrs } from 'vue-component-type-helpers'
|
||||||
|
|
||||||
|
interface ConfirmDialogOptions {
|
||||||
|
headerProps?: ComponentAttrs<typeof ConfirmHeader>
|
||||||
|
props?: ComponentAttrs<typeof ConfirmBody>
|
||||||
|
footerProps?: ComponentAttrs<typeof ConfirmFooter>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function showConfirmDialog(options: ConfirmDialogOptions = {}) {
|
||||||
|
const dialogStore = useDialogStore()
|
||||||
|
const { headerProps, props, footerProps } = options
|
||||||
|
return dialogStore.showDialog({
|
||||||
|
headerComponent: ConfirmHeader,
|
||||||
|
component: ConfirmBody,
|
||||||
|
footerComponent: ConfirmFooter,
|
||||||
|
headerProps,
|
||||||
|
props,
|
||||||
|
footerProps,
|
||||||
|
dialogComponentProps: {
|
||||||
|
pt: {
|
||||||
|
header: 'py-0! px-0!',
|
||||||
|
content: 'p-0!',
|
||||||
|
footer: 'p-0!'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||