Merge main into vue-node/style/improvements

Update semantic token usage to match current design system conventions:
- Use bg-component-node-widget-background (main's naming)
- Use text-base-foreground for primary text (main's convention)
- Use text-secondary for icons and secondary text (semantic token)
- Use bg-interface-stroke and bg-button-icon (semantic tokens)
- Use hover:bg-interface-menu-component-surface-hovered
- Use bg-interface-menu-component-surface-selected

This preserves the PR's intent of migrating to semantic tokens while
adopting the evolved token names from main.
This commit is contained in:
bymyself
2025-11-05 13:38:17 -07:00
440 changed files with 40866 additions and 27542 deletions

View File

@@ -23,10 +23,10 @@ TEST_COMFYUI_DIR=/home/ComfyUI
# Whether to enable minification of the frontend code. # Whether to enable minification of the frontend code.
ENABLE_MINIFY=true ENABLE_MINIFY=true
# Whether to disable proxying the `/templates` route. If true, allows you to # Whether to disable proxying the `/templates` route. If true, allows you to
# serve templates from the ComfyUI_frontend/public/templates folder (for # serve templates from the ComfyUI_frontend/public/templates folder (for
# locally testing changes to templates). When false or nonexistent, the # locally testing changes to templates). When false or nonexistent, the
# templates are served via the normal method from the server's python site # templates are served via the normal method from the server's python site
# packages. # packages.
DISABLE_TEMPLATES_PROXY=false DISABLE_TEMPLATES_PROXY=false
@@ -37,3 +37,8 @@ DISABLE_VUE_PLUGINS=false
# Algolia credentials required for developing with the new custom node manager. # Algolia credentials required for developing with the new custom node manager.
ALGOLIA_APP_ID=4E0RO38HS8 ALGOLIA_APP_ID=4E0RO38HS8
ALGOLIA_API_KEY=684d998c36b67a9a9fce8fc2d8860579 ALGOLIA_API_KEY=684d998c36b67a9a9fce8fc2d8860579
# Sentry ENV vars replace with real ones for debugging
# SENTRY_AUTH_TOKEN=private-token # get from sentry
# SENTRY_ORG=comfy-org
# SENTRY_PROJECT=cloud-frontend-staging

View File

@@ -25,3 +25,6 @@ e3bb29ceb8174b8bbca9e48ec7d42cd540f40efa
# [refactor] Improve updates/notifications domain organization (#5590) # [refactor] Improve updates/notifications domain organization (#5590)
27ab355f9c73415dc39f4d3f512b02308f847801 27ab355f9c73415dc39f4d3f512b02308f847801
# Migrate Tailwind styles to design-system package
9f19d8fb4bd22518879343b49c05634dca777df0

1
.gitattributes vendored
View File

@@ -7,6 +7,7 @@
*.json text eol=lf *.json text eol=lf
*.mjs text eol=lf *.mjs text eol=lf
*.mts text eol=lf *.mts text eol=lf
*.snap text eol=lf
*.ts text eol=lf *.ts text eol=lf
*.vue text eol=lf *.vue text eol=lf
*.yaml text eol=lf *.yaml text eol=lf

View File

@@ -69,34 +69,7 @@ jobs:
git config user.name "github-actions[bot]" git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com" git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Check if backports already exist
id: check-existing
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.pull_request.number }}
run: |
# Check for existing backport PRs for this PR number
EXISTING_BACKPORTS=$(gh pr list --state all --search "backport-${PR_NUMBER}-to" --json title,headRefName,baseRefName | jq -r '.[].headRefName')
if [ -z "$EXISTING_BACKPORTS" ]; then
echo "skip=false" >> $GITHUB_OUTPUT
exit 0
fi
# For manual triggers with force_rerun, proceed anyway
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.force_rerun }}" = "true" ]; then
echo "skip=false" >> $GITHUB_OUTPUT
echo "::warning::Force rerun requested - existing backports will be updated"
exit 0
fi
echo "Found existing backport PRs:"
echo "$EXISTING_BACKPORTS"
echo "skip=true" >> $GITHUB_OUTPUT
echo "::warning::Backport PRs already exist for PR #${PR_NUMBER}, skipping to avoid duplicates"
- name: Collect backport targets - name: Collect backport targets
if: steps.check-existing.outputs.skip != 'true'
id: targets id: targets
run: | run: |
TARGETS=() TARGETS=()
@@ -138,6 +111,14 @@ jobs:
add_target "$label" "${BASH_REMATCH[1]}" add_target "$label" "${BASH_REMATCH[1]}"
elif [[ "$label" =~ ^backport:(.+)$ ]]; then elif [[ "$label" =~ ^backport:(.+)$ ]]; then
add_target "$label" "${BASH_REMATCH[1]}" add_target "$label" "${BASH_REMATCH[1]}"
elif [[ "$label" =~ ^core\/([0-9]+)\.([0-9]+)$ ]]; then
SAFE_MAJOR="${BASH_REMATCH[1]}"
SAFE_MINOR="${BASH_REMATCH[2]}"
add_target "$label" "core/${SAFE_MAJOR}.${SAFE_MINOR}"
elif [[ "$label" =~ ^cloud\/([0-9]+)\.([0-9]+)$ ]]; then
SAFE_MAJOR="${BASH_REMATCH[1]}"
SAFE_MINOR="${BASH_REMATCH[2]}"
add_target "$label" "cloud/${SAFE_MAJOR}.${SAFE_MINOR}"
elif [[ "$label" =~ ^[0-9]+\.[0-9]+$ ]]; then elif [[ "$label" =~ ^[0-9]+\.[0-9]+$ ]]; then
add_target "$label" "core/${label}" add_target "$label" "core/${label}"
fi fi
@@ -151,8 +132,76 @@ jobs:
echo "targets=${TARGETS[*]}" >> $GITHUB_OUTPUT echo "targets=${TARGETS[*]}" >> $GITHUB_OUTPUT
echo "Found backport targets: ${TARGETS[*]}" echo "Found backport targets: ${TARGETS[*]}"
- name: Filter already backported targets
id: filter-targets
env:
EVENT_NAME: ${{ github.event_name }}
FORCE_RERUN_INPUT: >-
${{ github.event_name == 'workflow_dispatch' && inputs.force_rerun
|| 'false' }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: >-
${{ github.event_name == 'workflow_dispatch' && inputs.pr_number
|| github.event.pull_request.number }}
run: |
set -euo pipefail
REQUESTED_TARGETS="${{ steps.targets.outputs.targets }}"
if [ -z "$REQUESTED_TARGETS" ]; then
echo "skip=true" >> $GITHUB_OUTPUT
echo "pending-targets=" >> $GITHUB_OUTPUT
exit 0
fi
FORCE_RERUN=false
if [ "$EVENT_NAME" = "workflow_dispatch" ] && [ "$FORCE_RERUN_INPUT" = "true" ]; then
FORCE_RERUN=true
fi
mapfile -t EXISTING_BRANCHES < <(
git ls-remote --heads origin "backport-${PR_NUMBER}-to-*" || true
)
PENDING=()
SKIPPED=()
for target in $REQUESTED_TARGETS; do
SAFE_TARGET=$(echo "$target" | tr '/' '-')
BACKPORT_BRANCH="backport-${PR_NUMBER}-to-${SAFE_TARGET}"
if [ "$FORCE_RERUN" = true ]; then
PENDING+=("$target")
continue
fi
if printf '%s\n' "${EXISTING_BRANCHES[@]:-}" |
grep -Fq "refs/heads/${BACKPORT_BRANCH}"; then
SKIPPED+=("$target")
else
PENDING+=("$target")
fi
done
SKIPPED_JOINED="${SKIPPED[*]:-}"
PENDING_JOINED="${PENDING[*]:-}"
echo "already-exists=${SKIPPED_JOINED}" >> $GITHUB_OUTPUT
echo "pending-targets=${PENDING_JOINED}" >> $GITHUB_OUTPUT
if [ -z "$PENDING_JOINED" ]; then
echo "skip=true" >> $GITHUB_OUTPUT
if [ -n "$SKIPPED_JOINED" ]; then
echo "::warning::Backport branches already exist for: ${SKIPPED_JOINED}"
fi
else
echo "skip=false" >> $GITHUB_OUTPUT
if [ -n "$SKIPPED_JOINED" ]; then
echo "::notice::Skipping already backported targets: ${SKIPPED_JOINED}"
fi
fi
- name: Backport commits - name: Backport commits
if: steps.check-existing.outputs.skip != 'true' if: steps.filter-targets.outputs.skip != 'true'
id: backport id: backport
env: env:
PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.pull_request.number }} PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.pull_request.number }}
@@ -170,7 +219,7 @@ jobs:
MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}" MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}"
fi fi
for target in ${{ steps.targets.outputs.targets }}; do for target in ${{ steps.filter-targets.outputs.pending-targets }}; do
TARGET_BRANCH="${target}" TARGET_BRANCH="${target}"
SAFE_TARGET=$(echo "$TARGET_BRANCH" | tr '/' '-') SAFE_TARGET=$(echo "$TARGET_BRANCH" | tr '/' '-')
BACKPORT_BRANCH="backport-${PR_NUMBER}-to-${SAFE_TARGET}" BACKPORT_BRANCH="backport-${PR_NUMBER}-to-${SAFE_TARGET}"
@@ -185,6 +234,14 @@ jobs:
continue continue
fi fi
# Check if commit already exists on target branch
if git branch -r --contains "${MERGE_COMMIT}" | grep -q "origin/${TARGET_BRANCH}"; then
echo "::notice::Commit ${MERGE_COMMIT} already exists on ${TARGET_BRANCH}, skipping backport"
FAILED="${FAILED}${TARGET_BRANCH}:already-exists "
echo "::endgroup::"
continue
fi
# Create backport branch # Create backport branch
git checkout -b "${BACKPORT_BRANCH}" "origin/${TARGET_BRANCH}" git checkout -b "${BACKPORT_BRANCH}" "origin/${TARGET_BRANCH}"
@@ -219,7 +276,7 @@ jobs:
fi fi
- name: Create PR for each successful backport - name: Create PR for each successful backport
if: steps.check-existing.outputs.skip != 'true' && steps.backport.outputs.success if: steps.filter-targets.outputs.skip != 'true' && steps.backport.outputs.success
env: env:
GH_TOKEN: ${{ secrets.PR_GH_TOKEN }} GH_TOKEN: ${{ secrets.PR_GH_TOKEN }}
PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.pull_request.number }} PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.pull_request.number }}
@@ -258,7 +315,7 @@ jobs:
done done
- name: Comment on failures - name: Comment on failures
if: steps.check-existing.outputs.skip != 'true' && failure() && steps.backport.outputs.failed if: steps.filter-targets.outputs.skip != 'true' && failure() && steps.backport.outputs.failed
env: env:
GH_TOKEN: ${{ github.token }} GH_TOKEN: ${{ github.token }}
run: | run: |
@@ -279,6 +336,9 @@ jobs:
if [ "${reason}" = "branch-missing" ]; then if [ "${reason}" = "branch-missing" ]; then
gh pr comment "${PR_NUMBER}" --body "@${PR_AUTHOR} Backport failed: Branch \`${target}\` does not exist" gh pr comment "${PR_NUMBER}" --body "@${PR_AUTHOR} Backport failed: Branch \`${target}\` does not exist"
elif [ "${reason}" = "already-exists" ]; then
gh pr comment "${PR_NUMBER}" --body "@${PR_AUTHOR} Commit \`${MERGE_COMMIT}\` already exists on branch \`${target}\`. No backport needed."
elif [ "${reason}" = "conflicts" ]; then elif [ "${reason}" = "conflicts" ]; then
# Convert comma-separated conflicts back to newlines for display # Convert comma-separated conflicts back to newlines for display
CONFLICTS_LIST=$(echo "${conflicts}" | tr ',' '\n' | sed 's/^/- /') CONFLICTS_LIST=$(echo "${conflicts}" | tr ',' '\n' | sed 's/^/- /')
@@ -287,3 +347,9 @@ jobs:
gh pr comment "${PR_NUMBER}" --body "${COMMENT_BODY}" gh pr comment "${PR_NUMBER}" --body "${COMMENT_BODY}"
fi fi
done done
- name: Remove needs-backport label
if: steps.filter-targets.outputs.skip != 'true' && success()
run: gh pr edit ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || github.event.pull_request.number }} --remove-label "needs-backport"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -17,40 +17,9 @@ concurrency:
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
wait-for-ci: claude-review:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.event.label.name == 'claude-review' if: github.event.label.name == 'claude-review'
outputs:
should-proceed: ${{ steps.check-status.outputs.proceed }}
steps:
- name: Wait for other CI checks
uses: lewagon/wait-on-check-action@e106e5c43e8ca1edea6383a39a01c5ca495fd812
with:
ref: ${{ github.event.pull_request.head.sha }}
check-regexp: '^(lint-and-format|test|playwright-tests)'
allowed-conclusions: success,skipped,failure,cancelled,neutral,action_required,timed_out,stale
wait-interval: 30
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Check if we should proceed
id: check-status
run: |
CHECK_RUNS=$(gh api repos/${{ github.repository }}/commits/${{ github.event.pull_request.head.sha }}/check-runs --jq '.check_runs[] | select(.name | test("lint-and-format")) | {name, conclusion}')
if echo "$CHECK_RUNS" | grep -Eq '"conclusion": "(failure|cancelled|timed_out|action_required)"'; then
echo "Some CI checks failed - skipping Claude review"
echo "proceed=false" >> $GITHUB_OUTPUT
else
echo "All CI checks passed - proceeding with Claude review"
echo "proceed=true" >> $GITHUB_OUTPUT
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
claude-review:
needs: wait-for-ci
if: needs.wait-for-ci.outputs.should-proceed == 'true'
runs-on: ubuntu-latest
timeout-minutes: 30 timeout-minutes: 30
steps: steps:
- name: Checkout repository - name: Checkout repository

View File

@@ -69,6 +69,9 @@ jobs:
echo "prev_version=$PREV_VERSION" >> $GITHUB_OUTPUT echo "prev_version=$PREV_VERSION" >> $GITHUB_OUTPUT
echo "prev_minor=$PREV_MINOR" >> $GITHUB_OUTPUT echo "prev_minor=$PREV_MINOR" >> $GITHUB_OUTPUT
BASE_COMMIT=$(git rev-parse HEAD)
echo "base_commit=$BASE_COMMIT" >> $GITHUB_OUTPUT
# Get previous major version for comparison # Get previous major version for comparison
PREV_MAJOR=$(echo $PREV_VERSION | cut -d. -f1) PREV_MAJOR=$(echo $PREV_VERSION | cut -d. -f1)
@@ -87,13 +90,13 @@ jobs:
elif [[ "$MAJOR" -gt "$PREV_MAJOR" && "$MINOR" == "0" && "$PATCH" == "0" ]]; then elif [[ "$MAJOR" -gt "$PREV_MAJOR" && "$MINOR" == "0" && "$PATCH" == "0" ]]; then
# Major version bump (e.g., 1.99.x → 2.0.0) # Major version bump (e.g., 1.99.x → 2.0.0)
echo "is_minor_bump=true" >> $GITHUB_OUTPUT echo "is_minor_bump=true" >> $GITHUB_OUTPUT
BRANCH_NAME="core/${PREV_MAJOR}.${PREV_MINOR}" BRANCH_BASE="${PREV_MAJOR}.${PREV_MINOR}"
echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT echo "branch_base=$BRANCH_BASE" >> $GITHUB_OUTPUT
elif [[ "$MAJOR" == "$PREV_MAJOR" && "$MINOR" -gt "$PREV_MINOR" && "$PATCH" == "0" ]]; then elif [[ "$MAJOR" == "$PREV_MAJOR" && "$MINOR" -gt "$PREV_MINOR" && "$PATCH" == "0" ]]; then
# Minor version bump (e.g., 1.23.x → 1.24.0) # Minor version bump (e.g., 1.23.x → 1.24.0)
echo "is_minor_bump=true" >> $GITHUB_OUTPUT echo "is_minor_bump=true" >> $GITHUB_OUTPUT
BRANCH_NAME="core/${MAJOR}.${PREV_MINOR}" BRANCH_BASE="${MAJOR}.${PREV_MINOR}"
echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT echo "branch_base=$BRANCH_BASE" >> $GITHUB_OUTPUT
else else
echo "is_minor_bump=false" >> $GITHUB_OUTPUT echo "is_minor_bump=false" >> $GITHUB_OUTPUT
fi fi
@@ -101,64 +104,97 @@ jobs:
# Return to main branch # Return to main branch
git checkout main git checkout main
- name: Create release branch - name: Create release branches
id: create_branches
if: steps.check_version.outputs.is_minor_bump == 'true' if: steps.check_version.outputs.is_minor_bump == 'true'
run: | run: |
BRANCH_NAME="${{ steps.check_version.outputs.branch_name }}" BRANCH_BASE="${{ steps.check_version.outputs.branch_base }}"
PREV_VERSION="${{ steps.check_version.outputs.prev_version }}"
# Check if branch already exists if [[ -z "$BRANCH_BASE" ]]; then
if git ls-remote --heads origin "$BRANCH_NAME" | grep -q "$BRANCH_NAME"; then echo "::error::Branch base not set; unable to determine release branches"
echo "⚠️ Branch $BRANCH_NAME already exists, skipping creation" exit 1
echo "branch_exists=true" >> $GITHUB_ENV
exit 0
else
echo "branch_exists=false" >> $GITHUB_ENV
fi fi
# Create branch from the commit BEFORE the version bump BASE_COMMIT="${{ steps.check_version.outputs.base_commit }}"
# This ensures the release branch has the previous minor version
git checkout -b "$BRANCH_NAME" HEAD^1
# Push the new branch if [[ -z "$BASE_COMMIT" ]]; then
git push origin "$BRANCH_NAME" echo "::error::Base commit not provided; cannot create release branches"
exit 1
fi
echo "✅ Created release branch: $BRANCH_NAME" RESULTS_FILE=$(mktemp)
echo "This branch is now in feature freeze and will only receive:" trap 'rm -f "$RESULTS_FILE"' EXIT
echo "- Bug fixes"
echo "- Critical security patches"
echo "- Documentation updates"
for PREFIX in core cloud; do
BRANCH_NAME="${PREFIX}/${BRANCH_BASE}"
if git ls-remote --exit-code --heads origin \
"$BRANCH_NAME" >/dev/null 2>&1; then
echo "⚠️ Branch $BRANCH_NAME already exists"
echo " Skipping creation for $BRANCH_NAME"
STATUS="exists"
else
# Create branch from the commit BEFORE the version bump
if ! git push origin "$BASE_COMMIT:refs/heads/$BRANCH_NAME"; then
echo "::error::Failed to push release branch $BRANCH_NAME"
exit 1
fi
echo "✅ Created release branch: $BRANCH_NAME"
STATUS="created"
fi
echo "$BRANCH_NAME|$STATUS|$PREV_VERSION" >> "$RESULTS_FILE"
done
{
echo "results<<'EOF'"
cat "$RESULTS_FILE"
echo "EOF"
} >> $GITHUB_OUTPUT
- name: Post summary - name: Post summary
if: steps.check_version.outputs.is_minor_bump == 'true' if: steps.check_version.outputs.is_minor_bump == 'true'
run: | run: |
BRANCH_NAME="${{ steps.check_version.outputs.branch_name }}"
PREV_VERSION="${{ steps.check_version.outputs.prev_version }}"
CURRENT_VERSION="${{ steps.check_version.outputs.current_version }}" CURRENT_VERSION="${{ steps.check_version.outputs.current_version }}"
RESULTS="${{ steps.create_branches.outputs.results }}"
if [[ "${{ env.branch_exists }}" == "true" ]]; then if [[ -z "$RESULTS" ]]; then
cat >> $GITHUB_STEP_SUMMARY << EOF cat >> $GITHUB_STEP_SUMMARY << EOF
## 🌿 Release Branch Already Exists ## 🌿 Release Branch Summary
The release branch for the previous minor version already exists: Release branch creation skipped; no eligible branches were found.
EOF
else
cat >> $GITHUB_STEP_SUMMARY << EOF
## 🌿 Release Branch Created
A new release branch has been created for the previous minor version:
EOF EOF
exit 0
fi fi
cat >> $GITHUB_STEP_SUMMARY << EOF cat >> $GITHUB_STEP_SUMMARY << EOF
## 🌿 Release Branch Summary
- **Branch**: \`$BRANCH_NAME\`
- **Version**: \`$PREV_VERSION\` (feature frozen)
- **Main branch**: \`$CURRENT_VERSION\` (active development) - **Main branch**: \`$CURRENT_VERSION\` (active development)
### Branch Status
EOF
while IFS='|' read -r BRANCH STATUS PREV_VERSION; do
if [[ "$STATUS" == "created" ]]; then
cat >> $GITHUB_STEP_SUMMARY << EOF
- \`$BRANCH\` created from version \`$PREV_VERSION\`
EOF
else
cat >> $GITHUB_STEP_SUMMARY << EOF
- \`$BRANCH\` already existed (based on version \`$PREV_VERSION\`)
EOF
fi
done <<< "$RESULTS"
cat >> $GITHUB_STEP_SUMMARY << EOF
### Branch Policy ### Branch Policy
The \`$BRANCH_NAME\` branch is now in **feature freeze** and will only accept: Release branches are feature-frozen and only accept:
- 🐛 Bug fixes - 🐛 Bug fixes
- 🔒 Security patches - 🔒 Security patches
- 📚 Documentation updates - 📚 Documentation updates
@@ -167,9 +203,9 @@ jobs:
### Backporting Changes ### Backporting Changes
To backport a fix to this release branch: To backport a fix:
1. Create your fix on \`main\` first 1. Create your fix on \`main\` first
2. Cherry-pick to \`$BRANCH_NAME\` 2. Cherry-pick to the target release branch
3. Create a PR targeting \`$BRANCH_NAME\` 3. Create a PR targeting that branch
4. Use the \`Release\` label on the PR 4. Apply the matching \`core/x.y\` or \`cloud/x.y\` label
EOF EOF

144
.github/workflows/weekly-docs-check.yaml vendored Normal file
View File

@@ -0,0 +1,144 @@
name: "Weekly Documentation Check"
description: "Automated weekly documentation accuracy check and update via Claude"
permissions:
contents: write
pull-requests: write
id-token: write
on:
schedule:
# Run every Monday at 9 AM UTC
- cron: '0 9 * * 1'
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: true
jobs:
docs-check:
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 0
ref: main
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- name: Install dependencies for analysis tools
run: |
# Check if packages are already available locally
if ! pnpm list typescript @vue/compiler-sfc >/dev/null 2>&1; then
echo "Installing TypeScript and Vue compiler globally..."
pnpm install -g typescript @vue/compiler-sfc
else
echo "TypeScript and Vue compiler already available locally"
fi
- name: Run Claude Documentation Review
uses: anthropics/claude-code-action@v1.0.6
with:
prompt: |
Is all documentation still 100% accurate?
INSTRUCTIONS:
1. Fact-check all documentation against the current codebase
2. Look for:
- Outdated API references
- Deprecated functions or components still documented
- Missing documentation for new features
- Incorrect code examples
- Broken internal references
- Configuration examples that no longer work
- Documentation that contradicts actual implementation
3. Update any inaccurate or outdated documentation
4. Add documentation for significant undocumented features
5. Ensure all code examples are valid and tested against current code
Focus on these key areas:
- docs/**/*.md (all documentation files)
- CLAUDE.md (project guidelines)
- README.md files throughout the repository
- .claude/commands/*.md (Claude command documentation)
Make changes directly to the documentation files as needed.
DO NOT modify any source code files unless absolutely necessary for documentation accuracy.
After making all changes, create a comprehensive PR message summary:
1. Write a detailed PR body to /tmp/pr-body-${{ github.run_id }}.md in markdown format
2. Include:
- ## Summary section with bullet points of what was changed
- ## Changes Made section with details organized by category
- ## Review Notes section with any important context
3. Be specific about which files were updated and why
4. If no changes were needed, write a brief message stating documentation is up to date
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
claude_args: "--max-turns 256 --allowedTools 'Bash(git status),Bash(git diff),Bash(git log),Bash(pnpm:*),Bash(npm:*),Bash(node:*),Bash(tsc:*),Bash(echo:*),Read,Write,Edit,Glob,Grep'"
continue-on-error: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Check for changes
id: check_changes
run: |
if git diff --quiet && git diff --cached --quiet; then
echo "has_changes=false" >> $GITHUB_OUTPUT
echo "No documentation changes needed"
else
echo "has_changes=true" >> $GITHUB_OUTPUT
echo "Documentation changes detected"
fi
- name: Create default PR body if not generated
if: steps.check_changes.outputs.has_changes == 'true'
run: |
if [ ! -f /tmp/pr-body-${{ github.run_id }}.md ]; then
cat > /tmp/pr-body-${{ github.run_id }}.md <<'EOF'
## Automated Documentation Review
This PR contains documentation updates identified by the weekly automated review.
### Review Process
- Automated fact-checking against current codebase
- Verification of code examples and API references
- Detection of outdated or missing documentation
### What was checked
- All markdown documentation in `docs/`
- Project guidelines in `CLAUDE.md`
- README files throughout the repository
- Claude command documentation in `.claude/commands/`
**Note**: This is an automated PR. Please review all changes carefully before merging.
🤖 Generated by weekly documentation check workflow
EOF
fi
- name: Create or Update Pull Request
if: steps.check_changes.outputs.has_changes == 'true'
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.PR_GH_TOKEN }}
commit-message: 'docs: weekly documentation accuracy update'
branch: docs/weekly-update
delete-branch: true
title: 'docs: Weekly Documentation Update'
body-path: /tmp/pr-body-${{ github.run_id }}.md
labels: |
documentation
automated
draft: true

4
.gitignore vendored
View File

@@ -18,6 +18,7 @@ yarn.lock
.stylelintcache .stylelintcache
node_modules node_modules
.pnpm-store
dist dist
dist-ssr dist-ssr
*.local *.local
@@ -92,3 +93,6 @@ storybook-static
.github/instructions/nx.instructions.md .github/instructions/nx.instructions.md
vite.config.*.timestamp* vite.config.*.timestamp*
vitest.config.*.timestamp* vitest.config.*.timestamp*
# Weekly docs check output
/output.txt

View File

@@ -62,6 +62,11 @@ Key Nx features:
## Project Philosophy ## Project Philosophy
- Follow good software engineering principles
- YAGNI
- AHA
- DRY
- SOLID
- Clean, stable public APIs - Clean, stable public APIs
- Domain-driven design - Domain-driven design
- Thousands of users and extensions - Thousands of users and extensions

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

View File

@@ -1657,7 +1657,8 @@ export const comfyPageFixture = base.extend<{
'Comfy.userId': userId, 'Comfy.userId': userId,
// 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
}) })
} catch (e) { } catch (e) {
console.error(e) console.error(e)

View File

@@ -503,7 +503,7 @@ export class NodeReference {
for (const position of clickPositions) { for (const position of clickPositions) {
// Clear any selection first // Clear any selection first
await this.comfyPage.canvas.click({ await this.comfyPage.canvas.click({
position: { x: 50, y: 50 }, position: { x: 250, y: 250 },
force: true force: true
}) })
await this.comfyPage.nextFrame() await this.comfyPage.nextFrame()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 77 KiB

View File

@@ -5,7 +5,6 @@ import { createTypeScriptImportResolver } from 'eslint-import-resolver-typescrip
import { importX } from 'eslint-plugin-import-x' import { importX } from 'eslint-plugin-import-x'
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended' import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
import storybook from 'eslint-plugin-storybook' import storybook from 'eslint-plugin-storybook'
import tailwind from 'eslint-plugin-tailwindcss'
import unusedImports from 'eslint-plugin-unused-imports' import unusedImports from 'eslint-plugin-unused-imports'
import pluginVue from 'eslint-plugin-vue' import pluginVue from 'eslint-plugin-vue'
import { defineConfig } from 'eslint/config' import { defineConfig } from 'eslint/config'
@@ -34,11 +33,7 @@ const settings = {
], ],
noWarnOnMultipleProjects: true noWarnOnMultipleProjects: true
}) })
], ]
tailwindcss: {
config: `${import.meta.dirname}/packages/design-system/src/css/style.css`,
functions: ['cn', 'clsx', 'tw']
}
} as const } as const
const commonParserOptions = { const commonParserOptions = {
@@ -97,7 +92,6 @@ export default defineConfig([
// Difference in typecheck on CI vs Local // Difference in typecheck on CI vs Local
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore Bad types in the plugin // @ts-ignore Bad types in the plugin
tailwind.configs['flat/recommended'],
pluginVue.configs['flat/recommended'], pluginVue.configs['flat/recommended'],
eslintPluginPrettierRecommended, eslintPluginPrettierRecommended,
storybook.configs['flat/recommended'], storybook.configs['flat/recommended'],
@@ -129,7 +123,6 @@ export default defineConfig([
'import-x/no-relative-packages': 'error', 'import-x/no-relative-packages': 'error',
'unused-imports/no-unused-imports': 'error', 'unused-imports/no-unused-imports': 'error',
'no-console': ['error', { allow: ['warn', 'error'] }], 'no-console': ['error', { allow: ['warn', 'error'] }],
'tailwindcss/no-custom-classname': 'off', // TODO: fix
'vue/no-v-html': 'off', 'vue/no-v-html': 'off',
// Enforce dark-theme: instead of dark: prefix // Enforce dark-theme: instead of dark: prefix
'vue/no-restricted-class': ['error', '/^dark:/'], 'vue/no-restricted-class': ['error', '/^dark:/'],

View File

@@ -14,7 +14,7 @@ const config: KnipConfig = {
}, },
'apps/desktop-ui': { 'apps/desktop-ui': {
entry: ['src/main.ts', 'src/i18n.ts'], entry: ['src/main.ts', 'src/i18n.ts'],
project: ['src/**/*.{js,ts,vue}', '*.{js,ts,mts}'] project: ['src/**/*.{js,ts,vue}']
}, },
'packages/tailwind-utils': { 'packages/tailwind-utils': {
project: ['src/**/*.{js,ts}'] project: ['src/**/*.{js,ts}']

View File

@@ -1,7 +1,7 @@
{ {
"name": "@comfyorg/comfyui-frontend", "name": "@comfyorg/comfyui-frontend",
"private": true, "private": true,
"version": "1.31.0", "version": "1.32.1",
"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",
@@ -43,7 +43,8 @@
"test:browser": "pnpm exec nx e2e", "test:browser": "pnpm exec nx e2e",
"test:unit": "nx run test", "test:unit": "nx run test",
"typecheck": "vue-tsc --noEmit", "typecheck": "vue-tsc --noEmit",
"zipdist": "node scripts/zipdist.js" "zipdist": "node scripts/zipdist.js",
"clean": "nx reset"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "catalog:", "@eslint/js": "catalog:",
@@ -56,12 +57,12 @@
"@pinia/testing": "catalog:", "@pinia/testing": "catalog:",
"@playwright/test": "catalog:", "@playwright/test": "catalog:",
"@prettier/plugin-oxc": "catalog:", "@prettier/plugin-oxc": "catalog:",
"@sentry/vite-plugin": "catalog:",
"@storybook/addon-docs": "catalog:", "@storybook/addon-docs": "catalog:",
"@storybook/vue3": "catalog:", "@storybook/vue3": "catalog:",
"@storybook/vue3-vite": "catalog:", "@storybook/vue3-vite": "catalog:",
"@tailwindcss/vite": "catalog:", "@tailwindcss/vite": "catalog:",
"@trivago/prettier-plugin-sort-imports": "catalog:", "@trivago/prettier-plugin-sort-imports": "catalog:",
"@types/eslint-plugin-tailwindcss": "catalog:",
"@types/fs-extra": "catalog:", "@types/fs-extra": "catalog:",
"@types/jsdom": "catalog:", "@types/jsdom": "catalog:",
"@types/node": "catalog:", "@types/node": "catalog:",
@@ -78,7 +79,6 @@
"eslint-plugin-import-x": "catalog:", "eslint-plugin-import-x": "catalog:",
"eslint-plugin-prettier": "catalog:", "eslint-plugin-prettier": "catalog:",
"eslint-plugin-storybook": "catalog:", "eslint-plugin-storybook": "catalog:",
"eslint-plugin-tailwindcss": "catalog:",
"eslint-plugin-unused-imports": "catalog:", "eslint-plugin-unused-imports": "catalog:",
"eslint-plugin-vue": "catalog:", "eslint-plugin-vue": "catalog:",
"fs-extra": "^11.2.0", "fs-extra": "^11.2.0",

View File

@@ -9,29 +9,18 @@
@config '../../tailwind.config.ts'; @config '../../tailwind.config.ts';
@media (prefers-color-scheme: dark) {
:root {
--fg-color: #fff;
--bg-color: #202020;
--content-bg: #4e4e4e;
--content-fg: #fff;
--content-hover-bg: #222;
--content-hover-fg: #fff;
}
}
@theme { @theme {
--text-xxs: 0.625rem; --text-xxs: 0.625rem;
--text-xxs--line-height: calc(1 / 0.625); --text-xxs--line-height: calc(1 / 0.625);
/* Spacing */ --text-xxxs: 0.5625rem;
--spacing-xs: 8px; --text-xxxs--line-height: calc(1 / 0.5625);
/* Font Families */ /* Font Families */
--font-inter: 'Inter', sans-serif; --font-inter: 'Inter', sans-serif;
/* Palette Colors */ /* Palette Colors */
--color-charcoal-100: #171718; --color-charcoal-100: #55565e;
--color-charcoal-200: #494a50; --color-charcoal-200: #494a50;
--color-charcoal-300: #3c3d42; --color-charcoal-300: #3c3d42;
--color-charcoal-400: #313235; --color-charcoal-400: #313235;
@@ -42,43 +31,61 @@
--color-neutral-550: #636363; --color-neutral-550: #636363;
--color-stone-100: #828282; --color-ash-300: #bbbbbb;
--color-stone-200: #444444; --color-ash-500: #828282;
--color-stone-300: #bbbbbb; --color-ash-800: #444444;
--color-ivory-100: #fdfbfa; --color-ivory-100: #fdfbfa;
--color-ivory-200: #faf9f5; --color-ivory-200: #faf9f5;
--color-ivory-300: #f0eee6; --color-ivory-300: #f0eee6;
--color-gray-100: #f3f3f3; --color-smoke-100: #f3f3f3;
--color-gray-200: #e9e9e9; --color-smoke-200: #e9e9e9;
--color-gray-300: #e1e1e1; --color-smoke-300: #e1e1e1;
--color-gray-400: #d9d9d9; --color-smoke-400: #d9d9d9;
--color-gray-500: #c5c5c5; --color-smoke-500: #c5c5c5;
--color-gray-600: #b4b4b4; --color-smoke-600: #b4b4b4;
--color-gray-700: #a0a0a0; --color-smoke-700: #a0a0a0;
--color-gray-800: #8a8a8a; --color-smoke-800: #8a8a8a;
--color-sand-100: #e1ded5; --color-sand-100: #e1ded5;
--color-sand-200: #d6cfc2; --color-sand-200: #fff7d5;
--color-sand-300: #888682; --color-sand-300: #888682;
--color-sand-400: #eed7ac;
--color-pure-black: #000000;
--color-pure-white: #ffffff;
--color-slate-100: #9c9eab; --color-slate-100: #9c9eab;
--color-slate-200: #9fa2bd; --color-slate-200: #9fa2bd;
--color-slate-300: #5b5e7d; --color-slate-300: #5b5e7d;
--color-brand-yellow: #f0ff41; --color-white: #ffffff;
--color-brand-blue: #172dd7; --color-black: #000000;
--color-electric-400: #f0ff41;
--color-sapphire-700: #172dd7;
--color-brand-yellow: var(--color-electric-400);
--color-brand-blue: var(--color-sapphire-700);
--color-azure-300: #78bae9;
--color-azure-400: #31b9f4;
--color-azure-600: #0b8ce9;
--color-cobalt-800: #185a8b;
--color-jade-400: #47e469;
--color-jade-600: #00cd72;
--color-graphite-400: #9C9EAB;
--color-gold-400: #fcbf64;
--color-gold-500: #fdab34;
--color-gold-600: #fd9903;
--color-coral-500: #f75951;
--color-coral-600: #e04e48;
--color-coral-700: #b33a3a;
--color-magenta-300: #ceaac9;
--color-magenta-700: #6a246a;
--color-blue-100: #0b8ce9;
--color-blue-200: #31b9f4;
--color-success-100: #00cd72;
--color-success-200: #47e469;
--color-warning-100: #fd9903;
--color-warning-200: #fcbf64;
--color-danger-100: #c02323; --color-danger-100: #c02323;
--color-danger-200: #d62952; --color-danger-200: #d62952;
@@ -90,28 +97,31 @@
--color-error: #962a2a; --color-error: #962a2a;
--color-comfy-menu-secondary: var(--comfy-menu-secondary-bg); --color-comfy-menu-secondary: var(--comfy-menu-secondary-bg);
--text-xxxs: 0.5625rem;
--text-xxxs--line-height: calc(1 / 0.5625);
--color-blue-selection: rgb(from var(--color-blue-100) r g b / 0.3); --color-blue-selection: rgb(from var(--color-azure-600) r g b / 0.3);
--color-node-hover-100: rgb(from var(--color-charcoal-100) r g b/ 0.15); --color-node-hover-100: rgb(from var(--color-charcoal-800) r g b/ 0.15);
--color-node-hover-200: rgb(from var(--color-charcoal-100) r g b/ 0.1); --color-node-hover-200: rgb(from var(--color-charcoal-800) r g b/ 0.1);
--color-modal-tag: rgb(from var(--color-gray-400) r g b/ 0.4); --color-modal-tag: rgb(from var(--color-smoke-400) r g b/ 0.4);
--color-alpha-charcoal-600-30: color-mix( --color-alpha-charcoal-600-30: color-mix(
in srgb, in srgb,
var(--color-charcoal-600) 30%, var(--color-charcoal-600) 30%,
transparent transparent
); );
--color-alpha-stone-100-20: color-mix( --color-alpha-ash-500-20: color-mix(
in srgb, in srgb,
var(--color-stone-100) 20%, var(--color-ash-500) 20%,
transparent transparent
); );
--color-alpha-gray-500-50: color-mix( --color-alpha-smoke-500-50: color-mix(
in srgb, in srgb,
var(--color-gray-500) 50%, var(--color-smoke-500) 50%,
transparent transparent
); );
--color-alpha-smoke-500-20: #c5c5c533;
--color-alpha-smoke-400-40: #d9d9d966;
--color-alpha-azure-600-30: #0b8ce94d;
--color-alpha-magenta-700-60: #6a246a99;
--color-alpha-magenta-300-60: #ceaac999;
/* PrimeVue pulled colors */ /* PrimeVue pulled colors */
--color-muted: var(--p-text-muted-color); --color-muted: var(--p-text-muted-color);
@@ -145,8 +155,12 @@
--content-hover-bg: #adadad; --content-hover-bg: #adadad;
--content-hover-fg: #000; --content-hover-fg: #000;
--button-surface: var(--color-pure-white); --button-surface: var(--color-white);
--button-surface-contrast: var(--color-pure-black); --button-surface-contrast: var(--color-black);
--subscription-button-gradient: linear-gradient(315deg, rgb(105 230 255 / 0.15) 0%, rgb(99 73 233 / 0.50) 100%), radial-gradient(70.71% 70.71% at 50% 50%, rgb(62 99 222 / 0.15) 0.01%, rgb(66 0 123 / 0.50) 100%), linear-gradient(92deg, #D000FF 0.38%, #B009FE 37.07%, #3E1FFC 65.17%, #009DFF 103.86%), var(--color-button-surface, #2D2E32);
--modal-card-button-surface: var(--color-smoke-300);
/* Code styling colors for help menu*/ /* Code styling colors for help menu*/
--code-text-color: rgb(0 122 255 / 1); --code-text-color: rgb(0 122 255 / 1);
@@ -157,31 +171,36 @@
--accent-primary: var(--color-charcoal-700); --accent-primary: var(--color-charcoal-700);
--backdrop: var(--color-white); --backdrop: var(--color-white);
--button-hover-surface: var(--color-gray-200);
--button-active-surface: var(--color-gray-400); --button-hover-surface: var(--color-smoke-200);
--button-icon: var(--color-gray-600); --button-active-surface: var(--color-smoke-400);
--button-icon: var(--color-smoke-600);
--dialog-surface: var(--color-neutral-200); --dialog-surface: var(--color-neutral-200);
--interface-menu-component-surface-hovered: var(--color-gray-200);
--interface-menu-component-surface-selected: var(--color-gray-400); --interface-menu-component-surface-hovered: var(--color-smoke-200);
--interface-menu-keybind-surface-default: var(--color-gray-500); --interface-menu-component-surface-selected: var(--color-smoke-400);
--interface-panel-surface: var(--color-pure-white); --interface-menu-keybind-surface-default: var(--color-smoke-500);
--interface-stroke: var(--color-gray-300); --interface-panel-surface: var(--color-white);
--nav-background: var(--color-pure-white); --interface-stroke: var(--color-smoke-300);
--node-border: var(--color-gray-300);
--node-component-border: var(--color-gray-400); --nav-background: var(--color-white);
--node-component-disabled: var(--color-alpha-stone-100-20);
--node-border: var(--color-smoke-300);
--node-component-border: var(--color-smoke-400);
--node-component-disabled: var(--color-alpha-ash-500-20);
--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-stone-200); --node-component-header-icon: var(--color-ash-800);
--node-component-header-surface: var(--color-white); --node-component-header-surface: var(--color-white);
--node-component-outline: var(--color-black); --node-component-outline: var(--color-black);
--node-component-ring: rgb(from var(--color-gray-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;
--node-component-slot-dot-outline-opacity: 5%; --node-component-slot-dot-outline-opacity: 5%;
--node-component-slot-dot-outline: var(--color-black); --node-component-slot-dot-outline: var(--color-black);
--node-component-slot-text: var(--color-stone-200); --node-component-slot-text: var(--color-ash-800);
--node-component-surface-highlight: var(--color-stone-100); --node-component-surface-highlight: var(--color-ash-500);
--node-component-surface-hovered: var(--color-gray-200); --node-component-surface-hovered: var(--color-smoke-200);
--node-component-surface-selected: var(--color-charcoal-200); --node-component-surface-selected: var(--color-charcoal-200);
--node-component-surface: var(--color-white); --node-component-surface: var(--color-white);
--node-component-tooltip: var(--color-charcoal-700); --node-component-tooltip: var(--color-charcoal-700);
@@ -193,61 +212,156 @@
); );
--node-component-widget-skeleton-surface: var(--color-zinc-300); --node-component-widget-skeleton-surface: var(--color-zinc-300);
--node-divider: var(--color-sand-100); --node-divider: var(--color-sand-100);
--node-icon-disabled: var(--color-alpha-gray-500-50); --node-icon-disabled: var(--color-alpha-smoke-500-50);
--node-stroke: var(--color-gray-400); --node-stroke: var(--color-smoke-400);
--node-stroke-selected: var(--color-accent-primary); --node-stroke-selected: var(--color-accent-primary);
--node-stroke-error: var(--color-error); --node-stroke-error: var(--color-error);
--node-stroke-executing: var(--color-blue-100); --node-stroke-executing: var(--color-azure-600);
--text-secondary: var(--color-stone-100);
--text-secondary: var(--color-ash-500);
--text-primary: var(--color-charcoal-700); --text-primary: var(--color-charcoal-700);
--input-surface: rgb(0 0 0 / 0.15); --input-surface: rgb(0 0 0 / 0.15);
/* Semantic tokens - light mode */
--muted-foreground: var(--color-charcoal-200);
--base-foreground: var(--color-charcoal-800);
--brand-yellow: var(--color-electric-400);
--brand-blue: var(--color-sapphire-700);
--secondary-background: var(--color-smoke-200);
--secondary-background-hover: var(--color-smoke-200);
--secondary-background-selected: var(--color-smoke-600);
--base-background: var(--color-white);
--primary-background: var(--color-azure-400);
--primary-background-hover: var(--color-cobalt-800);
--destructive-background: var(--color-coral-500);
--destructive-background-hover: var(--color-coral-600);
--inverted-background-hover: var(--color-charcoal-600);
--warning-background: var(--color-gold-400);
--warning-background-hover: var(--color-gold-500);
--border-default: var(--color-smoke-600);
--border-subtle: var(--color-smoke-400);
--muted-background: var(--color-smoke-700);
--accent-background: var(--color-smoke-800);
/* Component/Node tokens from design system light */
--component-node-background: var(--color-white);
--component-node-border: var(--color-border-default);
--component-node-foreground: var(--base-foreground);
--component-node-foreground-secondary: var(--color-muted-foreground);
--component-node-widget-background: var(--secondary-background);
--component-node-widget-background-hovered: var(--secondary-background-hover);
--component-node-widget-background-selected: var(--secondary-background-selected);
--component-node-widget-background-disabled: var(--color-alpha-ash-500-20);
--component-node-widget-background-highlighted: var(--color-ash-500);
/* Default UI element color palette variables */
--palette-contrast-mix-color: #fff;
--palette-interface-panel-surface: var(--comfy-menu-bg);
--palette-interface-stroke: color-mix(in srgb, var(--interface-panel-surface) 75.5%, var(--contrast-mix-color));
--palette-interface-panel-box-shadow: 1px 1px 8px 0 rgb(0 0 0 / 0.4);
--palette-interface-panel-drop-shadow: 1px 1px 4px rgb(0 0 0 / 0.4);
--palette-interface-panel-hover-surface: color-mix(in srgb, var(--interface-panel-surface) 92.5%, var(--contrast-mix-color));
--palette-interface-panel-selected-surface: color-mix(in srgb, var(--interface-panel-surface) 87.5%, var(--contrast-mix-color));
--palette-interface-button-hover-surface: color-mix(in srgb, var(--interface-panel-surface) 82%, var(--contrast-mix-color));
} }
.dark-theme { .dark-theme {
--accent-primary: var(--color-pure-white); --fg-color: #fff;
--bg-color: #202020;
--content-bg: #4e4e4e;
--content-fg: #fff;
--content-hover-bg: #222;
--content-hover-fg: #fff;
--accent-primary: var(--color-white);
--backdrop: var(--color-neutral-900); --backdrop: var(--color-neutral-900);
--button-surface: var(--color-charcoal-600); --button-surface: var(--color-charcoal-600);
--button-surface-contrast: var(--color-pure-white); --button-surface-contrast: var(--color-white);
--button-hover-surface: var(--color-charcoal-600); --button-hover-surface: var(--color-charcoal-600);
--button-active-surface: var(--color-charcoal-600); --button-active-surface: var(--color-charcoal-600);
--button-icon: var(--color-gray-800); --button-icon: var(--color-smoke-800);
--subscription-button-gradient: linear-gradient(315deg, rgb(105 230 255 / 0.15) 0%, rgb(99 73 233 / 0.50) 100%), radial-gradient(70.71% 70.71% at 50% 50%, rgb(62 99 222 / 0.15) 0.01%, rgb(66 0 123 / 0.50) 100%), linear-gradient(92deg, #D000FF 0.38%, #B009FE 37.07%, #3E1FFC 65.17%, #009DFF 103.86%), var(--color-button-surface, #2D2E32);
--modal-card-button-surface: var(--color-charcoal-300);
--dialog-surface: var(--color-neutral-700); --dialog-surface: var(--color-neutral-700);
--interface-menu-component-surface-hovered: var(--color-charcoal-400); --interface-menu-component-surface-hovered: var(--color-charcoal-400);
--interface-menu-component-surface-selected: var(--color-charcoal-300); --interface-menu-component-surface-selected: var(--color-charcoal-300);
--interface-menu-keybind-surface-default: var(--color-charcoal-200); --interface-menu-keybind-surface-default: var(--color-charcoal-200);
--interface-panel-surface: var(--color-charcoal-100); --interface-panel-surface: var(--color-charcoal-800);
--interface-stroke: var(--color-charcoal-400); --interface-stroke: var(--color-charcoal-400);
--nav-background: var(--color-charcoal-100);
--nav-background: var(--color-charcoal-800);
--node-border: var(--color-charcoal-500); --node-border: var(--color-charcoal-500);
--node-component-border: var(--color-stone-200); --node-component-border: var(--color-ash-800);
--node-component-border-error: var(--color-danger-100); --node-component-border-error: var(--color-danger-100);
--node-component-border-executing: var(--color-blue-500); --node-component-border-executing: var(--color-blue-500);
--node-component-border-selected: var(--color-charcoal-200); --node-component-border-selected: var(--color-charcoal-200);
--node-component-header-icon: var(--color-slate-300); --node-component-header-icon: var(--color-slate-300);
--node-component-header-surface: var(--color-charcoal-800); --node-component-header-surface: var(--color-charcoal-800);
--node-component-outline: var(--color-white); --node-component-outline: var(--color-white);
--node-component-ring: rgb(var(--color-gray-500) / 20%); --node-component-ring: rgb(var(--color-smoke-500) / 20%);
--node-component-slot-dot-outline-opacity: 10%; --node-component-slot-dot-outline-opacity: 10%;
--node-component-slot-dot-outline: var(--color-white); --node-component-slot-dot-outline: var(--color-white);
--node-component-slot-text: var(--color-slate-200); --node-component-slot-text: var(--color-slate-200);
--node-component-surface-highlight: var(--color-slate-100); --node-component-surface-highlight: var(--color-slate-100);
--node-component-surface-hovered: var(--color-charcoal-600); --node-component-surface-hovered: var(--color-charcoal-600);
--node-component-surface-selected: var(--color-charcoal-200); --node-component-surface-selected: var(--color-charcoal-200);
--node-component-surface: var(--color-charcoal-800); --node-component-surface: var(--color-charcoal-600);
--node-component-tooltip: var(--color-white); --node-component-tooltip: var(--color-white);
--node-component-tooltip-border: var(--color-slate-300); --node-component-tooltip-border: var(--color-slate-300);
--node-component-tooltip-surface: var(--color-charcoal-800); --node-component-tooltip-surface: var(--color-charcoal-800);
--node-component-widget-skeleton-surface: var(--color-zinc-800); --node-component-widget-skeleton-surface: var(--color-zinc-800);
--node-component-disabled: var(--color-alpha-charcoal-600-30); --node-component-disabled: var(--color-alpha-charcoal-600-30);
--node-divider: var(--color-charcoal-500); --node-divider: var(--color-charcoal-500);
--node-icon-disabled: var(--color-alpha-stone-100-20); --node-icon-disabled: var(--color-alpha-ash-500-20);
--node-stroke: var(--color-stone-200); --node-stroke: var(--color-ash-800);
--node-stroke-selected: var(--color-pure-white); --node-stroke-selected: var(--color-white);
--node-stroke-error: var(--color-error); --node-stroke-error: var(--color-error);
--node-stroke-executing: var(--color-blue-100); --node-stroke-executing: var(--color-azure-600);
--text-secondary: var(--color-slate-100); --text-secondary: var(--color-slate-100);
--text-primary: var(--color-pure-white); --text-primary: var(--color-white);
--input-surface: rgb(130 130 130 / 0.1); --input-surface: rgb(130 130 130 / 0.1);
/* Semantic tokens - dark mode */
--muted-foreground: var(--color-smoke-800);
--base-foreground: var(--color-white);
--brand-yellow: var(--color-electric-400);
--brand-blue: var(--color-sapphire-700);
--secondary-background: var(--color-charcoal-600);
--secondary-background-hover: var(--color-charcoal-400);
--secondary-background-selected: var(--color-charcoal-200);
--base-background: var(--color-charcoal-800);
--primary-background: var(--color-azure-600);
--primary-background-hover: var(--color-azure-400);
--destructive-background: var(--color-coral-700);
--destructive-background-hover: var(--color-coral-600);
--inverted-background-hover: var(--color-smoke-200);
--warning-background: var(--color-gold-600);
--warning-background-hover: var(--color-gold-500);
--border-default: var(--color-charcoal-200);
--border-subtle: var(--color-charcoal-300);
--muted-background: var(--color-charcoal-100);
--accent-background: var(--color-charcoal-100);
/* Component/Node tokens from design dark system */
--component-node-background: var(--color-charcoal-600);
--component-node-border: var(--color-charcoal-100);
--component-node-foreground: var(--base-foreground);
--component-node-foreground-secondary: var(--color-muted-foreground);
--component-node-widget-background: var(--secondary-background-hover);
--component-node-widget-background-hovered: var(--secondary-background-selected);
--component-node-widget-background-selected: var(--color-charcoal-100);
--component-node-widget-background-disabled: var(--color-alpha-charcoal-600-30);
--component-node-widget-background-highlighted: var(--color-graphite-400);
} }
@theme inline { @theme inline {
@@ -257,6 +371,8 @@
--color-button-icon: var(--button-icon); --color-button-icon: var(--button-icon);
--color-button-surface: var(--button-surface); --color-button-surface: var(--button-surface);
--color-button-surface-contrast: var(--button-surface-contrast); --color-button-surface-contrast: var(--button-surface-contrast);
--color-subscription-button-gradient: var(--subscription-button-gradient);
--color-modal-card-button-surface: var(--modal-card-button-surface);
--color-dialog-surface: var(--dialog-surface); --color-dialog-surface: var(--dialog-surface);
--color-interface-menu-component-surface-hovered: var( --color-interface-menu-component-surface-hovered: var(
--interface-menu-component-surface-hovered --interface-menu-component-surface-hovered
@@ -268,6 +384,14 @@
--interface-menu-keybind-surface-default --interface-menu-keybind-surface-default
); );
--color-interface-panel-surface: var(--interface-panel-surface); --color-interface-panel-surface: var(--interface-panel-surface);
--color-interface-panel-hover-surface: var(--interface-panel-hover-surface);
--color-interface-panel-selected-surface: var(
--interface-panel-selected-surface
);
--color-interface-button-hover-surface: var(
--interface-button-hover-surface
);
--color-comfy-menu-bg: var(--comfy-menu-bg);
--color-interface-stroke: var(--interface-stroke); --color-interface-stroke: var(--interface-stroke);
--color-nav-background: var(--nav-background); --color-nav-background: var(--nav-background);
--color-node-border: var(--node-border); --color-node-border: var(--node-border);
@@ -312,6 +436,38 @@
--color-text-secondary: var(--text-secondary); --color-text-secondary: var(--text-secondary);
--color-text-primary: var(--text-primary); --color-text-primary: var(--text-primary);
--color-input-surface: var(--input-surface); --color-input-surface: var(--input-surface);
/* Component/Node design tokens */
--color-component-node-background: var(--component-node-background);
--color-component-node-border: var(--component-node-border);
--color-component-node-foreground: var(--component-node-foreground);
--color-component-node-foreground-secondary: var(--component-node-foreground-secondary);
--color-component-node-widget-background: var(--component-node-widget-background);
--color-component-node-widget-background-hovered: var(--component-node-widget-background-hovered);
--color-component-node-widget-background-selected: var(--component-node-widget-background-selected);
--color-component-node-widget-background-disabled: var(--component-node-widget-background-disabled);
--color-component-node-widget-background-highlighted: var(--component-node-widget-background-highlighted);
/* Semantic tokens */
--color-base-foreground: var(--base-foreground);
--color-muted-foreground: var(--muted-foreground);
--color-base-background: var(--base-background);
--color-secondary-background: var(--secondary-background);
--color-secondary-background-hover: var(--secondary-background-hover);
--color-secondary-background-selected: var(--secondary-background-selected);
--color-primary-background: var(--primary-background);
--color-primary-background-hover: var(--primary-background-hover);
--color-destructive-background: var(--destructive-background);
--color-destructive-background-hover: var(--destructive-background-hover);
--color-inverted-background-hover: var(--inverted-background-hover);
--color-warning-background: var(--warning-background);
--color-warning-background-hover: var(--warning-background-hover);
--color-border-default: var(--border-default);
--color-border-subtle: var(--border-subtle);
--color-muted-background: var(--muted-background);
--color-accent-background: var(--accent-background);
--color-brand-yellow: var(--brand-yellow);
--color-brand-blue: var(--brand-blue);
} }
@custom-variant dark-theme { @custom-variant dark-theme {
@@ -330,7 +486,6 @@
} }
} }
/* ===================== Scrollbar Utilities (Tailwind) ===================== /* ===================== Scrollbar Utilities (Tailwind) =====================
Usage: Add `scrollbar-custom` class to scrollable containers. Usage: Add `scrollbar-custom` class to scrollable containers.
The scrollbar styling adapts to light/dark theme automatically. The scrollbar styling adapts to light/dark theme automatically.
@@ -396,36 +551,40 @@ body {
.comfy-markdown { .comfy-markdown {
/* We assign the textarea and the Tiptap editor to the same CSS grid area to stack them on top of one another. */ /* We assign the textarea and the Tiptap editor to the same CSS grid area to stack them on top of one another. */
display: grid; display: grid;
} & > textarea,
.tiptap {
grid-area: 1 / 1 / 2 / 2;
}
.comfy-markdown > textarea { & > textarea {
grid-area: 1 / 1 / 2 / 2; opacity: 0;
} pointer-events: none;
}
.comfy-markdown .tiptap { &.editing {
grid-area: 1 / 1 / 2 / 2; & > textarea {
background-color: var(--comfy-input-bg); opacity: 1;
color: var(--input-text); pointer-events: all;
overflow: hidden; }
overflow-y: auto;
resize: none;
border: none;
box-sizing: border-box;
font-size: var(--comfy-textarea-font-size);
height: 100%;
padding: 0.5em;
}
.comfy-markdown.editing .tiptap { .tiptap {
display: none; opacity: 0;
} pointer-events: none;
}
}
.comfy-markdown .tiptap :first-child { .tiptap {
margin-top: 0; overflow-y: auto;
} font-size: var(--comfy-textarea-font-size);
.comfy-markdown .tiptap :last-child { :first-child {
margin-bottom: 0; margin-top: 0;
}
:last-child {
margin-bottom: 0;
}
}
} }
.comfy-markdown .tiptap blockquote { .comfy-markdown .tiptap blockquote {
@@ -1102,31 +1261,6 @@ audio.comfy-audio.empty-audio-widget {
padding: var(--comfy-tree-explorer-item-padding) !important; padding: var(--comfy-tree-explorer-item-padding) !important;
} }
/* Load3d styles */
.comfy-load-3d,
.comfy-load-3d-animation,
.comfy-preview-3d,
.comfy-preview-3d-animation {
display: flex;
flex-direction: column;
background: transparent;
flex: 1;
position: relative;
overflow: hidden;
}
.comfy-load-3d canvas,
.comfy-load-3d-animation canvas,
.comfy-preview-3d canvas,
.comfy-preview-3d-animation canvas,
.comfy-load-3d-viewer canvas {
display: flex;
width: 100% !important;
height: 100% !important;
}
/* End of Load3d styles */
/* [Desktop] Electron window specific styles */ /* [Desktop] Electron window specific styles */
.app-drag { .app-drag {
app-region: drag; app-region: drag;

View File

@@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 9.99996L11.9427 7.94263C11.6926 7.69267 11.3536 7.55225 11 7.55225C10.6464 7.55225 10.3074 7.69267 10.0573 7.94263L9 9M8 14H12.6667C13.403 14 14 13.403 14 12.6667V3.33333C14 2.59695 13.403 2 12.6667 2H3.33333C2.59695 2 2 2.59695 2 3.33333V8" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.51377 12.671L4.77612 14.3921C4.67222 14.6346 4.32853 14.6346 4.22463 14.3921L3.48699 12.671C3.45664 12.6002 3.40022 12.5437 3.32942 12.5134L1.60825 11.7757C1.36581 11.6718 1.36581 11.3282 1.60825 11.2243L3.32942 10.4866C3.40022 10.4563 3.45664 10.3998 3.48699 10.329L4.22463 8.60787C4.32853 8.36544 4.67222 8.36544 4.77612 8.60787L5.51377 10.329C5.54411 10.3998 5.60053 10.4563 5.67134 10.4866L7.39251 11.2243C7.63494 11.3282 7.63494 11.6718 7.39251 11.7757L5.67134 12.5134C5.60053 12.5437 5.54411 12.6002 5.51377 12.671Z" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
<path d="M5 5H5.0001" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,4 @@
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.13727 11.2201L6.27454 14.4398" stroke="var(--pin-color, var(--color-smoke-800, #8a8a8a))" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" />
<path d="M6.28085 6.68385C6.21652 6.92342 6.08664 7.1403 5.9058 7.31009C5.72497 7.47989 5.50035 7.59587 5.25721 7.645L3.95569 7.91742C3.71254 7.96655 3.48793 8.08253 3.30709 8.25233C3.12626 8.42212 2.99637 8.639 2.93204 8.87857L2.80091 9.36797C2.75515 9.53876 2.7791 9.72073 2.86751 9.87385C2.95591 10.027 3.10153 10.1387 3.27231 10.1845L10.9997 12.255C11.1705 12.3008 11.3525 12.2768 11.5056 12.1884C11.6587 12.1 11.7705 11.9544 11.8162 11.7836L11.9474 11.2942C12.0114 11.0546 12.0074 10.8018 11.9357 10.5643C11.864 10.3269 11.7274 10.1141 11.5414 9.95001L10.5505 9.06333C10.3645 8.89921 10.2279 8.68646 10.1562 8.44899C10.0845 8.21153 10.0805 7.95877 10.1446 7.71913L10.7933 5.29787C10.8391 5.12709 10.9508 4.98148 11.1039 4.89307C11.2571 4.80466 11.439 4.78071 11.6098 4.82647C11.9514 4.91799 12.3153 4.87008 12.6216 4.69327C12.9278 4.51646 13.1513 4.22523 13.2428 3.88366C13.3343 3.54209 13.2864 3.17815 13.1096 2.8719C12.9328 2.56566 12.6416 2.34219 12.3 2.25067L7.1484 0.870299C6.80683 0.778775 6.44289 0.826689 6.13665 1.0035C5.8304 1.18031 5.60694 1.47154 5.51541 1.81311C5.42389 2.15468 5.4718 2.51862 5.64861 2.82487C5.82542 3.13111 6.11665 3.35458 6.45822 3.4461C6.62901 3.49186 6.77462 3.6036 6.86302 3.75672C6.95143 3.90984 6.97539 4.09181 6.92962 4.2626L6.28085 6.68385Z" fill="var(--handle-color, var(--color-gold-600, #fd9903))" />
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -4,6 +4,13 @@ import { addDynamicIconSelectors } from '@iconify/tailwind'
import { iconCollection } from './src/iconCollection' import { iconCollection } from './src/iconCollection'
export default { export default {
theme: {
extend: {
boxShadow: {
interface: 'var(--interface-panel-box-shadow)'
}
}
},
plugins: [ plugins: [
addDynamicIconSelectors({ addDynamicIconSelectors({
iconSets: { iconSets: {

File diff suppressed because it is too large Load Diff

View File

@@ -474,3 +474,68 @@ export function formatDuration(milliseconds: number): string {
return parts.join(' ') return parts.join(' ')
} }
const IMAGE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp'] as const
const VIDEO_EXTENSIONS = ['mp4', 'webm', 'mov', 'avi'] as const
const AUDIO_EXTENSIONS = ['mp3', 'wav', 'ogg', 'flac'] as const
const THREE_D_EXTENSIONS = ['obj', 'fbx', 'gltf', 'glb'] as const
const MEDIA_TYPES = ['image', 'video', 'audio', '3D'] as const
type MediaType = (typeof MEDIA_TYPES)[number]
// Type guard helper for checking array membership
type ImageExtension = (typeof IMAGE_EXTENSIONS)[number]
type VideoExtension = (typeof VIDEO_EXTENSIONS)[number]
type AudioExtension = (typeof AUDIO_EXTENSIONS)[number]
type ThreeDExtension = (typeof THREE_D_EXTENSIONS)[number]
/**
* Truncates a filename while preserving the extension
* @param filename The filename to truncate
* @param maxLength Maximum length for the filename without extension
* @returns Truncated filename with extension preserved
*/
export function truncateFilename(
filename: string,
maxLength: number = 20
): string {
if (!filename || filename.length <= maxLength) {
return filename
}
const lastDotIndex = filename.lastIndexOf('.')
const nameWithoutExt =
lastDotIndex > -1 ? filename.substring(0, lastDotIndex) : filename
const extension = lastDotIndex > -1 ? filename.substring(lastDotIndex) : ''
// If the name without extension is short enough, return as is
if (nameWithoutExt.length <= maxLength) {
return filename
}
// Calculate how to split the truncation
const halfLength = Math.floor((maxLength - 3) / 2) // -3 for '...'
const start = nameWithoutExt.substring(0, halfLength)
const end = nameWithoutExt.substring(nameWithoutExt.length - halfLength)
return `${start}...${end}${extension}`
}
/**
* Determines the media type from a filename's extension (singular form)
* @param filename The filename to analyze
* @returns The media type: 'image', 'video', 'audio', or '3D'
*/
export function getMediaTypeFromFilename(filename: string): MediaType {
if (!filename) return 'image'
const ext = filename.split('.').pop()?.toLowerCase()
if (!ext) return 'image'
// Type-safe array includes check using type assertion
if (IMAGE_EXTENSIONS.includes(ext as ImageExtension)) return 'image'
if (VIDEO_EXTENSIONS.includes(ext as VideoExtension)) return 'video'
if (AUDIO_EXTENSIONS.includes(ext as AudioExtension)) return 'audio'
if (THREE_D_EXTENSIONS.includes(ext as ThreeDExtension)) return '3D'
return 'image'
}

View File

@@ -14,7 +14,7 @@ import { cn } from '@comfyorg/tailwind-utils'
// Use with conditional classes (ternary) // Use with conditional classes (ternary)
<button <button
:class="cn('px-4 py-2', isActive ? 'bg-blue-500' : 'bg-gray-500')" :class="cn('px-4 py-2', isActive ? 'bg-blue-500' : 'bg-smoke-500')"
/> />
``` ```

280
pnpm-lock.yaml generated
View File

@@ -12,15 +12,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
@@ -69,6 +63,9 @@ catalogs:
'@primevue/themes': '@primevue/themes':
specifier: ^4.2.5 specifier: ^4.2.5
version: 4.2.5 version: 4.2.5
'@sentry/vite-plugin':
specifier: ^4.6.0
version: 4.6.0
'@sentry/vue': '@sentry/vue':
specifier: ^8.48.0 specifier: ^8.48.0
version: 8.48.0 version: 8.48.0
@@ -87,9 +84,6 @@ catalogs:
'@trivago/prettier-plugin-sort-imports': '@trivago/prettier-plugin-sort-imports':
specifier: ^5.2.0 specifier: ^5.2.0
version: 5.2.2 version: 5.2.2
'@types/eslint-plugin-tailwindcss':
specifier: ^3.17.0
version: 3.17.0
'@types/fs-extra': '@types/fs-extra':
specifier: ^11.0.4 specifier: ^11.0.4
version: 11.0.4 version: 11.0.4
@@ -153,9 +147,6 @@ catalogs:
eslint-plugin-storybook: eslint-plugin-storybook:
specifier: ^9.1.6 specifier: ^9.1.6
version: 9.1.6 version: 9.1.6
eslint-plugin-tailwindcss:
specifier: 4.0.0-beta.0
version: 4.0.0-beta.0
eslint-plugin-unused-imports: eslint-plugin-unused-imports:
specifier: ^4.2.0 specifier: ^4.2.0
version: 4.2.0 version: 4.2.0
@@ -504,6 +495,9 @@ importers:
'@prettier/plugin-oxc': '@prettier/plugin-oxc':
specifier: 'catalog:' specifier: 'catalog:'
version: 0.0.4 version: 0.0.4
'@sentry/vite-plugin':
specifier: 'catalog:'
version: 4.6.0
'@storybook/addon-docs': '@storybook/addon-docs':
specifier: 'catalog:' specifier: 'catalog:'
version: 9.1.1(@types/react@19.1.9)(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))) version: 9.1.1(@types/react@19.1.9)(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)))
@@ -519,9 +513,6 @@ importers:
'@trivago/prettier-plugin-sort-imports': '@trivago/prettier-plugin-sort-imports':
specifier: 'catalog:' specifier: 'catalog:'
version: 5.2.2(@vue/compiler-sfc@3.5.13)(prettier@3.6.2) version: 5.2.2(@vue/compiler-sfc@3.5.13)(prettier@3.6.2)
'@types/eslint-plugin-tailwindcss':
specifier: 'catalog:'
version: 3.17.0
'@types/fs-extra': '@types/fs-extra':
specifier: 'catalog:' specifier: 'catalog:'
version: 11.0.4 version: 11.0.4
@@ -570,9 +561,6 @@ importers:
eslint-plugin-storybook: eslint-plugin-storybook:
specifier: 'catalog:' specifier: 'catalog:'
version: 9.1.6(eslint@9.35.0(jiti@2.4.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)))(typescript@5.9.2) version: 9.1.6(eslint@9.35.0(jiti@2.4.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)))(typescript@5.9.2)
eslint-plugin-tailwindcss:
specifier: 'catalog:'
version: 4.0.0-beta.0(tailwindcss@4.1.12)
eslint-plugin-unused-imports: eslint-plugin-unused-imports:
specifier: 'catalog:' specifier: 'catalog:'
version: 4.2.0(@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))(eslint@9.35.0(jiti@2.4.2)) version: 4.2.0(@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))(eslint@9.35.0(jiti@2.4.2))
@@ -2784,14 +2772,78 @@ packages:
resolution: {integrity: sha512-csILVupc5RkrsTrncuUTGmlB56FQSFjXPYWG8I8yBTGlXEJ+o8oTuF6+55R4vbw3EIzBveXWi4kEBbnQlXW/eg==} resolution: {integrity: sha512-csILVupc5RkrsTrncuUTGmlB56FQSFjXPYWG8I8yBTGlXEJ+o8oTuF6+55R4vbw3EIzBveXWi4kEBbnQlXW/eg==}
engines: {node: '>=14.18'} engines: {node: '>=14.18'}
'@sentry/babel-plugin-component-annotate@4.6.0':
resolution: {integrity: sha512-3soTX50JPQQ51FSbb4qvNBf4z/yP7jTdn43vMTp9E4IxvJ9HKJR7OEuKkCMszrZmWsVABXl02msqO7QisePdiQ==}
engines: {node: '>= 14'}
'@sentry/browser@8.48.0': '@sentry/browser@8.48.0':
resolution: {integrity: sha512-fuuVULB5/1vI8NoIwXwR3xwhJJqk+y4RdSdajExGF7nnUDBpwUJyXsmYJnOkBO+oLeEs58xaCpotCKiPUNnE3g==} resolution: {integrity: sha512-fuuVULB5/1vI8NoIwXwR3xwhJJqk+y4RdSdajExGF7nnUDBpwUJyXsmYJnOkBO+oLeEs58xaCpotCKiPUNnE3g==}
engines: {node: '>=14.18'} engines: {node: '>=14.18'}
'@sentry/bundler-plugin-core@4.6.0':
resolution: {integrity: sha512-Fub2XQqrS258jjS8qAxLLU1k1h5UCNJ76i8m4qZJJdogWWaF8t00KnnTyp9TEDJzrVD64tRXS8+HHENxmeUo3g==}
engines: {node: '>= 14'}
'@sentry/cli-darwin@2.57.0':
resolution: {integrity: sha512-v1wYQU3BcCO+Z3OVxxO+EnaW4oQhuOza6CXeYZ0z5ftza9r0QQBLz3bcZKTVta86xraNm0z8GDlREwinyddOxQ==}
engines: {node: '>=10'}
os: [darwin]
'@sentry/cli-linux-arm64@2.57.0':
resolution: {integrity: sha512-Kh1jTsMV5Fy/RvB381N/woXe1qclRMqsG6kM3Gq6m6afEF/+k3PyQdNW3HXAola6d63EptokLtxPG2xjWQ+w9Q==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux, freebsd, android]
'@sentry/cli-linux-arm@2.57.0':
resolution: {integrity: sha512-uNHB8xyygqfMd1/6tFzl9NUkuVefg7jdZtM/vVCQVaF/rJLWZ++Wms+LLhYyKXKN8yd7J9wy7kTEl4Qu4jWbGQ==}
engines: {node: '>=10'}
cpu: [arm]
os: [linux, freebsd, android]
'@sentry/cli-linux-i686@2.57.0':
resolution: {integrity: sha512-EYXghoK/tKd0zqz+KD/ewXXE3u1HLCwG89krweveytBy/qw7M5z58eFvw+iGb1Vnbl1f/fRD0G4E0AbEsPfmpg==}
engines: {node: '>=10'}
cpu: [x86, ia32]
os: [linux, freebsd, android]
'@sentry/cli-linux-x64@2.57.0':
resolution: {integrity: sha512-CyZrP/ssHmAPLSzfd4ydy7icDnwmDD6o3QjhkWwVFmCd+9slSBMQxpIqpamZmrWE6X4R+xBRbSUjmdoJoZ5yMw==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux, freebsd, android]
'@sentry/cli-win32-arm64@2.57.0':
resolution: {integrity: sha512-wji/GGE4Lh5I/dNCsuVbg6fRvttvZRG6db1yPW1BSvQRh8DdnVy1CVp+HMqSq0SRy/S4z60j2u+m4yXMoCL+5g==}
engines: {node: '>=10'}
cpu: [arm64]
os: [win32]
'@sentry/cli-win32-i686@2.57.0':
resolution: {integrity: sha512-hWvzyD7bTPh3b55qvJ1Okg3Wbl0Km8xcL6KvS7gfBl6uss+I6RldmQTP0gJKdHSdf/QlJN1FK0b7bLnCB3wHsg==}
engines: {node: '>=10'}
cpu: [x86, ia32]
os: [win32]
'@sentry/cli-win32-x64@2.57.0':
resolution: {integrity: sha512-QWYV/Y0sbpDSTyA4XQBOTaid4a6H2Iwa1Z8UI+qNxFlk0ADSEgIqo2NrRHDU8iRnghTkecQNX1NTt/7mXN3f/A==}
engines: {node: '>=10'}
cpu: [x64]
os: [win32]
'@sentry/cli@2.57.0':
resolution: {integrity: sha512-oC4HPrVIX06GvUTgK0i+WbNgIA9Zl5YEcwf9N4eWFJJmjonr2j4SML9Hn2yNENbUWDgwepy4MLod3P8rM4bk/w==}
engines: {node: '>= 10'}
hasBin: true
'@sentry/core@8.48.0': '@sentry/core@8.48.0':
resolution: {integrity: sha512-VGwYgTfLpvJ5LRO5A+qWo1gpo6SfqaGXL9TOzVgBucAdpzbrYHpZ87sEarDVq/4275uk1b0S293/mfsskFczyw==} resolution: {integrity: sha512-VGwYgTfLpvJ5LRO5A+qWo1gpo6SfqaGXL9TOzVgBucAdpzbrYHpZ87sEarDVq/4275uk1b0S293/mfsskFczyw==}
engines: {node: '>=14.18'} engines: {node: '>=14.18'}
'@sentry/vite-plugin@4.6.0':
resolution: {integrity: sha512-fMR2d+EHwbzBa0S1fp45SNUTProxmyFBp+DeBWWQOSP9IU6AH6ea2rqrpMAnp/skkcdW4z4LSRrOEpMZ5rWXLw==}
engines: {node: '>= 14'}
'@sentry/vue@8.48.0': '@sentry/vue@8.48.0':
resolution: {integrity: sha512-hqm9X7hz1vMQQB1HBYezrDBQihZk6e/MxWIG1wMJoClcBnD1Sh7y+D36UwaQlR4Gr/Ftiz+Bb0DxuAYHoUS4ow==} resolution: {integrity: sha512-hqm9X7hz1vMQQB1HBYezrDBQihZk6e/MxWIG1wMJoClcBnD1Sh7y+D36UwaQlR4Gr/Ftiz+Bb0DxuAYHoUS4ow==}
engines: {node: '>=14.18'} engines: {node: '>=14.18'}
@@ -3155,9 +3207,6 @@ packages:
'@types/diff-match-patch@1.0.36': '@types/diff-match-patch@1.0.36':
resolution: {integrity: sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==} resolution: {integrity: sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==}
'@types/eslint-plugin-tailwindcss@3.17.0':
resolution: {integrity: sha512-ucQGf2YIdTcndYcxRU3UdZgmhUHsOlbIF4BaRtl0op+7k2JmqM2i3aXZ6XIcfZgVq1ZKov7VM5c/BR81ukmkyg==}
'@types/estree@1.0.5': '@types/estree@1.0.5':
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
@@ -3701,6 +3750,10 @@ packages:
resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==} resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
agent-base@6.0.2:
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
engines: {node: '>= 6.0.0'}
agent-base@7.1.4: agent-base@7.1.4:
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
engines: {node: '>= 14'} engines: {node: '>= 14'}
@@ -4700,12 +4753,6 @@ packages:
eslint: '>=8' eslint: '>=8'
storybook: ^9.1.6 storybook: ^9.1.6
eslint-plugin-tailwindcss@4.0.0-beta.0:
resolution: {integrity: sha512-WWCajZgQu38Sd67ZCl2W6i3MRzqB0d+H8s4qV9iB6lBJbsDOIpIlj6R1Fj2FXkoWErbo05pZnZYbCGIU9o/DsA==}
engines: {node: '>=18.12.0'}
peerDependencies:
tailwindcss: ^3.4.0 || ^4.0.0
eslint-plugin-unused-imports@4.2.0: eslint-plugin-unused-imports@4.2.0:
resolution: {integrity: sha512-hLbJ2/wnjKq4kGA9AUaExVFIbNzyxYdVo49QZmKCnhk5pc9wcYRbfgLHvWJ8tnsdcseGhoUAddm9gn/lt+d74w==} resolution: {integrity: sha512-hLbJ2/wnjKq4kGA9AUaExVFIbNzyxYdVo49QZmKCnhk5pc9wcYRbfgLHvWJ8tnsdcseGhoUAddm9gn/lt+d74w==}
peerDependencies: peerDependencies:
@@ -4980,6 +5027,9 @@ packages:
resolution: {integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==} resolution: {integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==}
engines: {node: '>=14.14'} engines: {node: '>=14.14'}
fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
fsevents@2.3.2: fsevents@2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -5060,6 +5110,10 @@ packages:
engines: {node: 20 || >=22} engines: {node: 20 || >=22}
hasBin: true hasBin: true
glob@9.3.5:
resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==}
engines: {node: '>=16 || 14 >=14.17'}
global-directory@4.0.1: global-directory@4.0.1:
resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -5189,6 +5243,10 @@ packages:
resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
engines: {node: '>= 14'} engines: {node: '>= 14'}
https-proxy-agent@5.0.1:
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
engines: {node: '>= 6'}
https-proxy-agent@7.0.6: https-proxy-agent@7.0.6:
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
engines: {node: '>= 14'} engines: {node: '>= 14'}
@@ -5874,6 +5932,10 @@ packages:
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==}
magic-string@0.30.8:
resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==}
engines: {node: '>=12'}
magicast@0.3.5: magicast@0.3.5:
resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==}
@@ -6094,6 +6156,10 @@ packages:
resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
engines: {node: '>=10'} engines: {node: '>=10'}
minimatch@8.0.4:
resolution: {integrity: sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==}
engines: {node: '>=16 || 14 >=14.17'}
minimatch@9.0.1: minimatch@9.0.1:
resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==} resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==}
engines: {node: '>=16 || 14 >=14.17'} engines: {node: '>=16 || 14 >=14.17'}
@@ -6109,6 +6175,10 @@ packages:
minimist@1.2.8: minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
minipass@4.2.8:
resolution: {integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==}
engines: {node: '>=8'}
minipass@7.1.2: minipass@7.1.2:
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
engines: {node: '>=16 || 14 >=14.17'} engines: {node: '>=16 || 14 >=14.17'}
@@ -6548,6 +6618,10 @@ packages:
process-nextick-args@2.0.1: process-nextick-args@2.0.1:
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
progress@2.0.3:
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
engines: {node: '>=0.4.0'}
promise@7.3.1: promise@7.3.1:
resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==} resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==}
@@ -7163,11 +7237,6 @@ packages:
resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==}
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
tailwind-api-utils@1.0.3:
resolution: {integrity: sha512-KpzUHkH1ug1sq4394SLJX38ZtpeTiqQ1RVyFTTSY2XuHsNSTWUkRo108KmyyrMWdDbQrLYkSHaNKj/a3bmA4sQ==}
peerDependencies:
tailwindcss: ^3.3.0 || ^4.0.0 || ^4.0.0-beta
tailwind-merge@2.6.0: tailwind-merge@2.6.0:
resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==} resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==}
@@ -7452,6 +7521,9 @@ packages:
'@nuxt/kit': '@nuxt/kit':
optional: true optional: true
unplugin@1.0.1:
resolution: {integrity: sha512-aqrHaVBWW1JVKBHmGo33T5TxeL0qWzfvjWokObHA9bYmN7eNDkwOxmLjhioHl9878qDFMAaT51XNroRyuz7WxA==}
unplugin@1.16.1: unplugin@1.16.1:
resolution: {integrity: sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==} resolution: {integrity: sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==}
engines: {node: '>=14.0.0'} engines: {node: '>=14.0.0'}
@@ -7626,6 +7698,9 @@ 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.2:
resolution: {integrity: sha512-ch3/SKBtxdZq18vsEntiGCdSszCRNfhX5QaTxjSacCAXLlNQRXfXo+ANjoQEYJMsJOJy1/vHF6Tkc4s85MS+zw==}
vue-demi@0.14.10: vue-demi@0.14.10:
resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
engines: {node: '>=12'} engines: {node: '>=12'}
@@ -7722,6 +7797,13 @@ packages:
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
engines: {node: '>=12'} engines: {node: '>=12'}
webpack-sources@3.3.3:
resolution: {integrity: sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==}
engines: {node: '>=10.13.0'}
webpack-virtual-modules@0.5.0:
resolution: {integrity: sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==}
webpack-virtual-modules@0.6.2: webpack-virtual-modules@0.6.2:
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
@@ -10228,6 +10310,8 @@ snapshots:
'@sentry-internal/browser-utils': 8.48.0 '@sentry-internal/browser-utils': 8.48.0
'@sentry/core': 8.48.0 '@sentry/core': 8.48.0
'@sentry/babel-plugin-component-annotate@4.6.0': {}
'@sentry/browser@8.48.0': '@sentry/browser@8.48.0':
dependencies: dependencies:
'@sentry-internal/browser-utils': 8.48.0 '@sentry-internal/browser-utils': 8.48.0
@@ -10236,8 +10320,74 @@ snapshots:
'@sentry-internal/replay-canvas': 8.48.0 '@sentry-internal/replay-canvas': 8.48.0
'@sentry/core': 8.48.0 '@sentry/core': 8.48.0
'@sentry/bundler-plugin-core@4.6.0':
dependencies:
'@babel/core': 7.27.1
'@sentry/babel-plugin-component-annotate': 4.6.0
'@sentry/cli': 2.57.0
dotenv: 16.6.1
find-up: 5.0.0
glob: 9.3.5
magic-string: 0.30.8
unplugin: 1.0.1
transitivePeerDependencies:
- encoding
- supports-color
'@sentry/cli-darwin@2.57.0':
optional: true
'@sentry/cli-linux-arm64@2.57.0':
optional: true
'@sentry/cli-linux-arm@2.57.0':
optional: true
'@sentry/cli-linux-i686@2.57.0':
optional: true
'@sentry/cli-linux-x64@2.57.0':
optional: true
'@sentry/cli-win32-arm64@2.57.0':
optional: true
'@sentry/cli-win32-i686@2.57.0':
optional: true
'@sentry/cli-win32-x64@2.57.0':
optional: true
'@sentry/cli@2.57.0':
dependencies:
https-proxy-agent: 5.0.1
node-fetch: 2.7.0
progress: 2.0.3
proxy-from-env: 1.1.0
which: 2.0.2
optionalDependencies:
'@sentry/cli-darwin': 2.57.0
'@sentry/cli-linux-arm': 2.57.0
'@sentry/cli-linux-arm64': 2.57.0
'@sentry/cli-linux-i686': 2.57.0
'@sentry/cli-linux-x64': 2.57.0
'@sentry/cli-win32-arm64': 2.57.0
'@sentry/cli-win32-i686': 2.57.0
'@sentry/cli-win32-x64': 2.57.0
transitivePeerDependencies:
- encoding
- supports-color
'@sentry/core@8.48.0': {} '@sentry/core@8.48.0': {}
'@sentry/vite-plugin@4.6.0':
dependencies:
'@sentry/bundler-plugin-core': 4.6.0
unplugin: 1.0.1
transitivePeerDependencies:
- encoding
- supports-color
'@sentry/vue@8.48.0(pinia@2.2.2(typescript@5.9.2)(vue@3.5.13(typescript@5.9.2)))(vue@3.5.13(typescript@5.9.2))': '@sentry/vue@8.48.0(pinia@2.2.2(typescript@5.9.2)(vue@3.5.13(typescript@5.9.2)))(vue@3.5.13(typescript@5.9.2))':
dependencies: dependencies:
'@sentry/browser': 8.48.0 '@sentry/browser': 8.48.0
@@ -10308,7 +10458,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.1 vue-component-type-helpers: 3.1.2
'@swc/helpers@0.5.17': '@swc/helpers@0.5.17':
dependencies: dependencies:
@@ -10613,8 +10763,6 @@ snapshots:
'@types/diff-match-patch@1.0.36': {} '@types/diff-match-patch@1.0.36': {}
'@types/eslint-plugin-tailwindcss@3.17.0': {}
'@types/estree@1.0.5': {} '@types/estree@1.0.5': {}
'@types/estree@1.0.8': {} '@types/estree@1.0.8': {}
@@ -11239,6 +11387,12 @@ snapshots:
address@1.2.2: {} address@1.2.2: {}
agent-base@6.0.2:
dependencies:
debug: 4.4.3
transitivePeerDependencies:
- supports-color
agent-base@7.1.4: {} agent-base@7.1.4: {}
agentkeepalive@4.6.0: agentkeepalive@4.6.0:
@@ -12380,14 +12534,6 @@ snapshots:
- supports-color - supports-color
- typescript - typescript
eslint-plugin-tailwindcss@4.0.0-beta.0(tailwindcss@4.1.12):
dependencies:
fast-glob: 3.3.3
postcss: 8.5.6
synckit: 0.11.11
tailwind-api-utils: 1.0.3(tailwindcss@4.1.12)
tailwindcss: 4.1.12
eslint-plugin-unused-imports@4.2.0(@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))(eslint@9.35.0(jiti@2.4.2)): eslint-plugin-unused-imports@4.2.0(@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))(eslint@9.35.0(jiti@2.4.2)):
dependencies: dependencies:
eslint: 9.35.0(jiti@2.4.2) eslint: 9.35.0(jiti@2.4.2)
@@ -12746,6 +12892,8 @@ snapshots:
jsonfile: 6.2.0 jsonfile: 6.2.0
universalify: 2.0.1 universalify: 2.0.1
fs.realpath@1.0.0: {}
fsevents@2.3.2: fsevents@2.3.2:
optional: true optional: true
@@ -12840,6 +12988,13 @@ snapshots:
package-json-from-dist: 1.0.0 package-json-from-dist: 1.0.0
path-scurry: 2.0.0 path-scurry: 2.0.0
glob@9.3.5:
dependencies:
fs.realpath: 1.0.0
minimatch: 8.0.4
minipass: 4.2.8
path-scurry: 1.11.1
global-directory@4.0.1: global-directory@4.0.1:
dependencies: dependencies:
ini: 4.1.1 ini: 4.1.1
@@ -12970,6 +13125,13 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
https-proxy-agent@5.0.1:
dependencies:
agent-base: 6.0.2
debug: 4.4.3
transitivePeerDependencies:
- supports-color
https-proxy-agent@7.0.6: https-proxy-agent@7.0.6:
dependencies: dependencies:
agent-base: 7.1.4 agent-base: 7.1.4
@@ -13659,6 +13821,10 @@ snapshots:
dependencies: dependencies:
'@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/sourcemap-codec': 1.5.5
magic-string@0.30.8:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
magicast@0.3.5: magicast@0.3.5:
dependencies: dependencies:
'@babel/parser': 7.28.4 '@babel/parser': 7.28.4
@@ -14064,6 +14230,10 @@ snapshots:
dependencies: dependencies:
brace-expansion: 2.0.2 brace-expansion: 2.0.2
minimatch@8.0.4:
dependencies:
brace-expansion: 2.0.2
minimatch@9.0.1: minimatch@9.0.1:
dependencies: dependencies:
brace-expansion: 2.0.2 brace-expansion: 2.0.2
@@ -14078,6 +14248,8 @@ snapshots:
minimist@1.2.8: {} minimist@1.2.8: {}
minipass@4.2.8: {}
minipass@7.1.2: {} minipass@7.1.2: {}
minizlib@3.0.2: minizlib@3.0.2:
@@ -14583,6 +14755,8 @@ snapshots:
process-nextick-args@2.0.1: {} process-nextick-args@2.0.1: {}
progress@2.0.3: {}
promise@7.3.1: promise@7.3.1:
dependencies: dependencies:
asap: 2.0.6 asap: 2.0.6
@@ -15431,13 +15605,6 @@ snapshots:
string-width: 4.2.3 string-width: 4.2.3
strip-ansi: 6.0.1 strip-ansi: 6.0.1
tailwind-api-utils@1.0.3(tailwindcss@4.1.12):
dependencies:
enhanced-resolve: 5.18.3
jiti: 2.5.1
local-pkg: 1.1.2
tailwindcss: 4.1.12
tailwind-merge@2.6.0: {} tailwind-merge@2.6.0: {}
tailwindcss-primeui@0.6.1(tailwindcss@4.1.12): tailwindcss-primeui@0.6.1(tailwindcss@4.1.12):
@@ -15729,6 +15896,13 @@ snapshots:
- rollup - rollup
- supports-color - supports-color
unplugin@1.0.1:
dependencies:
acorn: 8.15.0
chokidar: 3.6.0
webpack-sources: 3.3.3
webpack-virtual-modules: 0.5.0
unplugin@1.16.1: unplugin@1.16.1:
dependencies: dependencies:
acorn: 8.15.0 acorn: 8.15.0
@@ -15983,6 +16157,8 @@ snapshots:
vue-component-type-helpers@3.1.1: {} vue-component-type-helpers@3.1.1: {}
vue-component-type-helpers@3.1.2: {}
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:
vue: 3.5.13(typescript@5.9.2) vue: 3.5.13(typescript@5.9.2)
@@ -16075,6 +16251,10 @@ snapshots:
webidl-conversions@7.0.0: {} webidl-conversions@7.0.0: {}
webpack-sources@3.3.3: {}
webpack-virtual-modules@0.5.0: {}
webpack-virtual-modules@0.6.2: {} webpack-virtual-modules@0.6.2: {}
websocket-driver@0.7.4: websocket-driver@0.7.4:

View File

@@ -24,13 +24,13 @@ catalog:
'@primevue/forms': ^4.2.5 '@primevue/forms': ^4.2.5
'@primevue/icons': 4.2.5 '@primevue/icons': 4.2.5
'@primevue/themes': ^4.2.5 '@primevue/themes': ^4.2.5
'@sentry/vite-plugin': ^4.6.0
'@sentry/vue': ^8.48.0 '@sentry/vue': ^8.48.0
'@storybook/addon-docs': ^9.1.1 '@storybook/addon-docs': ^9.1.1
'@storybook/vue3': ^9.1.1 '@storybook/vue3': ^9.1.1
'@storybook/vue3-vite': ^9.1.1 '@storybook/vue3-vite': ^9.1.1
'@tailwindcss/vite': ^4.1.12 '@tailwindcss/vite': ^4.1.12
'@trivago/prettier-plugin-sort-imports': ^5.2.0 '@trivago/prettier-plugin-sort-imports': ^5.2.0
'@types/eslint-plugin-tailwindcss': ^3.17.0
'@types/fs-extra': ^11.0.4 '@types/fs-extra': ^11.0.4
'@types/jsdom': ^21.1.7 '@types/jsdom': ^21.1.7
'@types/node': ^20.14.8 '@types/node': ^20.14.8
@@ -52,7 +52,6 @@ catalog:
eslint-plugin-import-x: ^4.16.1 eslint-plugin-import-x: ^4.16.1
eslint-plugin-prettier: ^5.5.4 eslint-plugin-prettier: ^5.5.4
eslint-plugin-storybook: ^9.1.6 eslint-plugin-storybook: ^9.1.6
eslint-plugin-tailwindcss: 4.0.0-beta.0
eslint-plugin-unused-imports: ^4.2.0 eslint-plugin-unused-imports: ^4.2.0
eslint-plugin-vue: ^10.4.0 eslint-plugin-vue: ^10.4.0
firebase: ^11.6.0 firebase: ^11.6.0
@@ -64,6 +63,7 @@ catalog:
knip: ^5.62.0 knip: ^5.62.0
lint-staged: ^15.2.7 lint-staged: ^15.2.7
markdown-table: ^3.0.4 markdown-table: ^3.0.4
mixpanel-browser: ^2.71.0
nx: 21.4.1 nx: 21.4.1
picocolors: ^1.1.1 picocolors: ^1.1.1
pinia: ^2.1.7 pinia: ^2.1.7
@@ -99,7 +99,6 @@ catalog:
zod: ^3.23.8 zod: ^3.23.8
zod-to-json-schema: ^3.24.1 zod-to-json-schema: ^3.24.1
zod-validation-error: ^3.3.0 zod-validation-error: ^3.3.0
mixpanel-browser: ^2.71.0
cleanupUnusedCatalogs: true cleanupUnusedCatalogs: true
@@ -113,6 +112,7 @@ onlyBuiltDependencies:
- '@playwright/browser-chromium' - '@playwright/browser-chromium'
- '@playwright/browser-firefox' - '@playwright/browser-firefox'
- '@playwright/browser-webkit' - '@playwright/browser-webkit'
- '@sentry/cli'
- '@tailwindcss/oxide' - '@tailwindcss/oxide'
- esbuild - esbuild
- nx - nx

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 KiB

View File

@@ -22,7 +22,7 @@
}, },
"litegraph_base": { "litegraph_base": {
"BACKGROUND_IMAGE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII=", "BACKGROUND_IMAGE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII=",
"CLEAR_BACKGROUND_COLOR": "#222", "CLEAR_BACKGROUND_COLOR": "#141414",
"NODE_TITLE_COLOR": "#999", "NODE_TITLE_COLOR": "#999",
"NODE_SELECTED_TITLE_COLOR": "#FFF", "NODE_SELECTED_TITLE_COLOR": "#FFF",
"NODE_TEXT_SIZE": 14, "NODE_TEXT_SIZE": 14,
@@ -52,7 +52,7 @@
"comfy_base": { "comfy_base": {
"fg-color": "#fff", "fg-color": "#fff",
"bg-color": "#202020", "bg-color": "#202020",
"comfy-menu-bg": "#353535", "comfy-menu-bg": "#171718",
"comfy-menu-secondary-bg": "#303030", "comfy-menu-secondary-bg": "#303030",
"comfy-input-bg": "#222", "comfy-input-bg": "#222",
"input-text": "#ddd", "input-text": "#ddd",

View File

@@ -68,7 +68,12 @@
"content-fg": "#222", "content-fg": "#222",
"content-hover-bg": "#adadad", "content-hover-bg": "#adadad",
"content-hover-fg": "#222", "content-hover-fg": "#222",
"bar-shadow": "rgba(16, 16, 16, 0.25) 0 0 0.5rem" "bar-shadow": "rgba(16, 16, 16, 0.25) 0 0 0.5rem",
"interface-panel-box-shadow": "1px 1px 8px 0 rgba(0, 0, 0, 0.2)",
"interface-panel-drop-shadow": "1px 1px 4px rgba(0, 0, 0, 0.4)",
"interface-panel-hover-surface": "var(--color-gray-200)",
"interface-panel-selected-surface": "color-mix(in srgb, var(--interface-panel-surface) 78%, var(--contrast-mix-color))",
"contrast-mix-color": "#000"
} }
} }
} }

View File

@@ -1,11 +1,11 @@
<template> <template>
<div v-if="!workspaceStore.focusMode" class="ml-2 flex pt-1"> <div v-if="!workspaceStore.focusMode" class="ml-1 flex gap-x-0.5 pt-1">
<div class="min-w-0 flex-1"> <div class="min-w-0 flex-1">
<SubgraphBreadcrumb /> <SubgraphBreadcrumb />
</div> </div>
<div <div
class="actionbar-container pointer-events-auto mx-1 flex h-12 items-center rounded-lg px-2 shadow-md" class="actionbar-container pointer-events-auto flex h-12 items-center rounded-lg border border-[var(--interface-stroke)] px-2 shadow-interface"
> >
<!-- Support for legacy topbar elements attached by custom scripts, hidden if no elements present --> <!-- Support for legacy topbar elements attached by custom scripts, hidden if no elements present -->
<div <div
@@ -48,6 +48,5 @@ onMounted(() => {
<style scoped> <style scoped>
.actionbar-container { .actionbar-container {
background-color: var(--comfy-menu-bg); background-color: var(--comfy-menu-bg);
border: 1px solid var(--p-panel-border-color);
} }
</style> </style>

View File

@@ -48,6 +48,7 @@ import { computed, nextTick, onMounted, ref, watch } from 'vue'
import { t } from '@/i18n' import { t } from '@/i18n'
import { useSettingStore } from '@/platform/settings/settingStore' import { useSettingStore } from '@/platform/settings/settingStore'
import { useTelemetry } from '@/platform/telemetry'
import { cn } from '@/utils/tailwindUtil' import { cn } from '@/utils/tailwindUtil'
import ComfyRunButton from './ComfyRunButton' import ComfyRunButton from './ComfyRunButton'
@@ -132,6 +133,15 @@ watch(visible, async (newVisible) => {
} }
}) })
/**
* Track run button handle drag start using mousedown on the drag handle.
*/
useEventListener(dragHandleRef, 'mousedown', () => {
useTelemetry()?.trackUiButtonClicked({
button_id: 'actionbar_run_handle_drag_start'
})
})
const lastDragState = ref({ const lastDragState = ref({
x: x.value, x: x.value,
y: y.value, y: y.value,
@@ -258,7 +268,9 @@ const panelClass = computed(() =>
cn( cn(
'actionbar pointer-events-auto z1000', 'actionbar pointer-events-auto z1000',
isDragging.value && 'select-none pointer-events-none', isDragging.value && 'select-none pointer-events-none',
isDocked.value ? 'p-0 static mr-2 border-none bg-transparent' : 'fixed' isDocked.value
? 'p-0 static mr-2 border-none bg-transparent'
: 'fixed shadow-interface'
) )
) )
</script> </script>

View File

@@ -100,7 +100,7 @@ import BatchCountEdit from '../BatchCountEdit.vue'
const workspaceStore = useWorkspaceStore() const workspaceStore = useWorkspaceStore()
const queueCountStore = storeToRefs(useQueuePendingTaskCountStore()) const queueCountStore = storeToRefs(useQueuePendingTaskCountStore())
const { mode: queueMode } = storeToRefs(useQueueSettingsStore()) const { mode: queueMode, batchCount } = storeToRefs(useQueueSettingsStore())
const { t } = useI18n() const { t } = useI18n()
const queueModeMenuItemLookup = computed(() => { const queueModeMenuItemLookup = computed(() => {
@@ -118,6 +118,9 @@ const queueModeMenuItemLookup = computed(() => {
label: `${t('menu.run')} (${t('menu.onChange')})`, label: `${t('menu.run')} (${t('menu.onChange')})`,
tooltip: t('menu.onChangeTooltip'), tooltip: t('menu.onChangeTooltip'),
command: () => { command: () => {
useTelemetry()?.trackUiButtonClicked({
button_id: 'queue_mode_option_run_on_change_selected'
})
queueMode.value = 'change' queueMode.value = 'change'
} }
} }
@@ -128,6 +131,9 @@ const queueModeMenuItemLookup = computed(() => {
label: `${t('menu.run')} (${t('menu.instant')})`, label: `${t('menu.run')} (${t('menu.instant')})`,
tooltip: t('menu.instantTooltip'), tooltip: t('menu.instantTooltip'),
command: () => { command: () => {
useTelemetry()?.trackUiButtonClicked({
button_id: 'queue_mode_option_run_instant_selected'
})
queueMode.value = 'instant' queueMode.value = 'instant'
} }
} }
@@ -158,11 +164,18 @@ const queuePrompt = async (e: Event) => {
? 'Comfy.QueuePromptFront' ? 'Comfy.QueuePromptFront'
: 'Comfy.QueuePrompt' : 'Comfy.QueuePrompt'
if (isCloud) { if (batchCount.value > 1) {
useTelemetry()?.trackRunButton({ subscribe_to_run: false }) useTelemetry()?.trackUiButtonClicked({
button_id: 'queue_run_multiple_batches_submitted'
})
} }
await commandStore.execute(commandId) await commandStore.execute(commandId, {
metadata: {
subscribe_to_run: false,
trigger_source: 'button'
}
})
} }
</script> </script>

View File

@@ -1,6 +1,6 @@
<template> <template>
<div <div
class="subgraph-breadcrumb w-auto drop-shadow-md" class="subgraph-breadcrumb w-auto drop-shadow-[var(--interface-panel-drop-shadow)]"
:class="{ :class="{
'subgraph-breadcrumb-collapse': collapseTabs, 'subgraph-breadcrumb-collapse': collapseTabs,
'subgraph-breadcrumb-overflow': overflowingTabs 'subgraph-breadcrumb-overflow': overflowingTabs
@@ -40,6 +40,7 @@ import { computed, onUpdated, ref, watch } from 'vue'
import SubgraphBreadcrumbItem from '@/components/breadcrumb/SubgraphBreadcrumbItem.vue' import SubgraphBreadcrumbItem from '@/components/breadcrumb/SubgraphBreadcrumbItem.vue'
import { useOverflowObserver } from '@/composables/element/useOverflowObserver' import { useOverflowObserver } from '@/composables/element/useOverflowObserver'
import { useTelemetry } from '@/platform/telemetry'
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore' import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore' import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore'
@@ -73,6 +74,9 @@ const items = computed(() => {
const items = navigationStore.navigationStack.map<MenuItem>((subgraph) => ({ const items = navigationStore.navigationStack.map<MenuItem>((subgraph) => ({
label: subgraph.name, label: subgraph.name,
command: () => { command: () => {
useTelemetry()?.trackUiButtonClicked({
button_id: 'breadcrumb_subgraph_item_selected'
})
const canvas = useCanvasStore().getCanvas() const canvas = useCanvasStore().getCanvas()
if (!canvas.graph) throw new TypeError('Canvas has no graph') if (!canvas.graph) throw new TypeError('Canvas has no graph')
@@ -97,6 +101,9 @@ const home = computed(() => ({
key: 'root', key: 'root',
isBlueprint: isBlueprint.value, isBlueprint: isBlueprint.value,
command: () => { command: () => {
useTelemetry()?.trackUiButtonClicked({
button_id: 'breadcrumb_subgraph_root_selected'
})
const canvas = useCanvasStore().getCanvas() const canvas = useCanvasStore().getCanvas()
if (!canvas.graph) throw new TypeError('Canvas has no graph') if (!canvas.graph) throw new TypeError('Canvas has no graph')
@@ -201,8 +208,8 @@ onUpdated(() => {
:deep(.p-breadcrumb-separator), :deep(.p-breadcrumb-separator),
:deep(.p-breadcrumb-item) { :deep(.p-breadcrumb-item) {
@apply h-12; @apply h-12;
border-top: 1px solid var(--p-panel-border-color); border-top: 1px solid var(--interface-stroke);
border-bottom: 1px solid var(--p-panel-border-color); border-bottom: 1px solid var(--interface-stroke);
background-color: var(--comfy-menu-bg); background-color: var(--comfy-menu-bg);
} }
@@ -214,7 +221,7 @@ onUpdated(() => {
@apply rounded-l-lg; @apply rounded-l-lg;
/* Then collapse the root workflow */ /* Then collapse the root workflow */
flex-shrink: 5000; flex-shrink: 5000;
border-left: 1px solid var(--p-panel-border-color); border-left: 1px solid var(--interface-stroke);
.p-breadcrumb-item-link { .p-breadcrumb-item-link {
padding-left: var(--p-breadcrumb-item-padding); padding-left: var(--p-breadcrumb-item-padding);
@@ -225,7 +232,7 @@ onUpdated(() => {
@apply rounded-r-lg; @apply rounded-r-lg;
/* Then collapse the active item */ /* Then collapse the active item */
flex-shrink: 1; flex-shrink: 1;
border-right: 1px solid var(--p-panel-border-color); border-right: 1px solid var(--interface-stroke);
} }
:deep(.p-breadcrumb-item-link:hover), :deep(.p-breadcrumb-item-link:hover),

View File

@@ -54,7 +54,7 @@ const {
}>() }>()
const topStyle = computed(() => { const topStyle = computed(() => {
const baseClasses = 'relative p-0' const baseClasses = 'relative p-0 overflow-hidden'
const ratioClasses = { const ratioClasses = {
square: 'aspect-square', square: 'aspect-square',

View File

@@ -21,6 +21,8 @@
@keyup.enter="blurInputElement" @keyup.enter="blurInputElement"
@keyup.escape="cancelEditing" @keyup.escape="cancelEditing"
@click.stop @click.stop
@pointerdown.stop.capture
@pointermove.stop.capture
/> />
</div> </div>
</template> </template>

View File

@@ -3,14 +3,14 @@
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div <div
class="preview-box flex h-16 w-16 items-center justify-center rounded border p-2" class="preview-box flex h-16 w-16 items-center justify-center rounded border p-2"
:class="{ 'bg-gray-100 dark-theme:bg-gray-800': !modelValue }" :class="{ 'bg-smoke-100 dark-theme:bg-smoke-800': !modelValue }"
> >
<img <img
v-if="modelValue" v-if="modelValue"
:src="modelValue" :src="modelValue"
class="max-h-full max-w-full object-contain" class="max-h-full max-w-full object-contain"
/> />
<i v-else class="pi pi-image text-xl text-gray-400" /> <i v-else class="pi pi-image text-xl text-smoke-400" />
</div> </div>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">

View File

@@ -56,7 +56,7 @@ describe('UserAvatar', () => {
const avatar = wrapper.findComponent(Avatar) const avatar = wrapper.findComponent(Avatar)
expect(avatar.exists()).toBe(true) expect(avatar.exists()).toBe(true)
expect(avatar.props('image')).toBeNull() expect(avatar.props('image')).toBeNull()
expect(avatar.props('icon')).toBe('pi pi-user') expect(avatar.props('icon')).toBe('icon-[lucide--user]')
}) })
it('renders with default icon when provided photo Url is null', () => { it('renders with default icon when provided photo Url is null', () => {
@@ -67,7 +67,7 @@ describe('UserAvatar', () => {
const avatar = wrapper.findComponent(Avatar) const avatar = wrapper.findComponent(Avatar)
expect(avatar.exists()).toBe(true) expect(avatar.exists()).toBe(true)
expect(avatar.props('image')).toBeNull() expect(avatar.props('image')).toBeNull()
expect(avatar.props('icon')).toBe('pi pi-user') expect(avatar.props('icon')).toBe('icon-[lucide--user]')
}) })
it('falls back to icon when image fails to load', async () => { it('falls back to icon when image fails to load', async () => {
@@ -82,7 +82,7 @@ describe('UserAvatar', () => {
avatar.vm.$emit('error') avatar.vm.$emit('error')
await nextTick() await nextTick()
expect(avatar.props('icon')).toBe('pi pi-user') expect(avatar.props('icon')).toBe('icon-[lucide--user]')
}) })
it('uses provided ariaLabel', () => { it('uses provided ariaLabel', () => {

View File

@@ -1,7 +1,9 @@
<template> <template>
<Avatar <Avatar
class="bg-interface-panel-selected-surface"
:image="photoUrl ?? undefined" :image="photoUrl ?? undefined"
:icon="hasAvatar ? undefined : 'pi pi-user'" :icon="hasAvatar ? undefined : 'icon-[lucide--user]'"
:pt:icon:class="{ 'size-4': !hasAvatar }"
shape="circle" shape="circle"
:aria-label="ariaLabel ?? $t('auth.login.userAvatar')" :aria-label="ariaLabel ?? $t('auth.login.userAvatar')"
@error="handleImageError" @error="handleImageError"

View File

@@ -36,53 +36,55 @@
</template> </template>
<template #contentFilter> <template #contentFilter>
<div class="relative flex flex-wrap gap-2 px-6 pt-2 pb-4"> <div class="relative flex flex-wrap justify-between gap-2 px-6 pb-4">
<!-- Model Filter --> <div class="flex flex-wrap gap-2">
<MultiSelect <!-- Model Filter -->
v-model="selectedModelObjects" <MultiSelect
v-model:search-query="modelSearchText" v-model="selectedModelObjects"
class="w-[250px]" v-model:search-query="modelSearchText"
:label="modelFilterLabel" class="w-[250px]"
:options="modelOptions" :label="modelFilterLabel"
:show-search-box="true" :options="modelOptions"
:show-selected-count="true" :show-search-box="true"
:show-clear-button="true" :show-selected-count="true"
> :show-clear-button="true"
<template #icon> >
<i class="icon-[lucide--cpu]" /> <template #icon>
</template> <i class="icon-[lucide--cpu]" />
</MultiSelect> </template>
</MultiSelect>
<!-- Use Case Filter --> <!-- Use Case Filter -->
<MultiSelect <MultiSelect
v-model="selectedUseCaseObjects" v-model="selectedUseCaseObjects"
:label="useCaseFilterLabel" :label="useCaseFilterLabel"
:options="useCaseOptions" :options="useCaseOptions"
:show-search-box="true" :show-search-box="true"
:show-selected-count="true" :show-selected-count="true"
:show-clear-button="true" :show-clear-button="true"
> >
<template #icon> <template #icon>
<i class="icon-[lucide--target]" /> <i class="icon-[lucide--target]" />
</template> </template>
</MultiSelect> </MultiSelect>
<!-- License Filter --> <!-- Runs On Filter -->
<MultiSelect <MultiSelect
v-model="selectedLicenseObjects" v-model="selectedRunsOnObjects"
:label="licenseFilterLabel" :label="runsOnFilterLabel"
:options="licenseOptions" :options="runsOnOptions"
:show-search-box="true" :show-search-box="true"
:show-selected-count="true" :show-selected-count="true"
:show-clear-button="true" :show-clear-button="true"
> >
<template #icon> <template #icon>
<i class="icon-[lucide--file-text]" /> <i class="icon-[lucide--server]" />
</template> </template>
</MultiSelect> </MultiSelect>
</div>
<!-- Sort Options --> <!-- Sort Options -->
<div class="absolute right-5"> <div>
<SingleSelect <SingleSelect
v-model="sortBy" v-model="sortBy"
:label="$t('templateWorkflows.sorting', 'Sort by')" :label="$t('templateWorkflows.sorting', 'Sort by')"
@@ -144,7 +146,7 @@
size="compact" size="compact"
variant="ghost" variant="ghost"
rounded="lg" rounded="lg"
class="hover:bg-white dark-theme:hover:bg-zinc-800" class="hover:bg-base-background"
> >
<template #top> <template #top>
<CardTop ratio="landscape"> <CardTop ratio="landscape">
@@ -178,7 +180,7 @@
variant="ghost" variant="ghost"
rounded="lg" rounded="lg"
:data-testid="`template-workflow-${template.name}`" :data-testid="`template-workflow-${template.name}`"
class="hover:bg-white dark-theme:hover:bg-zinc-800" class="hover:bg-base-background"
@mouseenter="hoveredTemplate = template.name" @mouseenter="hoveredTemplate = template.name"
@mouseleave="hoveredTemplate = null" @mouseleave="hoveredTemplate = null"
@click="onLoadWorkflow(template)" @click="onLoadWorkflow(template)"
@@ -323,7 +325,7 @@
size="compact" size="compact"
variant="ghost" variant="ghost"
rounded="lg" rounded="lg"
class="hover:bg-white dark-theme:hover:bg-zinc-800" class="hover:bg-base-background"
> >
<template #top> <template #top>
<CardTop ratio="square"> <CardTop ratio="square">
@@ -380,7 +382,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { useAsyncState } from '@vueuse/core' import { useAsyncState } from '@vueuse/core'
import ProgressSpinner from 'primevue/progressspinner' import ProgressSpinner from 'primevue/progressspinner'
import { computed, onBeforeUnmount, provide, ref, watch } from 'vue' import { computed, onBeforeUnmount, onMounted, provide, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import IconButton from '@/components/button/IconButton.vue' import IconButton from '@/components/button/IconButton.vue'
@@ -401,6 +403,8 @@ import LeftSidePanel from '@/components/widget/panel/LeftSidePanel.vue'
import { useIntersectionObserver } from '@/composables/useIntersectionObserver' import { useIntersectionObserver } from '@/composables/useIntersectionObserver'
import { useLazyPagination } from '@/composables/useLazyPagination' import { useLazyPagination } from '@/composables/useLazyPagination'
import { useTemplateFiltering } from '@/composables/useTemplateFiltering' import { useTemplateFiltering } from '@/composables/useTemplateFiltering'
import { isCloud } from '@/platform/distribution/types'
import { useTelemetry } from '@/platform/telemetry'
import { useTemplateWorkflows } from '@/platform/workflow/templates/composables/useTemplateWorkflows' import { useTemplateWorkflows } from '@/platform/workflow/templates/composables/useTemplateWorkflows'
import { useWorkflowTemplatesStore } from '@/platform/workflow/templates/repositories/workflowTemplatesStore' import { useWorkflowTemplatesStore } from '@/platform/workflow/templates/repositories/workflowTemplatesStore'
import type { TemplateInfo } from '@/platform/workflow/templates/types/template' import type { TemplateInfo } from '@/platform/workflow/templates/types/template'
@@ -410,10 +414,34 @@ import { createGridStyle } from '@/utils/gridUtil'
const { t } = useI18n() const { t } = useI18n()
const { onClose } = defineProps<{ const { onClose: originalOnClose } = defineProps<{
onClose: () => void onClose: () => void
}>() }>()
// Track session time for telemetry
const sessionStartTime = ref<number>(0)
const templateWasSelected = ref(false)
onMounted(() => {
sessionStartTime.value = Date.now()
})
// Wrap onClose to track session end
const onClose = () => {
if (isCloud) {
const timeSpentSeconds = Math.floor(
(Date.now() - sessionStartTime.value) / 1000
)
useTelemetry()?.trackTemplateLibraryClosed({
template_selected: templateWasSelected.value,
time_spent_seconds: timeSpentSeconds
})
}
originalOnClose()
}
provide(OnCloseKey, onClose) provide(OnCloseKey, onClose)
// Workflow templates store and composable // Workflow templates store and composable
@@ -500,12 +528,12 @@ const {
searchQuery, searchQuery,
selectedModels, selectedModels,
selectedUseCases, selectedUseCases,
selectedLicenses, selectedRunsOn,
sortBy, sortBy,
filteredTemplates, filteredTemplates,
availableModels, availableModels,
availableUseCases, availableUseCases,
availableLicenses, availableRunsOn,
filteredCount, filteredCount,
totalCount, totalCount,
resetFilters resetFilters
@@ -533,15 +561,15 @@ const selectedUseCaseObjects = computed({
} }
}) })
const selectedLicenseObjects = computed({ const selectedRunsOnObjects = computed({
get() { get() {
return selectedLicenses.value.map((license) => ({ return selectedRunsOn.value.map((runsOn) => ({
name: license, name: runsOn,
value: license value: runsOn
})) }))
}, },
set(value: { name: string; value: string }[]) { set(value: { name: string; value: string }[]) {
selectedLicenses.value = value.map((item) => item.value) selectedRunsOn.value = value.map((item) => item.value)
} }
}) })
@@ -574,10 +602,10 @@ const useCaseOptions = computed(() =>
})) }))
) )
const licenseOptions = computed(() => const runsOnOptions = computed(() =>
availableLicenses.value.map((license) => ({ availableRunsOn.value.map((runsOn) => ({
name: license, name: runsOn,
value: license value: runsOn
})) }))
) )
@@ -606,14 +634,14 @@ const useCaseFilterLabel = computed(() => {
} }
}) })
const licenseFilterLabel = computed(() => { const runsOnFilterLabel = computed(() => {
if (selectedLicenseObjects.value.length === 0) { if (selectedRunsOnObjects.value.length === 0) {
return t('templateWorkflows.licenseFilter', 'License') return t('templateWorkflows.runsOnFilter', 'Runs On')
} else if (selectedLicenseObjects.value.length === 1) { } else if (selectedRunsOnObjects.value.length === 1) {
return selectedLicenseObjects.value[0].name return selectedRunsOnObjects.value[0].name
} else { } else {
return t('templateWorkflows.licensesSelected', { return t('templateWorkflows.runsOnSelected', {
count: selectedLicenseObjects.value.length count: selectedRunsOnObjects.value.length
}) })
} }
}) })
@@ -680,7 +708,7 @@ watch(
sortBy, sortBy,
selectedModels, selectedModels,
selectedUseCases, selectedUseCases,
selectedLicenses selectedRunsOn
], ],
() => { () => {
resetPagination() resetPagination()
@@ -698,6 +726,7 @@ const onLoadWorkflow = async (template: any) => {
template.name, template.name,
getEffectiveSourceModule(template) getEffectiveSourceModule(template)
) )
templateWasSelected.value = true
onClose() onClose()
} finally { } finally {
loadingTemplate.value = null loadingTemplate.value = null

View File

@@ -61,6 +61,7 @@ import { useI18n } from 'vue-i18n'
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue' import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
import FindIssueButton from '@/components/dialog/content/error/FindIssueButton.vue' import FindIssueButton from '@/components/dialog/content/error/FindIssueButton.vue'
import { useCopyToClipboard } from '@/composables/useCopyToClipboard' import { useCopyToClipboard } from '@/composables/useCopyToClipboard'
import { useTelemetry } from '@/platform/telemetry'
import { api } from '@/scripts/api' import { api } from '@/scripts/api'
import { app } from '@/scripts/app' import { app } from '@/scripts/app'
import { useCommandStore } from '@/stores/commandStore' import { useCommandStore } from '@/stores/commandStore'
@@ -86,18 +87,33 @@ const repoOwner = 'comfyanonymous'
const repoName = 'ComfyUI' const repoName = 'ComfyUI'
const reportContent = ref('') const reportContent = ref('')
const reportOpen = ref(false) const reportOpen = ref(false)
/**
* Open the error report content and track telemetry.
*/
const showReport = () => { const showReport = () => {
useTelemetry()?.trackUiButtonClicked({
button_id: 'error_dialog_show_report_clicked'
})
reportOpen.value = true reportOpen.value = true
} }
const toast = useToast() const toast = useToast()
const { t } = useI18n() const { t } = useI18n()
const systemStatsStore = useSystemStatsStore() const systemStatsStore = useSystemStatsStore()
const telemetry = useTelemetry()
const title = computed<string>( const title = computed<string>(
() => error.nodeType ?? error.exceptionType ?? t('errorDialog.defaultTitle') () => error.nodeType ?? error.exceptionType ?? t('errorDialog.defaultTitle')
) )
/**
* Open contact support flow from error dialog and track telemetry.
*/
const showContactSupport = async () => { const showContactSupport = async () => {
telemetry?.trackHelpResourceClicked({
resource_type: 'help_feedback',
is_external: true,
source: 'error_dialog'
})
await useCommandStore().execute('Comfy.ContactSupport') await useCommandStore().execute('Comfy.ContactSupport')
} }

View File

@@ -43,8 +43,10 @@ import Tag from 'primevue/tag'
import { onBeforeUnmount, ref } from 'vue' import { onBeforeUnmount, ref } from 'vue'
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions' import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
import { useTelemetry } from '@/platform/telemetry'
const authActions = useFirebaseAuthActions() const authActions = useFirebaseAuthActions()
const telemetry = useTelemetry()
const { const {
amount, amount,
@@ -61,8 +63,11 @@ const didClickBuyNow = ref(false)
const loading = ref(false) const loading = ref(false)
const handleBuyNow = async () => { const handleBuyNow = async () => {
const creditAmount = editable ? customAmount.value : amount
telemetry?.trackApiCreditTopupButtonPurchaseClicked(creditAmount)
loading.value = true loading.value = true
await authActions.purchaseCredits(editable ? customAmount.value : amount) await authActions.purchaseCredits(creditAmount)
loading.value = false loading.value = false
didClickBuyNow.value = true didClickBuyNow.value = true
} }

View File

@@ -11,6 +11,8 @@
import Button from 'primevue/button' import Button from 'primevue/button'
import { computed } from 'vue' import { computed } from 'vue'
import { useTelemetry } from '@/platform/telemetry'
const props = defineProps<{ const props = defineProps<{
errorMessage: string errorMessage: string
repoOwner: string repoOwner: string
@@ -19,7 +21,13 @@ const props = defineProps<{
const queryString = computed(() => props.errorMessage + ' is:issue') const queryString = computed(() => props.errorMessage + ' is:issue')
/**
* Open GitHub issues search and track telemetry.
*/
const openGitHubIssues = () => { const openGitHubIssues = () => {
useTelemetry()?.trackUiButtonClicked({
button_id: 'error_dialog_find_existing_issues_clicked'
})
const query = encodeURIComponent(queryString.value) const query = encodeURIComponent(queryString.value)
const url = `https://github.com/${props.repoOwner}/${props.repoName}/issues?q=${query}` const url = `https://github.com/${props.repoOwner}/${props.repoName}/issues?q=${query}`
window.open(url, '_blank') window.open(url, '_blank')

View File

@@ -15,7 +15,7 @@
<UserCredit text-class="text-3xl font-bold" /> <UserCredit text-class="text-3xl font-bold" />
<Skeleton v-if="loading" width="2rem" height="2rem" /> <Skeleton v-if="loading" width="2rem" height="2rem" />
<Button <Button
v-else v-else-if="isActiveSubscription"
:label="$t('credits.purchaseCredits')" :label="$t('credits.purchaseCredits')"
:loading="loading" :loading="loading"
@click="handlePurchaseCreditsClick" @click="handlePurchaseCreditsClick"
@@ -92,6 +92,13 @@
icon="pi pi-question-circle" icon="pi pi-question-circle"
@click="handleFaqClick" @click="handleFaqClick"
/> />
<Button
:label="$t('subscription.partnerNodesCredits')"
text
severity="secondary"
icon="pi pi-question-circle"
@click="handleOpenPartnerNodesInfo"
/>
<Button <Button
:label="$t('credits.messageSupport')" :label="$t('credits.messageSupport')"
text text
@@ -116,6 +123,8 @@ import { computed, ref, watch } from 'vue'
import UserCredit from '@/components/common/UserCredit.vue' import UserCredit from '@/components/common/UserCredit.vue'
import UsageLogsTable from '@/components/dialog/content/setting/UsageLogsTable.vue' import UsageLogsTable from '@/components/dialog/content/setting/UsageLogsTable.vue'
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions' import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
import { useTelemetry } from '@/platform/telemetry'
import { useDialogService } from '@/services/dialogService' import { useDialogService } from '@/services/dialogService'
import { useCommandStore } from '@/stores/commandStore' import { useCommandStore } from '@/stores/commandStore'
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore' import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
@@ -132,6 +141,8 @@ const dialogService = useDialogService()
const authStore = useFirebaseAuthStore() const authStore = useFirebaseAuthStore()
const authActions = useFirebaseAuthActions() const authActions = useFirebaseAuthActions()
const commandStore = useCommandStore() const commandStore = useCommandStore()
const telemetry = useTelemetry()
const { isActiveSubscription } = useSubscription()
const loading = computed(() => authStore.loading) const loading = computed(() => authStore.loading)
const balanceLoading = computed(() => authStore.isFetchingBalance) const balanceLoading = computed(() => authStore.isFetchingBalance)
@@ -153,6 +164,8 @@ watch(
) )
const handlePurchaseCreditsClick = () => { const handlePurchaseCreditsClick = () => {
// Track purchase credits entry from Settings > Credits panel
useTelemetry()?.trackAddApiCreditButtonClicked()
dialogService.showTopUpCreditsDialog() dialogService.showTopUpCreditsDialog()
} }
@@ -161,6 +174,11 @@ const handleCreditsHistoryClick = async () => {
} }
const handleMessageSupport = async () => { const handleMessageSupport = async () => {
telemetry?.trackHelpResourceClicked({
resource_type: 'help_feedback',
is_external: true,
source: 'credits_panel'
})
await commandStore.execute('Comfy.ContactSupport') await commandStore.execute('Comfy.ContactSupport')
} }
@@ -168,5 +186,12 @@ const handleFaqClick = () => {
window.open('https://docs.comfy.org/tutorials/api-nodes/faq', '_blank') window.open('https://docs.comfy.org/tutorials/api-nodes/faq', '_blank')
} }
const handleOpenPartnerNodesInfo = () => {
window.open(
'https://docs.comfy.org/tutorials/api-nodes/overview#api-nodes',
'_blank'
)
}
const creditHistory = ref<CreditHistoryItemData[]>([]) const creditHistory = ref<CreditHistoryItemData[]>([])
</script> </script>

View File

@@ -50,7 +50,7 @@
<div class="font-semibold"> <div class="font-semibold">
{{ data.params?.api_name || 'API' }} {{ data.params?.api_name || 'API' }}
</div> </div>
<div class="text-sm text-gray-400"> <div class="text-sm text-smoke-400">
{{ $t('credits.model') }}: {{ data.params?.model || '-' }} {{ $t('credits.model') }}: {{ data.params?.model || '-' }}
</div> </div>
</div> </div>
@@ -96,6 +96,7 @@ import Message from 'primevue/message'
import ProgressSpinner from 'primevue/progressspinner' import ProgressSpinner from 'primevue/progressspinner'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { useTelemetry } from '@/platform/telemetry'
import type { AuditLog } from '@/services/customerEventsService' import type { AuditLog } from '@/services/customerEventsService'
import { import {
EventType, EventType,
@@ -159,6 +160,9 @@ const loadEvents = async () => {
if (response.totalPages) { if (response.totalPages) {
pagination.value.totalPages = response.totalPages pagination.value.totalPages = response.totalPages
} }
// Check if a pending top-up has completed
useTelemetry()?.checkForCompletedTopup(response.events)
} else { } else {
error.value = customerEventService.error.value || 'Failed to load events' error.value = customerEventService.error.value || 'Failed to load events'
} }

View File

@@ -78,7 +78,7 @@
<!-- Login Section --> <!-- Login Section -->
<div v-else class="flex flex-col gap-4"> <div v-else class="flex flex-col gap-4">
<p class="text-gray-600"> <p class="text-smoke-600">
{{ $t('auth.login.title') }} {{ $t('auth.login.title') }}
</p> </p>

View File

@@ -2,14 +2,14 @@
<Button <Button
ref="buttonRef" ref="buttonRef"
severity="secondary" severity="secondary"
class="group h-8 rounded-none! bg-interface-panel-surface p-0 transition-none! hover:rounded-lg! hover:bg-button-hover-surface!" class="group h-8 rounded-none! bg-comfy-menu-bg p-0 transition-none! hover:rounded-lg! hover:bg-interface-button-hover-surface!"
:style="buttonStyles" :style="buttonStyles"
@click="toggle" @click="toggle"
> >
<template #default> <template #default>
<div class="flex items-center gap-1 pr-0.5"> <div class="flex items-center gap-1 pr-0.5">
<div <div
class="rounded-lg bg-button-active-surface p-2 group-hover:bg-button-hover-surface" class="rounded-lg bg-interface-panel-selected-surface p-2 group-hover:bg-interface-button-hover-surface"
> >
<i :class="currentModeIcon" class="block h-4 w-4" /> <i :class="currentModeIcon" class="block h-4 w-4" />
</div> </div>
@@ -114,7 +114,7 @@ const popoverPt = computed(() => ({
content: { content: {
class: [ class: [
'mb-2 text-text-primary', 'mb-2 text-text-primary',
'shadow-lg border border-node-border', 'shadow-lg border border-interface-stroke',
'bg-nav-background', 'bg-nav-background',
'rounded-lg', 'rounded-lg',
'p-2 px-3', 'p-2 px-3',

View File

@@ -3,8 +3,10 @@
<!-- If load immediately, the top-level splitter stateKey won't be correctly <!-- If load immediately, the top-level splitter stateKey won't be correctly
synced with the stateStorage (localStorage). --> synced with the stateStorage (localStorage). -->
<LiteGraphCanvasSplitterOverlay v-if="comfyAppReady"> <LiteGraphCanvasSplitterOverlay v-if="comfyAppReady">
<template v-if="showUI && workflowTabsPosition === 'Topbar'" #workflow-tabs> <template v-if="showUI" #workflow-tabs>
<TryVueNodeBanner />
<div <div
v-if="workflowTabsPosition === 'Topbar'"
class="workflow-tabs-container pointer-events-auto relative h-9.5 w-full" class="workflow-tabs-container pointer-events-auto relative h-9.5 w-full"
> >
<!-- Native drag area for Electron --> <!-- Native drag area for Electron -->
@@ -152,6 +154,8 @@ import { useSearchBoxStore } from '@/stores/workspace/searchBoxStore'
import { useWorkspaceStore } from '@/stores/workspaceStore' import { useWorkspaceStore } from '@/stores/workspaceStore'
import { isNativeWindow } from '@/utils/envUtil' import { isNativeWindow } from '@/utils/envUtil'
import TryVueNodeBanner from '../topbar/TryVueNodeBanner.vue'
const emit = defineEmits<{ const emit = defineEmits<{
ready: [] ready: []
}>() }>()

View File

@@ -10,8 +10,10 @@
></div> ></div>
<ButtonGroup <ButtonGroup
class="absolute right-0 bottom-0 z-[1200] flex-row gap-1 border-[1px] border-node-border bg-interface-panel-surface p-2" class="absolute right-0 bottom-0 z-[1200] flex-row gap-1 border-[1px] border-interface-stroke bg-comfy-menu-bg p-2"
:style="stringifiedMinimapStyles.buttonGroupStyles" :style="{
...stringifiedMinimapStyles.buttonGroupStyles
}"
@wheel="canvasInteractions.handleWheel" @wheel="canvasInteractions.handleWheel"
> >
<CanvasModeSelector <CanvasModeSelector
@@ -26,7 +28,7 @@
icon="pi pi-expand" icon="pi pi-expand"
:aria-label="fitViewTooltip" :aria-label="fitViewTooltip"
:style="stringifiedMinimapStyles.buttonStyles" :style="stringifiedMinimapStyles.buttonStyles"
class="h-8 w-8 bg-interface-panel-surface p-0 hover:bg-button-hover-surface!" class="h-8 w-8 bg-comfy-menu-bg p-0 hover:bg-interface-button-hover-surface!"
@click="() => commandStore.execute('Comfy.Canvas.FitView')" @click="() => commandStore.execute('Comfy.Canvas.FitView')"
> >
<template #icon> <template #icon>
@@ -61,7 +63,7 @@
data-testid="toggle-minimap-button" data-testid="toggle-minimap-button"
:style="stringifiedMinimapStyles.buttonStyles" :style="stringifiedMinimapStyles.buttonStyles"
:class="minimapButtonClass" :class="minimapButtonClass"
@click="() => commandStore.execute('Comfy.Canvas.ToggleMinimap')" @click="onMinimapToggleClick"
> >
<template #icon> <template #icon>
<i class="icon-[lucide--map] h-4 w-4" /> <i class="icon-[lucide--map] h-4 w-4" />
@@ -82,7 +84,7 @@
:aria-label="linkVisibilityAriaLabel" :aria-label="linkVisibilityAriaLabel"
data-testid="toggle-link-visibility-button" data-testid="toggle-link-visibility-button"
:style="stringifiedMinimapStyles.buttonStyles" :style="stringifiedMinimapStyles.buttonStyles"
@click="() => commandStore.execute('Comfy.Canvas.ToggleLinkVisibility')" @click="onLinkVisibilityToggleClick"
> >
<template #icon> <template #icon>
<i class="icon-[lucide--route-off] h-4 w-4" /> <i class="icon-[lucide--route-off] h-4 w-4" />
@@ -101,6 +103,7 @@ import { useI18n } from 'vue-i18n'
import { useZoomControls } from '@/composables/useZoomControls' import { useZoomControls } from '@/composables/useZoomControls'
import { LiteGraph } from '@/lib/litegraph/src/litegraph' import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import { useSettingStore } from '@/platform/settings/settingStore' import { useSettingStore } from '@/platform/settings/settingStore'
import { useTelemetry } from '@/platform/telemetry'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions' import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
import { useMinimap } from '@/renderer/extensions/minimap/composables/useMinimap' import { useMinimap } from '@/renderer/extensions/minimap/composables/useMinimap'
@@ -163,18 +166,18 @@ const minimapCommandText = computed(() =>
// Computed properties for button classes and states // Computed properties for button classes and states
const zoomButtonClass = computed(() => [ const zoomButtonClass = computed(() => [
'bg-interface-panel-surface', 'bg-comfy-menu-bg',
isModalVisible.value ? 'not-active:bg-button-active-surface!' : '', isModalVisible.value ? 'not-active:bg-interface-panel-selected-surface!' : '',
'hover:bg-button-hover-surface!', 'hover:bg-interface-button-hover-surface!',
'p-0', 'p-0',
'h-8', 'h-8',
'w-15' 'w-15'
]) ])
const minimapButtonClass = computed(() => ({ const minimapButtonClass = computed(() => ({
'bg-interface-panel-surface': true, 'bg-comfy-menu-bg': true,
'hover:bg-button-hover-surface!': true, 'hover:bg-interface-button-hover-surface!': true,
'not-active:bg-button-active-surface!': settingStore.get( 'not-active:bg-interface-panel-selected-surface!': settingStore.get(
'Comfy.Minimap.Visible' 'Comfy.Minimap.Visible'
), ),
'p-0': true, 'p-0': true,
@@ -206,9 +209,9 @@ const linkVisibilityAriaLabel = computed(() =>
: t('graphCanvasMenu.hideLinks') : t('graphCanvasMenu.hideLinks')
) )
const linkVisibleClass = computed(() => [ const linkVisibleClass = computed(() => [
'bg-interface-panel-surface', 'bg-comfy-menu-bg',
linkHidden.value ? 'not-active:bg-button-active-surface!' : '', linkHidden.value ? 'not-active:bg-interface-panel-selected-surface!' : '',
'hover:bg-button-hover-surface!', 'hover:bg-interface-button-hover-surface!',
'p-0', 'p-0',
'w-8', 'w-8',
'h-8' 'h-8'
@@ -218,6 +221,26 @@ onMounted(() => {
canvasStore.initScaleSync() canvasStore.initScaleSync()
}) })
/**
* Track minimap toggle button click and execute the command.
*/
const onMinimapToggleClick = () => {
useTelemetry()?.trackUiButtonClicked({
button_id: 'graph_menu_minimap_toggle_clicked'
})
void commandStore.execute('Comfy.Canvas.ToggleMinimap')
}
/**
* Track hide/show links button click and execute the command.
*/
const onLinkVisibilityToggleClick = () => {
useTelemetry()?.trackUiButtonClicked({
button_id: 'graph_menu_hide_links_toggle_clicked'
})
void commandStore.execute('Comfy.Canvas.ToggleLinkVisibility')
}
onBeforeUnmount(() => { onBeforeUnmount(() => {
canvasStore.cleanupScaleSync() canvasStore.cleanupScaleSync()
}) })

View File

@@ -4,7 +4,7 @@
class="absolute right-0 bottom-[62px] z-1300 flex w-[250px] justify-center border-0! bg-inherit!" class="absolute right-0 bottom-[62px] z-1300 flex w-[250px] justify-center border-0! bg-inherit!"
> >
<div <div
class="w-4/5 rounded-lg border border-node-border bg-interface-panel-surface p-2 text-text-primary shadow-lg select-none" class="w-4/5 rounded-lg border border-interface-stroke bg-interface-panel-surface p-2 text-text-primary shadow-lg select-none"
:style="filteredMinimapStyles" :style="filteredMinimapStyles"
@click.stop @click.stop
> >
@@ -148,6 +148,6 @@ watch(
</script> </script>
<style> <style>
.zoomInputContainer:focus-within { .zoomInputContainer:focus-within {
border: 1px solid var(--color-pure-white); border: 1px solid var(--color-white);
} }
</style> </style>

View File

@@ -92,7 +92,7 @@ describe('BypassButton', () => {
const button = wrapper.find('button') const button = wrapper.find('button')
expect(button.classes()).not.toContain( expect(button.classes()).not.toContain(
'dark-theme:[&:not(:active)]:!bg-[#262729]' 'dark-theme:[&:not(:active)]:!bg-charcoal-600'
) )
}) })

View File

@@ -7,11 +7,11 @@
severity="secondary" severity="secondary"
text text
data-testid="bypass-button" data-testid="bypass-button"
class="hover:bg-[#E7E6E6] hover:dark-theme:bg-charcoal-600" class="hover:bg-secondary-background"
@click="toggleBypass" @click="toggleBypass"
> >
<template #icon> <template #icon>
<i class="icon-[lucide--ban] h-4 w-4" /> <i class="icon-[lucide--redo-dot] h-4 w-4" />
</template> </template>
</Button> </Button>
</template> </template>

View File

@@ -4,7 +4,7 @@
value: t('selectionToolbox.executeButton.tooltip'), value: t('selectionToolbox.executeButton.tooltip'),
showDelay: 1000 showDelay: 1000
}" }"
class="size-8 bg-[#31B9F4] !p-0 dark-theme:bg-[#0B8CE9]" class="size-8 bg-azure-400 !p-0 dark-theme:bg-azure-600"
text text
@mouseenter="() => handleMouseEnter()" @mouseenter="() => handleMouseEnter()"
@mouseleave="() => handleMouseLeave()" @mouseleave="() => handleMouseLeave()"

Some files were not shown because too many files have changed in this diff Show More