Compare commits

..

2 Commits

Author SHA1 Message Date
filtered
782d93a7a0 Add awaits to various tests 2024-11-15 01:28:48 +11:00
filtered
7be14c5189 Add nodeTemplate tests
Test failure confirmed when links are not connected
2024-11-15 01:28:18 +11:00
1304 changed files with 29554 additions and 222636 deletions

View File

@@ -1,43 +0,0 @@
# Add Missing i18n Translations
## Task: Add English translations for all new localized strings
### Step 1: Identify new translation keys
Find all translation keys that were added in the current branch's changes. These keys appear as arguments to translation functions: `t()`, `st()`, `$t()`, or similar i18n functions.
### Step 2: Add translations to English locale file
For each new translation key found, add the corresponding English text to the file `src/locales/en/main.json`.
### Key-to-JSON mapping rules:
- Translation keys use dot notation to represent nested JSON structure
- Convert dot notation to nested JSON objects when adding to the locale file
- Example: The key `g.user.name` maps to:
```json
{
"g": {
"user": {
"name": "User Name"
}
}
}
```
### Important notes:
1. **Only modify the English locale file** (`src/locales/en/main.json`)
2. **Do not modify other locale files** - translations for other languages are automatically generated by the `i18n.yaml` workflow
3. **Exception for manual translations**: Only add translations to non-English locale files if:
- You have specific domain knowledge that would produce a more accurate translation than the automated system
- The automated translation would likely be incorrect due to technical terminology or context-specific meaning
### Example workflow:
1. If you added `t('settings.advanced.enable')` in a Vue component
2. Add to `src/locales/en/main.json`:
```json
{
"settings": {
"advanced": {
"enable": "Enable advanced settings"
}
}
}
```

View File

@@ -1,708 +0,0 @@
# Comprehensive PR Review for ComfyUI Frontend
<task>
You are performing a comprehensive code review for PR #$1 in the ComfyUI frontend repository. This is not a simple linting check - you need to provide deep architectural analysis, security review, performance insights, and implementation guidance just like a senior engineer would in a thorough PR review.
Your review should cover:
1. Architecture and design patterns
2. Security vulnerabilities and risks
3. Performance implications
4. Code quality and maintainability
5. Integration with existing systems
6. Best practices and conventions
7. Testing considerations
8. Documentation needs
</task>
Arguments: PR number passed via PR_NUMBER environment variable
## Phase 0: Initialize Variables and Helper Functions
```bash
# Validate PR_NUMBER first thing
if [ -z "$PR_NUMBER" ]; then
echo "Error: PR_NUMBER environment variable is not set"
echo "Usage: PR_NUMBER=<number> claude run /comprehensive-pr-review"
exit 1
fi
# Initialize all counters at the start
CRITICAL_COUNT=0
HIGH_COUNT=0
MEDIUM_COUNT=0
LOW_COUNT=0
ARCHITECTURE_ISSUES=0
SECURITY_ISSUES=0
PERFORMANCE_ISSUES=0
QUALITY_ISSUES=0
# Helper function for posting review comments
post_review_comment() {
local file_path=$1
local line_number=$2
local severity=$3 # critical/high/medium/low
local category=$4 # architecture/security/performance/quality
local issue=$5
local context=$6
local suggestion=$7
# Update counters
case $severity in
"critical") ((CRITICAL_COUNT++)) ;;
"high") ((HIGH_COUNT++)) ;;
"medium") ((MEDIUM_COUNT++)) ;;
"low") ((LOW_COUNT++)) ;;
esac
case $category in
"architecture") ((ARCHITECTURE_ISSUES++)) ;;
"security") ((SECURITY_ISSUES++)) ;;
"performance") ((PERFORMANCE_ISSUES++)) ;;
"quality") ((QUALITY_ISSUES++)) ;;
esac
# Post inline comment via GitHub CLI
local comment="${issue}\n${context}\n${suggestion}"
gh pr review $PR_NUMBER --comment --body "$comment" -F - <<< "$comment"
}
```
## Phase 1: Environment Setup and PR Context
```bash
# Pre-flight checks
check_prerequisites() {
# Check gh CLI is available
if ! command -v gh &> /dev/null; then
echo "Error: gh CLI is not installed"
exit 1
fi
# In GitHub Actions, auth is handled via GITHUB_TOKEN
if [ -n "$GITHUB_ACTIONS" ] && [ -z "$GITHUB_TOKEN" ]; then
echo "Error: GITHUB_TOKEN is not set in GitHub Actions"
exit 1
fi
# Check if we're authenticated
if ! gh auth status &> /dev/null; then
echo "Error: Not authenticated with GitHub. Run 'gh auth login'"
exit 1
fi
# Set repository if not already set
if [ -z "$REPOSITORY" ]; then
REPOSITORY="Comfy-Org/ComfyUI_frontend"
fi
# Check PR exists and is open
PR_STATE=$(gh pr view $PR_NUMBER --repo $REPOSITORY --json state -q .state 2>/dev/null || echo "NOT_FOUND")
if [ "$PR_STATE" = "NOT_FOUND" ]; then
echo "Error: PR #$PR_NUMBER not found in $REPOSITORY"
exit 1
elif [ "$PR_STATE" != "OPEN" ]; then
echo "Error: PR #$PR_NUMBER is not open (state: $PR_STATE)"
exit 1
fi
# Check API rate limits
RATE_REMAINING=$(gh api /rate_limit --jq '.rate.remaining' 2>/dev/null || echo "5000")
if [ "$RATE_REMAINING" -lt 100 ]; then
echo "Warning: Low API rate limit: $RATE_REMAINING remaining"
if [ "$RATE_REMAINING" -lt 50 ]; then
echo "Error: Insufficient API rate limit for comprehensive review"
exit 1
fi
fi
echo "Pre-flight checks passed"
}
# Run pre-flight checks
check_prerequisites
echo "Starting comprehensive review of PR #$PR_NUMBER"
# Fetch PR information with error handling
echo "Fetching PR information..."
if ! gh pr view $PR_NUMBER --repo $REPOSITORY --json files,title,body,additions,deletions,baseRefName,headRefName > pr_info.json; then
echo "Error: Failed to fetch PR information"
exit 1
fi
# Extract branch names
BASE_BRANCH=$(jq -r '.baseRefName' < pr_info.json)
HEAD_BRANCH=$(jq -r '.headRefName' < pr_info.json)
# Checkout PR branch locally for better file inspection
echo "Checking out PR branch..."
git fetch origin "pull/$PR_NUMBER/head:pr-$PR_NUMBER"
git checkout "pr-$PR_NUMBER"
# Get changed files using git locally (much faster)
git diff --name-only "origin/$BASE_BRANCH" > changed_files.txt
# Get the diff using git locally
git diff "origin/$BASE_BRANCH" > pr_diff.txt
# Get detailed file changes with line numbers
git diff --name-status "origin/$BASE_BRANCH" > file_changes.txt
# For API compatibility, create a simplified pr_files.json
echo '[]' > pr_files.json
while IFS=$'\t' read -r status file; do
if [[ "$status" != "D" ]]; then # Skip deleted files
# Get the patch for this file
patch=$(git diff "origin/$BASE_BRANCH" -- "$file" | jq -Rs .)
additions=$(git diff --numstat "origin/$BASE_BRANCH" -- "$file" | awk '{print $1}')
deletions=$(git diff --numstat "origin/$BASE_BRANCH" -- "$file" | awk '{print $2}')
jq --arg file "$file" \
--arg patch "$patch" \
--arg additions "$additions" \
--arg deletions "$deletions" \
'. += [{
"filename": $file,
"patch": $patch,
"additions": ($additions | tonumber),
"deletions": ($deletions | tonumber)
}]' pr_files.json > pr_files.json.tmp
mv pr_files.json.tmp pr_files.json
fi
done < file_changes.txt
# Setup caching directory
CACHE_DIR=".claude-review-cache"
mkdir -p "$CACHE_DIR"
# Function to cache analysis results
cache_analysis() {
local file_path=$1
local analysis_result=$2
local file_hash=$(git hash-object "$file_path" 2>/dev/null || echo "no-hash")
if [ "$file_hash" != "no-hash" ]; then
echo "$analysis_result" > "$CACHE_DIR/${file_hash}.cache"
fi
}
# Function to get cached analysis
get_cached_analysis() {
local file_path=$1
local file_hash=$(git hash-object "$file_path" 2>/dev/null || echo "no-hash")
if [ "$file_hash" != "no-hash" ] && [ -f "$CACHE_DIR/${file_hash}.cache" ]; then
cat "$CACHE_DIR/${file_hash}.cache"
return 0
fi
return 1
}
# Clean old cache entries (older than 7 days)
find "$CACHE_DIR" -name "*.cache" -mtime +7 -delete 2>/dev/null || true
```
## Phase 2: Load Comprehensive Knowledge Base
```bash
# Don't create knowledge directory until we know we need it
KNOWLEDGE_FOUND=false
# Use local cache for knowledge base to avoid repeated downloads
KNOWLEDGE_CACHE_DIR=".claude-knowledge-cache"
mkdir -p "$KNOWLEDGE_CACHE_DIR"
# Option to use cloned prompt library for better performance
PROMPT_LIBRARY_PATH="../comfy-claude-prompt-library"
if [ -d "$PROMPT_LIBRARY_PATH" ]; then
echo "Using local prompt library at $PROMPT_LIBRARY_PATH"
USE_LOCAL_PROMPT_LIBRARY=true
else
echo "No local prompt library found, will use GitHub API"
USE_LOCAL_PROMPT_LIBRARY=false
fi
# Function to fetch with cache
fetch_with_cache() {
local url=$1
local output_file=$2
local cache_file="$KNOWLEDGE_CACHE_DIR/$(echo "$url" | sed 's/[^a-zA-Z0-9]/_/g')"
# Check if cached version exists and is less than 1 day old
if [ -f "$cache_file" ] && [ $(find "$cache_file" -mtime -1 2>/dev/null | wc -l) -gt 0 ]; then
# Create knowledge directory only when we actually have content
if [ "$KNOWLEDGE_FOUND" = "false" ]; then
mkdir -p review_knowledge
KNOWLEDGE_FOUND=true
fi
cp "$cache_file" "$output_file"
echo "Using cached version of $(basename "$output_file")"
return 0
fi
# Try to fetch fresh version
if curl -s -f "$url" > "$output_file.tmp"; then
# Create knowledge directory only when we actually have content
if [ "$KNOWLEDGE_FOUND" = "false" ]; then
mkdir -p review_knowledge
KNOWLEDGE_FOUND=true
fi
mv "$output_file.tmp" "$output_file"
cp "$output_file" "$cache_file"
echo "Downloaded fresh version of $(basename "$output_file")"
return 0
else
# If fetch failed but we have a cache, use it
if [ -f "$cache_file" ]; then
if [ "$KNOWLEDGE_FOUND" = "false" ]; then
mkdir -p review_knowledge
KNOWLEDGE_FOUND=true
fi
cp "$cache_file" "$output_file"
echo "Using stale cache for $(basename "$output_file") (download failed)"
return 0
fi
echo "Failed to load $(basename "$output_file")"
return 1
fi
}
# Load REPOSITORY_GUIDE.md for deep architectural understanding
echo "Loading ComfyUI Frontend repository guide..."
if [ "$USE_LOCAL_PROMPT_LIBRARY" = "true" ] && [ -f "$PROMPT_LIBRARY_PATH/project-summaries-for-agents/ComfyUI_frontend/REPOSITORY_GUIDE.md" ]; then
if [ "$KNOWLEDGE_FOUND" = "false" ]; then
mkdir -p review_knowledge
KNOWLEDGE_FOUND=true
fi
cp "$PROMPT_LIBRARY_PATH/project-summaries-for-agents/ComfyUI_frontend/REPOSITORY_GUIDE.md" "review_knowledge/repository_guide.md"
echo "Loaded repository guide from local prompt library"
else
fetch_with_cache "https://raw.githubusercontent.com/Comfy-Org/comfy-claude-prompt-library/master/project-summaries-for-agents/ComfyUI_frontend/REPOSITORY_GUIDE.md" "review_knowledge/repository_guide.md"
fi
# 3. Discover and load relevant knowledge folders from GitHub API
echo "Discovering available knowledge folders..."
KNOWLEDGE_API_URL="https://api.github.com/repos/Comfy-Org/comfy-claude-prompt-library/contents/.claude/knowledge"
if KNOWLEDGE_FOLDERS=$(curl -s "$KNOWLEDGE_API_URL" | jq -r '.[] | select(.type=="dir") | .name' 2>/dev/null); then
echo "Available knowledge folders: $KNOWLEDGE_FOLDERS"
# Analyze changed files to determine which knowledge folders might be relevant
CHANGED_FILES=$(cat changed_files.txt)
PR_TITLE=$(jq -r '.title' < pr_info.json)
PR_BODY=$(jq -r '.body // ""' < pr_info.json)
# For each knowledge folder, check if it might be relevant to the PR
for folder in $KNOWLEDGE_FOLDERS; do
# Simple heuristic: if folder name appears in changed file paths or PR context
if echo "$CHANGED_FILES $PR_TITLE $PR_BODY" | grep -qi "$folder"; then
echo "Loading knowledge folder: $folder"
# Fetch all files in that knowledge folder
FOLDER_API_URL="https://api.github.com/repos/Comfy-Org/comfy-claude-prompt-library/contents/.claude/knowledge/$folder"
curl -s "$FOLDER_API_URL" | jq -r '.[] | select(.type=="file") | .download_url' 2>/dev/null | \
while read url; do
if [ -n "$url" ]; then
filename=$(basename "$url")
fetch_with_cache "$url" "review_knowledge/${folder}_${filename}"
fi
done
fi
done
else
echo "Could not discover knowledge folders"
fi
# 4. Load validation rules from the repository
echo "Loading validation rules..."
VALIDATION_API_URL="https://api.github.com/repos/Comfy-Org/comfy-claude-prompt-library/contents/.claude/commands/validation"
if VALIDATION_FILES=$(curl -s "$VALIDATION_API_URL" | jq -r '.[] | select(.name | contains("frontend") or contains("security") or contains("performance")) | .download_url' 2>/dev/null); then
for url in $VALIDATION_FILES; do
if [ -n "$url" ]; then
filename=$(basename "$url")
fetch_with_cache "$url" "review_knowledge/validation_${filename}"
fi
done
else
echo "Could not load validation rules"
fi
# 5. Load local project guidelines
if [ -f "CLAUDE.md" ]; then
if [ "$KNOWLEDGE_FOUND" = "false" ]; then
mkdir -p review_knowledge
KNOWLEDGE_FOUND=true
fi
cp CLAUDE.md review_knowledge/local_claude.md
fi
if [ -f ".github/CLAUDE.md" ]; then
if [ "$KNOWLEDGE_FOUND" = "false" ]; then
mkdir -p review_knowledge
KNOWLEDGE_FOUND=true
fi
cp .github/CLAUDE.md review_knowledge/github_claude.md
fi
```
## Phase 3: Deep Analysis Instructions
Perform a comprehensive analysis covering these areas:
### 3.1 Architectural Analysis
Based on the repository guide and project summary, evaluate:
- Does this change align with the established architecture patterns?
- Are domain boundaries respected?
- Is the extension system used appropriately?
- Are components properly organized by feature?
- Does it follow the established service/composable/store patterns?
### 3.2 Code Quality Beyond Linting
- Cyclomatic complexity and cognitive load
- SOLID principles adherence
- DRY violations that aren't caught by simple duplication checks
- Proper abstraction levels
- Interface design and API clarity
- No leftover debug code (console.log, commented code, TODO comments)
### 3.3 Library Usage Enforcement
CRITICAL: Never re-implement functionality that exists in our standard libraries:
- **Tailwind CSS**: Use utility classes instead of custom CSS or style attributes
- **PrimeVue**: Never re-implement components that exist in PrimeVue (buttons, modals, dropdowns, etc.)
- **VueUse**: Never re-implement composables that exist in VueUse (useLocalStorage, useDebounceFn, etc.)
- **Lodash**: Never re-implement utility functions (debounce, throttle, cloneDeep, etc.)
- **Common components**: Reuse components from src/components/common/
- **DOMPurify**: Always use for HTML sanitization
- **Fuse.js**: Use for fuzzy search functionality
- **Marked**: Use for markdown parsing
- **Pinia**: Use for global state management, not custom solutions
- **Zod**: Use for form validation with zodResolver pattern
- **Tiptap**: Use for rich text/markdown editing
- **Xterm.js**: Use for terminal emulation
- **Axios**: Use for HTTP client initialization
### 3.4 Security Deep Dive
Beyond obvious vulnerabilities:
- Authentication/authorization implications
- Data validation completeness
- State management security
- Cross-origin concerns
- Extension security boundaries
### 3.5 Performance Analysis
- Render performance implications
- Layout thrashing prevention
- Memory leak potential
- Network request optimization
- State management efficiency
### 3.6 Integration Concerns
- Breaking changes to internal APIs
- Extension compatibility
- Backward compatibility
- Migration requirements
## Phase 4: Create Detailed Review Comments
CRITICAL: Keep comments extremely concise and effective. Use only as many words as absolutely necessary.
- NO markdown formatting (no #, ##, ###, **, etc.)
- NO emojis
- Get to the point immediately
- Burden the reader as little as possible
For each issue found, create a concise inline comment with:
1. What's wrong (one line)
2. Why it matters (one line)
3. How to fix it (one line)
4. Code example only if essential
```bash
# Helper function for comprehensive comments
post_review_comment() {
local file_path=$1
local line_number=$2
local severity=$3 # critical/high/medium/low
local category=$4 # architecture/security/performance/quality
local issue=$5
local context=$6
local suggestion=$7
local example=$8
local body="### [$category] $severity Priority
**Issue**: $issue
**Context**: $context
**Suggestion**: $suggestion"
if [ -n "$example" ]; then
body="$body
**Example**:
\`\`\`typescript
$example
\`\`\`"
fi
body="$body
*Related: See [repository guide](https://github.com/Comfy-Org/comfy-claude-prompt-library/blob/master/project-summaries-for-agents/ComfyUI_frontend/REPOSITORY_GUIDE.md) for patterns*"
gh api -X POST /repos/$REPOSITORY/pulls/$PR_NUMBER/comments \
-f path="$file_path" \
-f line=$line_number \
-f body="$body" \
-f commit_id="$COMMIT_SHA" \
-f side='RIGHT' || echo "Failed to post comment at $file_path:$line_number"
}
```
## Phase 5: Validation Rules Application
Apply ALL validation rules from the loaded knowledge, but focus on the changed lines:
### From Frontend Standards
- Vue 3 Composition API patterns
- Component communication patterns
- Proper use of composables
- TypeScript strict mode compliance
- Bundle optimization
### From Security Audit
- Input validation
- XSS prevention
- CSRF protection
- Secure state management
- API security
### From Performance Check
- Render optimization
- Memory management
- Network efficiency
- Bundle size impact
## Phase 6: Contextual Review Based on PR Type
Analyze the PR description and changes to determine the type:
```bash
# Extract PR metadata with error handling
if [ ! -f pr_info.json ]; then
echo "Error: pr_info.json not found"
exit 1
fi
PR_TITLE=$(jq -r '.title // "Unknown"' < pr_info.json)
PR_BODY=$(jq -r '.body // ""' < pr_info.json)
FILE_COUNT=$(wc -l < changed_files.txt)
ADDITIONS=$(jq -r '.additions // 0' < pr_info.json)
DELETIONS=$(jq -r '.deletions // 0' < pr_info.json)
# Determine PR type and apply specific review criteria
if echo "$PR_TITLE $PR_BODY" | grep -qiE "(feature|feat)"; then
echo "Detected feature PR - applying feature review criteria"
# Check for tests, documentation, backward compatibility
elif echo "$PR_TITLE $PR_BODY" | grep -qiE "(fix|bug)"; then
echo "Detected bug fix - checking root cause and regression tests"
# Verify fix addresses root cause, includes tests
elif echo "$PR_TITLE $PR_BODY" | grep -qiE "(refactor)"; then
echo "Detected refactoring - ensuring behavior preservation"
# Check that tests still pass, no behavior changes
fi
```
## Phase 7: Generate Comprehensive Summary
After all inline comments, create a detailed summary:
```bash
# Initialize metrics tracking
REVIEW_START_TIME=$(date +%s)
# Create the comprehensive summary
gh pr review $PR_NUMBER --comment --body "# Comprehensive PR Review
This review is generated by Claude. It may not always be accurate, as with human reviewers. If you believe that any of the comments are invalid or incorrect, please state why for each. For others, please implement the changes in one way or another.
## Review Summary
**PR**: $PR_TITLE (#$PR_NUMBER)
**Impact**: $ADDITIONS additions, $DELETIONS deletions across $FILE_COUNT files
### Issue Distribution
- Critical: $CRITICAL_COUNT
- High: $HIGH_COUNT
- Medium: $MEDIUM_COUNT
- Low: $LOW_COUNT
### Category Breakdown
- Architecture: $ARCHITECTURE_ISSUES issues
- Security: $SECURITY_ISSUES issues
- Performance: $PERFORMANCE_ISSUES issues
- Code Quality: $QUALITY_ISSUES issues
## Key Findings
### Architecture & Design
[Detailed architectural analysis based on repository patterns]
### Security Considerations
[Security implications beyond basic vulnerabilities]
### Performance Impact
[Performance analysis including bundle size, render impact]
### Integration Points
[How this affects other systems, extensions, etc.]
## Positive Observations
[What was done well, good patterns followed]
## References
- [Repository Architecture Guide](https://github.com/Comfy-Org/comfy-claude-prompt-library/blob/master/project-summaries-for-agents/ComfyUI_frontend/REPOSITORY_GUIDE.md)
- [Frontend Standards](https://github.com/Comfy-Org/comfy-claude-prompt-library/blob/master/.claude/commands/validation/frontend-code-standards.md)
- [Security Guidelines](https://github.com/Comfy-Org/comfy-claude-prompt-library/blob/master/.claude/commands/validation/security-audit.md)
## Next Steps
1. Address critical issues before merge
2. Consider architectural feedback for long-term maintainability
3. Add tests for uncovered scenarios
4. Update documentation if needed
---
*This is a comprehensive automated review. For architectural decisions requiring human judgment, please request additional manual review.*"
```
## Important: Think Deeply
When reviewing:
1. **Think hard** about architectural implications
2. Consider how changes affect the entire system
3. Look for subtle bugs and edge cases
4. Evaluate maintainability over time
5. Consider extension developer experience
6. Think about migration paths
This is a COMPREHENSIVE review, not a linting pass. Provide the same quality feedback a senior engineer would give after careful consideration.
## Phase 8: Track Review Metrics
After completing the review, save metrics for analysis:
```bash
# Calculate review duration
REVIEW_END_TIME=$(date +%s)
REVIEW_DURATION=$((REVIEW_END_TIME - REVIEW_START_TIME))
# Calculate total issues
TOTAL_ISSUES=$((CRITICAL_COUNT + HIGH_COUNT + MEDIUM_COUNT + LOW_COUNT))
# Create metrics directory if it doesn't exist
METRICS_DIR=".claude/review-metrics"
mkdir -p "$METRICS_DIR"
# Generate metrics file
METRICS_FILE="$METRICS_DIR/metrics-$(date +%Y%m).json"
# Create or update monthly metrics file
if [ -f "$METRICS_FILE" ]; then
# Append to existing file
jq -n \
--arg pr "$PR_NUMBER" \
--arg title "$PR_TITLE" \
--arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--arg duration "$REVIEW_DURATION" \
--arg files "$FILE_COUNT" \
--arg additions "$ADDITIONS" \
--arg deletions "$DELETIONS" \
--arg total "$TOTAL_ISSUES" \
--arg critical "$CRITICAL_COUNT" \
--arg high "$HIGH_COUNT" \
--arg medium "$MEDIUM_COUNT" \
--arg low "$LOW_COUNT" \
--arg architecture "$ARCHITECTURE_ISSUES" \
--arg security "$SECURITY_ISSUES" \
--arg performance "$PERFORMANCE_ISSUES" \
--arg quality "$QUALITY_ISSUES" \
'{
pr_number: $pr,
pr_title: $title,
timestamp: $timestamp,
review_duration_seconds: ($duration | tonumber),
files_reviewed: ($files | tonumber),
lines_added: ($additions | tonumber),
lines_deleted: ($deletions | tonumber),
issues: {
total: ($total | tonumber),
by_severity: {
critical: ($critical | tonumber),
high: ($high | tonumber),
medium: ($medium | tonumber),
low: ($low | tonumber)
},
by_category: {
architecture: ($architecture | tonumber),
security: ($security | tonumber),
performance: ($performance | tonumber),
quality: ($quality | tonumber)
}
}
}' > "$METRICS_FILE.new"
# Merge with existing data
jq -s '.[0] + [.[1]]' "$METRICS_FILE" "$METRICS_FILE.new" > "$METRICS_FILE.tmp"
mv "$METRICS_FILE.tmp" "$METRICS_FILE"
rm "$METRICS_FILE.new"
else
# Create new file
jq -n \
--arg pr "$PR_NUMBER" \
--arg title "$PR_TITLE" \
--arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--arg duration "$REVIEW_DURATION" \
--arg files "$FILE_COUNT" \
--arg additions "$ADDITIONS" \
--arg deletions "$DELETIONS" \
--arg total "$TOTAL_ISSUES" \
--arg critical "$CRITICAL_COUNT" \
--arg high "$HIGH_COUNT" \
--arg medium "$MEDIUM_COUNT" \
--arg low "$LOW_COUNT" \
--arg architecture "$ARCHITECTURE_ISSUES" \
--arg security "$SECURITY_ISSUES" \
--arg performance "$PERFORMANCE_ISSUES" \
--arg quality "$QUALITY_ISSUES" \
'[{
pr_number: $pr,
pr_title: $title,
timestamp: $timestamp,
review_duration_seconds: ($duration | tonumber),
files_reviewed: ($files | tonumber),
lines_added: ($additions | tonumber),
lines_deleted: ($deletions | tonumber),
issues: {
total: ($total | tonumber),
by_severity: {
critical: ($critical | tonumber),
high: ($high | tonumber),
medium: ($medium | tonumber),
low: ($low | tonumber)
},
by_category: {
architecture: ($architecture | tonumber),
security: ($security | tonumber),
performance: ($performance | tonumber),
quality: ($quality | tonumber)
}
}
}]' > "$METRICS_FILE"
fi
echo "Review metrics saved to $METRICS_FILE"
```
This creates monthly metrics files (e.g., `metrics-202407.json`) that track:
- Which PRs were reviewed
- How long reviews took
- Types and severity of issues found
- Trends over time
You can later analyze these to see patterns and improve your development process.

View File

@@ -1,625 +0,0 @@
# Create Frontend Release
This command guides you through creating a comprehensive frontend release with semantic versioning analysis, automated change detection, security scanning, and multi-stage human verification.
<task>
Create a frontend release with version type: $ARGUMENTS
Expected format: Version increment type and optional description
Examples:
- `patch` - Bug fixes only
- `minor` - New features, backward compatible
- `major` - Breaking changes
- `prerelease` - Alpha/beta/rc releases
- `patch "Critical security fixes"` - With custom description
- `minor --skip-changelog` - Skip automated changelog generation
- `minor --dry-run` - Simulate release without executing
If no arguments provided, the command will always perform prerelease if the current version is prerelease, or patch in other cases. This command will never perform minor or major releases without explicit direction.
</task>
## Prerequisites
Before starting, ensure:
- You have push access to the repository
- GitHub CLI (`gh`) is authenticated
- You're on a clean main branch working tree
- All intended changes are merged to main
- You understand the scope of changes being released
## Critical Checks Before Starting
### 1. Check Current Version Status
```bash
# Get current version and check if it's a pre-release
CURRENT_VERSION=$(node -p "require('./package.json').version")
if [[ "$CURRENT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+- ]]; then
echo "⚠️ Current version $CURRENT_VERSION is a pre-release"
echo "Consider releasing stable (e.g., 1.24.0-1 → 1.24.0) first"
fi
```
### 2. Find Last Stable Release
```bash
# Get last stable release tag (no pre-release suffix)
LAST_STABLE=$(git tag -l "v*" | grep -v "\-" | sort -V | tail -1)
echo "Last stable release: $LAST_STABLE"
```
## Configuration Options
**Environment Variables:**
- `RELEASE_SKIP_SECURITY_SCAN=true` - Skip security audit
- `RELEASE_AUTO_APPROVE=true` - Skip some confirmation prompts
- `RELEASE_DRY_RUN=true` - Simulate release without executing
## Release Process
### Step 1: Environment Safety Check
1. Verify clean working directory:
```bash
git status --porcelain
```
2. Confirm on main branch:
```bash
git branch --show-current
```
3. Pull latest changes:
```bash
git pull origin main
```
4. Check GitHub CLI authentication:
```bash
gh auth status
```
5. Verify npm/PyPI publishing access (dry run)
6. **CONFIRMATION REQUIRED**: Environment ready for release?
### Step 2: Analyze Recent Changes
1. Get current version from package.json
2. **IMPORTANT**: Determine correct base for comparison:
```bash
# If current version is pre-release, use last stable release
if [[ "$CURRENT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+- ]]; then
BASE_TAG=$LAST_STABLE
else
BASE_TAG=$(git describe --tags --abbrev=0)
fi
```
3. Find commits since base release (CRITICAL: use --first-parent):
```bash
git log ${BASE_TAG}..HEAD --oneline --no-merges --first-parent
```
4. Count total commits:
```bash
COMMIT_COUNT=$(git log ${BASE_TAG}..HEAD --oneline --no-merges --first-parent | wc -l)
echo "Found $COMMIT_COUNT commits since $BASE_TAG"
```
5. Analyze commits for:
- Breaking changes (BREAKING CHANGE, !, feat())
- New features (feat:, feature:)
- Bug fixes (fix:, bugfix:)
- Documentation changes (docs:)
- Dependency updates
6. **VERIFY PR TARGET BRANCHES**:
```bash
# Get merged PRs and verify they were merged to main
gh pr list --state merged --limit 50 --json number,title,baseRefName,mergedAt | \
jq -r '.[] | select(.baseRefName == "main") | "\(.number): \(.title)"'
```
7. **HUMAN ANALYSIS**: Review change summary and verify scope
### Step 3: Version Preview
**Version Preview:**
- Current: `${CURRENT_VERSION}`
- Proposed: Show exact version number
- **CONFIRMATION REQUIRED**: Proceed with version `X.Y.Z`?
### Step 4: Security and Dependency Audit
1. Run security audit:
```bash
npm audit --audit-level moderate
```
2. Check for known vulnerabilities in dependencies
3. Scan for hardcoded secrets or credentials:
```bash
git log -p ${BASE_TAG}..HEAD | grep -iE "(password|key|secret|token)" || echo "No sensitive data found"
```
4. Verify no sensitive data in recent commits
5. **SECURITY REVIEW**: Address any critical findings before proceeding?
### Step 5: Pre-Release Testing
1. Run complete test suite:
```bash
npm run test:unit
npm run test:component
npm run test:browser
```
2. Run type checking:
```bash
npm run typecheck
```
3. Run linting (may have issues with missing packages):
```bash
npm run lint || echo "Lint issues - verify if critical"
```
4. Test build process:
```bash
npm run build
npm run build:types
```
5. **QUALITY GATE**: All tests and builds passing?
### Step 6: Breaking Change Analysis
1. Analyze API changes in:
- Public TypeScript interfaces
- Extension APIs
- Component props
- CLAUDE.md guidelines
2. Check for:
- Removed public functions/classes
- Changed function signatures
- Deprecated feature removals
- Configuration changes
3. Generate breaking change summary
4. **COMPATIBILITY REVIEW**: Breaking changes documented and justified?
### Step 7: Generate and Save Changelog
1. Extract commit messages since base release:
```bash
git log ${BASE_TAG}..HEAD --oneline --no-merges --first-parent > commits.txt
```
2. **CRITICAL**: Verify PR inclusion by checking merge location:
```bash
# For each significant PR mentioned, verify it's on main
for PR in ${SIGNIFICANT_PRS}; do
COMMIT=$(gh pr view $PR --json mergeCommit -q .mergeCommit.oid)
git branch -r --contains $COMMIT | grep -q "origin/main" || \
echo "WARNING: PR #$PR not on main branch!"
done
```
3. Group by type:
- 🚀 **Features** (feat:)
- 🐛 **Bug Fixes** (fix:)
- 💥 **Breaking Changes** (BREAKING CHANGE)
- 📚 **Documentation** (docs:)
- 🔧 **Maintenance** (chore:, refactor:)
- ⬆️ **Dependencies** (deps:, dependency updates)
4. Include PR numbers and links
5. Add issue references (Fixes #123)
6. **Save changelog locally:**
```bash
# Save to dated file for history
echo "$CHANGELOG" > release-notes-${NEW_VERSION}-$(date +%Y%m%d).md
# Save to current for easy access
echo "$CHANGELOG" > CURRENT_RELEASE_NOTES.md
```
7. **CHANGELOG REVIEW**: Verify all PRs listed are actually on main branch
### Step 8: Create Enhanced Release Notes
1. Create comprehensive user-facing release notes including:
- **What's New**: Major features and improvements
- **Bug Fixes**: User-visible fixes
- **Breaking Changes**: Migration guide if applicable
- **Dependencies**: Major dependency updates
- **Performance**: Notable performance improvements
- **Contributors**: Thank contributors for their work
2. Reference related documentation updates
3. Include screenshots for UI changes (if available)
4. **Save release notes:**
```bash
# Enhanced release notes for GitHub
echo "$RELEASE_NOTES" > github-release-notes-${NEW_VERSION}.md
```
5. **CONTENT REVIEW**: Release notes clear and helpful for users?
### Step 9: Create Version Bump PR
**For standard version bumps (patch/minor/major):**
```bash
# Trigger the workflow
gh workflow run version-bump.yaml -f version_type=${VERSION_TYPE}
# Workflow runs quickly - usually creates PR within 30 seconds
echo "Workflow triggered. Waiting for PR creation..."
```
**For releasing a stable version:**
1. Must manually create branch and update version:
```bash
git checkout -b version-bump-${NEW_VERSION}
# Edit package.json to remove pre-release suffix
git add package.json
git commit -m "${NEW_VERSION}"
git push origin version-bump-${NEW_VERSION}
```
2. Wait for PR creation (if using workflow) or create manually:
```bash
# For workflow-created PRs - wait and find it
sleep 30
# Look for PR from comfy-pr-bot (not github-actions)
PR_NUMBER=$(gh pr list --author comfy-pr-bot --limit 1 --json number --jq '.[0].number')
# Verify we got the PR
if [ -z "$PR_NUMBER" ]; then
echo "PR not found yet. Checking recent PRs..."
gh pr list --limit 5 --json number,title,author
fi
# For manual PRs
gh pr create --title "${NEW_VERSION}" \
--body-file enhanced-pr-description.md \
--label "Release"
```
3. **Create enhanced PR description:**
```bash
cat > enhanced-pr-description.md << EOF
# Release v${NEW_VERSION}
## Version Change
\`${CURRENT_VERSION}\` → \`${NEW_VERSION}\` (${VERSION_TYPE})
## Changelog
${CHANGELOG}
## Breaking Changes
${BREAKING_CHANGES}
## Testing Performed
- ✅ Full test suite (unit, component, browser)
- ✅ TypeScript compilation
- ✅ Linting checks
- ✅ Build verification
- ✅ Security audit
## Distribution Channels
- GitHub Release (with dist.zip)
- PyPI Package (comfyui-frontend-package)
- npm Package (@comfyorg/comfyui-frontend-types)
## Post-Release Tasks
- [ ] Verify all distribution channels
- [ ] Update external documentation
- [ ] Monitor for issues
EOF
```
4. Update PR with enhanced description:
```bash
gh pr edit ${PR_NUMBER} --body-file enhanced-pr-description.md
```
5. Add changelog as comment for easy reference:
```bash
gh pr comment ${PR_NUMBER} --body-file CURRENT_RELEASE_NOTES.md
```
6. **PR REVIEW**: Version bump PR created and enhanced correctly?
### Step 11: Critical Release PR Verification
1. **CRITICAL**: Verify PR has "Release" label:
```bash
gh pr view ${PR_NUMBER} --json labels | jq -r '.labels[].name' | grep -q "Release" || \
echo "ERROR: Release label missing! Add it immediately!"
```
2. Check for update-locales commits:
```bash
# WARNING: update-locales may add [skip ci] which blocks release workflow!
gh pr view ${PR_NUMBER} --json commits | grep -q "skip ci" && \
echo "WARNING: [skip ci] detected - release workflow may not trigger!"
```
3. Verify version number in package.json
4. Review all changed files
5. Ensure no unintended changes included
6. Wait for required PR checks:
```bash
gh pr checks ${PR_NUMBER} --watch
```
7. **FINAL CODE REVIEW**: Release label present and no [skip ci]?
### Step 12: Pre-Merge Validation
1. **Review Requirements**: Release PRs require approval
2. Monitor CI checks - watch for update-locales
3. **CRITICAL WARNING**: If update-locales adds [skip ci], the release workflow won't trigger!
4. Check no new commits to main since PR creation
5. **DEPLOYMENT READINESS**: Ready to merge?
### Step 13: Execute Release
1. **FINAL CONFIRMATION**: Merge PR to trigger release?
2. Merge the Release PR:
```bash
gh pr merge ${PR_NUMBER} --merge
```
3. **IMMEDIATELY CHECK**: Did release workflow trigger?
```bash
sleep 10
gh run list --workflow=release.yaml --limit=1
```
4. If workflow didn't trigger due to [skip ci]:
```bash
echo "ERROR: Release workflow didn't trigger!"
echo "Options:"
echo "1. Create patch release (e.g., 1.24.1) to trigger workflow"
echo "2. Investigate manual release options"
```
5. If workflow triggered, monitor execution:
```bash
WORKFLOW_RUN_ID=$(gh run list --workflow=release.yaml --limit=1 --json databaseId --jq '.[0].databaseId')
gh run watch ${WORKFLOW_RUN_ID}
```
### Step 14: Enhance GitHub Release
1. Wait for automatic release creation:
```bash
# Wait for release to be created
while ! gh release view v${NEW_VERSION} >/dev/null 2>&1; do
echo "Waiting for release creation..."
sleep 10
done
```
2. **Enhance the GitHub release:**
```bash
# Update release with our enhanced notes
gh release edit v${NEW_VERSION} \
--title "🚀 ComfyUI Frontend v${NEW_VERSION}" \
--notes-file github-release-notes-${NEW_VERSION}.md \
--latest
# Add any additional assets if needed
# gh release upload v${NEW_VERSION} additional-assets.zip
```
3. **Verify release details:**
```bash
gh release view v${NEW_VERSION}
```
### Step 15: Verify Multi-Channel Distribution
1. **GitHub Release:**
```bash
gh release view v${NEW_VERSION} --json assets,body,createdAt,tagName
```
- ✅ Check release notes
- ✅ Verify dist.zip attachment
- ✅ Confirm release marked as latest (for main branch)
2. **PyPI Package:**
```bash
# Check PyPI availability (may take a few minutes)
for i in {1..10}; do
if curl -s https://pypi.org/pypi/comfyui-frontend-package/json | jq -r '.releases | keys[]' | grep -q ${NEW_VERSION}; then
echo "✅ PyPI package available"
break
fi
echo "⏳ Waiting for PyPI package... (attempt $i/10)"
sleep 30
done
```
3. **npm Package:**
```bash
# Check npm availability
for i in {1..10}; do
if npm view @comfyorg/comfyui-frontend-types@${NEW_VERSION} version >/dev/null 2>&1; then
echo "✅ npm package available"
break
fi
echo "⏳ Waiting for npm package... (attempt $i/10)"
sleep 30
done
```
4. **DISTRIBUTION VERIFICATION**: All channels published successfully?
### Step 16: Post-Release Monitoring Setup
1. **Monitor immediate release health:**
```bash
# Check for immediate issues
gh issue list --label "bug" --state open --limit 5 --json title,number,createdAt
# Monitor download metrics (if accessible)
gh release view v${NEW_VERSION} --json assets --jq '.assets[].downloadCount'
```
2. **Update documentation tracking:**
```bash
cat > post-release-checklist.md << EOF
# Post-Release Checklist for v${NEW_VERSION}
## Immediate Tasks (Next 24 hours)
- [ ] Monitor error rates and user feedback
- [ ] Watch for critical issues
- [ ] Verify documentation is up to date
- [ ] Check community channels for questions
## Short-term Tasks (Next week)
- [ ] Update external integration guides
- [ ] Monitor adoption metrics
- [ ] Gather user feedback
- [ ] Plan next release cycle
## Long-term Tasks
- [ ] Analyze release process improvements
- [ ] Update release templates based on learnings
- [ ] Document any new patterns discovered
## Key Metrics to Track
- Download counts: GitHub, PyPI, npm
- Issue reports related to v${NEW_VERSION}
- Community feedback and adoption
- Performance impact measurements
EOF
```
3. **Create release summary:**
```bash
cat > release-summary-${NEW_VERSION}.md << EOF
# Release Summary: ComfyUI Frontend v${NEW_VERSION}
**Released:** $(date)
**Type:** ${VERSION_TYPE}
**Duration:** ~${RELEASE_DURATION} minutes
**Release Commit:** ${RELEASE_COMMIT}
## Metrics
- **Commits Included:** ${COMMITS_COUNT}
- **Contributors:** ${CONTRIBUTORS_COUNT}
- **Files Changed:** ${FILES_CHANGED}
- **Lines Added/Removed:** +${LINES_ADDED}/-${LINES_REMOVED}
## Distribution Status
- ✅ GitHub Release: Published
- ✅ PyPI Package: Available
- ✅ npm Types: Available
## Next Steps
- Monitor for 24-48 hours
- Address any critical issues immediately
- Plan next release cycle
## Files Generated
- \`release-notes-${NEW_VERSION}-$(date +%Y%m%d).md\` - Detailed changelog
- \`github-release-notes-${NEW_VERSION}.md\` - GitHub release notes
- \`post-release-checklist.md\` - Follow-up tasks
EOF
```
4. **RELEASE COMPLETION**: All post-release setup completed?
## Advanced Safety Features
### Rollback Procedures
**Pre-Merge Rollback:**
```bash
# Close version bump PR and reset
gh pr close ${PR_NUMBER}
git reset --hard origin/main
git clean -fd
```
**Post-Merge Rollback:**
```bash
# Create immediate patch release with reverts
git revert ${RELEASE_COMMIT}
# Follow this command again with patch version
```
**Emergency Procedures:**
```bash
# Document incident
cat > release-incident-${NEW_VERSION}.md << EOF
# Release Incident Report
**Version:** ${NEW_VERSION}
**Issue:** [Describe the problem]
**Impact:** [Severity and scope]
**Resolution:** [Steps taken]
**Prevention:** [Future improvements]
EOF
# Contact package registries for critical issues
echo "For critical security issues, consider:"
echo "- PyPI: Contact support for package yanking"
echo "- npm: Use 'npm unpublish' within 72 hours"
echo "- GitHub: Update release with warning notes"
```
### Quality Gates Summary
The command implements multiple quality gates:
1. **🔒 Security Gate**: Vulnerability scanning, secret detection
2. **🧪 Quality Gate**: Full test suite, linting, type checking
3. **📋 Content Gate**: Changelog accuracy, release notes quality
4. **🔄 Process Gate**: Release timing verification
5. **✅ Verification Gate**: Multi-channel publishing confirmation
6. **📊 Monitoring Gate**: Post-release health tracking
## Common Scenarios
### Scenario 1: Regular Feature Release
```bash
/project:create-frontend-release minor
```
- Analyzes features since last release
- Generates changelog automatically
- Creates comprehensive release notes
### Scenario 2: Critical Security Patch
```bash
/project:create-frontend-release patch "Security fixes for CVE-2024-XXXX"
```
- Expedited security scanning
- Enhanced monitoring setup
### Scenario 3: Major Version with Breaking Changes
```bash
/project:create-frontend-release major
```
- Comprehensive breaking change analysis
- Migration guide generation
### Scenario 4: Pre-release Testing
```bash
/project:create-frontend-release prerelease
```
- Creates alpha/beta/rc versions
- Draft release status
- Python package specs require that prereleases use alpha/beta/rc as the preid
## Common Issues and Solutions
### Issue: Pre-release Version Confusion
**Problem**: Not sure whether to promote pre-release or create new version
**Solution**:
- Follow semver standards: a prerelease version is followed by a normal release. It should have the same major, minor, and patch versions as the prerelease.
### Issue: Wrong Commit Count
**Problem**: Changelog includes commits from other branches
**Solution**: Always use `--first-parent` flag with git log
**Update**: Sometimes update-locales doesn't add [skip ci] - always verify!
### Issue: Missing PRs in Changelog
**Problem**: PR was merged to different branch
**Solution**: Verify PR merge target with:
```bash
gh pr view ${PR_NUMBER} --json baseRefName
```
### Issue: Release Failed Due to [skip ci]
**Problem**: Release workflow didn't trigger after merge
**Prevention**: Always avoid this scenario
- Ensure that `[skip ci]` or similar flags are NOT in the `HEAD` commit message of the PR
- Push a new, empty commit to the PR
- Always double-check this immediately before merging
**Recovery Strategy**:
1. Revert version in a new PR (e.g., 1.24.0 → 1.24.0-1)
2. Merge the revert PR
3. Run version bump workflow again
4. This creates a fresh PR without [skip ci]
Benefits: Cleaner than creating extra version numbers
## Key Learnings & Notes
1. **PR Author**: Version bump PRs are created by `comfy-pr-bot`, not `github-actions`
2. **Workflow Speed**: Version bump workflow typically completes in ~20-30 seconds
3. **Update-locales Behavior**: Inconsistent - sometimes adds [skip ci], sometimes doesn't
4. **Recovery Options**: Reverting version is cleaner than creating extra versions

View File

@@ -1,222 +0,0 @@
# Create Hotfix Release
This command guides you through creating a patch/hotfix release for ComfyUI Frontend with comprehensive safety checks and human confirmations at each step.
<task>
Create a hotfix release by cherry-picking commits or PR commits from main to a core branch: $ARGUMENTS
Expected format: Comma-separated list of commits or PR numbers
Examples:
- `abc123,def456,ghi789` (commits)
- `#1234,#5678` (PRs)
- `abc123,#1234,def456` (mixed)
If no arguments provided, the command will help identify the correct core branch and guide you through selecting commits/PRs.
</task>
## Prerequisites
Before starting, ensure:
- You have push access to the repository
- GitHub CLI (`gh`) is authenticated
- You're on a clean working tree
- You understand the commits/PRs you're cherry-picking
## Hotfix Release Process
### Step 1: Identify Target Core Branch
1. Fetch the current ComfyUI requirements.txt from master branch:
```bash
curl -s https://raw.githubusercontent.com/comfyanonymous/ComfyUI/master/requirements.txt | grep "comfyui-frontend-package"
```
2. Extract the `comfyui-frontend-package` version (e.g., `comfyui-frontend-package==1.23.4`)
3. Parse version to get major.minor (e.g., `1.23.4` → `1.23`)
4. Determine core branch: `core/<major>.<minor>` (e.g., `core/1.23`)
5. Verify the core branch exists: `git ls-remote origin refs/heads/core/*`
6. **CONFIRMATION REQUIRED**: Is `core/X.Y` the correct target branch?
### Step 2: Parse and Validate Arguments
1. Parse the comma-separated list of commits/PRs
2. For each item:
- If starts with `#`: Treat as PR number
- Otherwise: Treat as commit hash
3. For PR numbers:
- Fetch PR details using `gh pr view <number>`
- Extract the merge commit if PR is merged
- If PR has multiple commits, list them all
- **CONFIRMATION REQUIRED**: Use merge commit or cherry-pick individual commits?
4. Validate all commit hashes exist in the repository
### Step 3: Analyze Target Changes
1. For each commit/PR to cherry-pick:
- Display commit hash, author, date
- Show PR title and number (if applicable)
- Display commit message
- Show files changed and diff statistics
- Check if already in core branch: `git branch --contains <commit>`
2. Identify potential conflicts by checking changed files
3. **CONFIRMATION REQUIRED**: Proceed with these commits?
### Step 4: Create Hotfix Branch
1. Checkout the core branch (e.g., `core/1.23`)
2. Pull latest changes: `git pull origin core/X.Y`
3. Display current version from package.json
4. Create hotfix branch: `hotfix/<version>-<timestamp>`
- Example: `hotfix/1.23.4-20241120`
5. **CONFIRMATION REQUIRED**: Created branch correctly?
### Step 5: Cherry-pick Changes
For each commit:
1. Attempt cherry-pick: `git cherry-pick <commit>`
2. If conflicts occur:
- Display conflict details
- Show conflicting sections
- Provide resolution guidance
- **CONFIRMATION REQUIRED**: Conflicts resolved correctly?
3. After successful cherry-pick:
- Show the changes: `git show HEAD`
- Run validation: `npm run typecheck && npm run lint`
4. **CONFIRMATION REQUIRED**: Cherry-pick successful and valid?
### Step 6: Create PR to Core Branch
1. Push the hotfix branch: `git push origin hotfix/<version>-<timestamp>`
2. Create PR using gh CLI:
```bash
gh pr create --base core/X.Y --head hotfix/<version>-<timestamp> \
--title "[Hotfix] Cherry-pick fixes to core/X.Y" \
--body "Cherry-picked commits: ..."
```
3. Add appropriate labels (but NOT "Release" yet)
4. PR body should include:
- List of cherry-picked commits/PRs
- Original issue references
- Testing instructions
- Impact assessment
5. **CONFIRMATION REQUIRED**: PR created correctly?
### Step 7: Wait for Tests
1. Monitor PR checks: `gh pr checks`
2. Display test results as they complete
3. If any tests fail:
- Show failure details
- Analyze if related to cherry-picks
- **DECISION REQUIRED**: Fix and continue, or abort?
4. Wait for all required checks to pass
5. **CONFIRMATION REQUIRED**: All tests passing?
### Step 8: Merge Hotfix PR
1. Verify all checks have passed
2. Check for required approvals
3. Merge the PR: `gh pr merge --merge`
4. Delete the hotfix branch
5. **CONFIRMATION REQUIRED**: PR merged successfully?
### Step 9: Create Version Bump
1. Checkout the core branch: `git checkout core/X.Y`
2. Pull latest changes: `git pull origin core/X.Y`
3. Read current version from package.json
4. Determine patch version increment:
- Current: `1.23.4` → New: `1.23.5`
5. Create release branch named with new version: `release/1.23.5`
6. Update version in package.json to `1.23.5`
7. Commit: `git commit -m "[release] Bump version to 1.23.5"`
8. **CONFIRMATION REQUIRED**: Version bump correct?
### Step 10: Create Release PR
1. Push release branch: `git push origin release/1.23.5`
2. Create PR with Release label:
```bash
gh pr create --base core/X.Y --head release/1.23.5 \
--title "[Release] v1.23.5" \
--body "..." \
--label "Release"
```
3. **CRITICAL**: Verify "Release" label is added
4. PR description should include:
- Version: `1.23.4` → `1.23.5`
- Included fixes (link to previous PR)
- Release notes for users
5. **CONFIRMATION REQUIRED**: Release PR has "Release" label?
### Step 11: Monitor Release Process
1. Wait for PR checks to pass
2. **FINAL CONFIRMATION**: Ready to trigger release by merging?
3. Merge the PR: `gh pr merge --merge`
4. Monitor release workflow:
```bash
gh run list --workflow=release.yaml --limit=1
gh run watch
```
5. Track progress:
- GitHub release draft/publication
- PyPI upload
- npm types publication
### Step 12: Post-Release Verification
1. Verify GitHub release:
```bash
gh release view v1.23.5
```
2. Check PyPI package:
```bash
pip index versions comfyui-frontend-package | grep 1.23.5
```
3. Verify npm package:
```bash
npm view @comfyorg/comfyui-frontend-types@1.23.5
```
4. Generate release summary with:
- Version released
- Commits included
- Issues fixed
- Distribution status
5. **CONFIRMATION REQUIRED**: Release completed successfully?
## Safety Checks
Throughout the process:
- Always verify core branch matches ComfyUI's requirements.txt
- For PRs: Ensure using correct commits (merge vs individual)
- Check version numbers follow semantic versioning
- **Critical**: "Release" label must be on version bump PR
- Validate cherry-picks don't break core branch stability
- Keep audit trail of all operations
## Rollback Procedures
If something goes wrong:
- Before push: `git reset --hard origin/core/X.Y`
- After PR creation: Close PR and start over
- After failed release: Create new patch version with fixes
- Document any issues for future reference
## Important Notes
- Core branch version will be behind main - this is expected
- The "Release" label triggers the PyPI/npm publication
- PR numbers must include the `#` prefix
- Mixed commits/PRs are supported but review carefully
- Always wait for full test suite before proceeding
## Expected Timeline
- Step 1-3: ~10 minutes (analysis)
- Steps 4-6: ~15-30 minutes (cherry-picking)
- Step 7: ~10-20 minutes (tests)
- Steps 8-10: ~10 minutes (version bump)
- Step 11-12: ~15-20 minutes (release)
- Total: ~60-90 minutes
This process ensures a safe, verified hotfix release with multiple confirmation points and clear tracking of what changes are being released.

View File

@@ -1,57 +0,0 @@
Your task is to perform visual verification of our recent changes to ensure they display correctly in the browser. This verification is critical for catching visual regressions, layout issues, and ensuring our UI changes render properly for end users.
<instructions>
Follow these steps systematically to verify our changes:
1. **Server Setup**
- Check if the dev server is running on port 5173 using browser navigation or port checking
- If not running, start it with `npm run dev` from the root directory
- If the server fails to start, provide detailed troubleshooting steps by reading package.json and README.md for accurate instructions
- Wait for the server to be fully ready before proceeding
2. **Visual Testing Process**
- Navigate to http://localhost:5173/
- For each target page (specified in arguments or recently changed files):
* Navigate to the page using direct URL or site navigation
* Take a high-quality screenshot
* Analyze the screenshot for the specific changes we implemented
* Document any visual issues or improvements needed
3. **Quality Verification**
Check each page for:
- Content accuracy and completeness
- Proper styling and layout alignment
- Responsive design elements
- Navigation functionality
- Image loading and display
- Typography and readability
- Color scheme consistency
- Interactive elements (buttons, links, forms)
</instructions>
<examples>
Common issues to watch for:
- Broken layouts or overlapping elements
- Missing images or broken image links
- Inconsistent styling or spacing
- Navigation menu problems
- Mobile responsiveness issues
- Text overflow or truncation
- Color contrast problems
</examples>
<reporting>
For each page tested, provide:
1. Page URL and screenshot
2. Confirmation that changes display correctly OR detailed description of issues found
3. Any design improvement suggestions
4. Overall assessment of visual quality
If you find issues, be specific about:
- Exact location of the problem
- Expected vs actual behavior
- Severity level (critical, important, minor)
- Suggested fix if obvious
</reporting>
Remember: Take your time with each screenshot and analysis. Visual quality directly impacts user experience and our project's professional appearance.

View File

@@ -1,61 +0,0 @@
# Vue 3 Composition API Project Rules
## Vue 3 Composition API Best Practices
- Use setup() function for component logic
- Utilize ref and reactive for reactive state
- Implement computed properties with computed()
- Use watch and watchEffect for side effects
- Implement lifecycle hooks with onMounted, onUpdated, etc.
- Utilize provide/inject for dependency injection
- Use vue 3.5 style of default prop declaration. Example:
```typescript
const { nodes, showTotal = true } = defineProps<{
nodes: ApiNodeCost[]
showTotal?: boolean
}>()
```
- Organize vue component in <template> <script> <style> order
## Project Structure
```
src/
components/
constants/
composables/
views/
stores/
services/
App.vue
main.ts
```
## Styling Guidelines
- Use Tailwind CSS for styling
- Implement responsive design with Tailwind CSS
## PrimeVue Component Guidelines
DO NOT use deprecated PrimeVue components. Use these replacements instead:
- Dropdown → Use Select (import from 'primevue/select')
- OverlayPanel → Use Popover (import from 'primevue/popover')
- Calendar → Use DatePicker (import from 'primevue/datepicker')
- InputSwitch → Use ToggleSwitch (import from 'primevue/toggleswitch')
- Sidebar → Use Drawer (import from 'primevue/drawer')
- Chips → Use AutoComplete with multiple enabled and typeahead disabled
- TabMenu → Use Tabs without panels
- Steps → Use Stepper without panels
- InlineMessage → Use Message component
## Development Guidelines
1. Leverage VueUse functions for performance-enhancing styles
2. Use lodash for utility functions
3. Use TypeScript for type safety
4. Implement proper props and emits definitions
5. Utilize Vue 3's Teleport component when needed
6. Use Suspense for async components
7. Implement proper error handling
8. Follow Vue 3 style guide and naming conventions
9. Use Vite for fast development and building
10. Use vue-i18n in composition API for any string literals. Place new translation entries in src/locales/en/main.json
11. Never use deprecated PrimeVue components listed above

View File

@@ -1,5 +1,4 @@
# Local development playwright target
# Note: Don't add a trailing / after the port
PLAYWRIGHT_TEST_URL=http://localhost:5173
# PLAYWRIGHT_TEST_URL=http://localhost:8188
@@ -7,29 +6,18 @@ PLAYWRIGHT_TEST_URL=http://localhost:5173
# Note: localhost:8188 does not work.
DEV_SERVER_COMFYUI_URL=http://127.0.0.1:8188
# Allow dev server access from remote IP addresses.
# If true, the vite dev server will listen on all addresses, including LAN
# and public addresses.
VITE_REMOTE_DEV=false
# The target ComfyUI checkout directory to deploy the frontend code to.
# The dist directory will be copied to {DEPLOY_COMFYUI_DIR}/custom_web_versions/main/dev
# Add `--front-end-root {DEPLOY_COMFYUI_DIR}/custom_web_versions/main/dev`
# to ComfyUI launch script to serve the custom web version.
DEPLOY_COMFYUI_DIR=/home/ComfyUI/web
# The directory containing the ComfyUI installation used to run Playwright tests.
# If you aren't using a separate install for testing, point this to your regular install.
TEST_COMFYUI_DIR=/home/ComfyUI
# The directory containing the ComfyUI_examples repo used to extract test workflows.
EXAMPLE_REPO_PATH=tests-ui/ComfyUI_examples
# Whether to enable minification of the frontend code.
ENABLE_MINIFY=true
# Whether to disable proxying the `/templates` route. If true, allows you to
# serve templates from the ComfyUI_frontend/public/templates folder (for
# locally testing changes to templates). When false or nonexistent, the
# templates are served via the normal method from the server's python site
# packages.
DISABLE_TEMPLATES_PROXY=false
# If playwright tests are being run via vite dev server, Vue plugins will
# invalidate screenshots. When `true`, vite plugins will not be loaded.
DISABLE_VUE_PLUGINS=false
# Algolia credentials required for developing with the new custom node manager.
ALGOLIA_APP_ID=4E0RO38HS8
ALGOLIA_API_KEY=684d998c36b67a9a9fce8fc2d8860579

36
.github/CLAUDE.md vendored
View File

@@ -1,36 +0,0 @@
# ComfyUI Frontend - Claude Review Context
This file provides additional context for the automated PR review system.
## Quick Reference
### PrimeVue Component Migrations
When reviewing, flag these deprecated components:
- `Dropdown` → Use `Select` from 'primevue/select'
- `OverlayPanel` → Use `Popover` from 'primevue/popover'
- `Calendar` → Use `DatePicker` from 'primevue/datepicker'
- `InputSwitch` → Use `ToggleSwitch` from 'primevue/toggleswitch'
- `Sidebar` → Use `Drawer` from 'primevue/drawer'
- `Chips` → Use `AutoComplete` with multiple enabled and typeahead disabled
- `TabMenu` → Use `Tabs` without panels
- `Steps` → Use `Stepper` without panels
- `InlineMessage` → Use `Message` component
### API Utilities Reference
- `api.apiURL()` - Backend API calls (/prompt, /queue, /view, etc.)
- `api.fileURL()` - Static file access (templates, extensions)
- `$t()` / `i18n.global.t()` - Internationalization
- `DOMPurify.sanitize()` - HTML sanitization
## Review Scope
This automated review performs comprehensive analysis including:
- Architecture and design patterns
- Security vulnerabilities
- Performance implications
- Code quality and maintainability
- Integration concerns
For implementation details, see `.claude/commands/comprehensive-pr-review.md`.

View File

@@ -1,9 +1,7 @@
name: Bug Report
description: 'Something is not behaving as expected.'
title: '[Bug]: '
description: "Something is not behaving as expected."
title: "[Bug]: "
labels: ['Potential Bug']
type: Bug
body:
- type: markdown
attributes:
@@ -12,31 +10,13 @@ body:
- **1:** You are running the latest version of ComfyUI.
- **2:** You have looked at the existing bug reports and made sure this isn't already reported.
- type: checkboxes
id: custom-nodes-test
attributes:
label: Custom Node Testing
description: Please confirm you have tried to reproduce the issue with all custom nodes disabled.
options:
- label: I have tried disabling custom nodes and the issue persists (see [how to disable custom nodes](https://docs.comfy.org/troubleshooting/custom-node-issues#step-1%3A-test-with-all-custom-nodes-disabled) if you need help)
required: true
- **3:** You confirmed that the bug is not caused by a custom node. You can disable all custom nodes by passing
`--disable-all-custom-nodes` command line argument.
- type: textarea
attributes:
label: Frontend Version
description: |
What is the frontend version you are using? You can check this in the settings dialog.
<details>
<summary>Click to show where to find the version</summary>
Open the setting by clicking the cog icon in the bottom-left of the screen, then click `About`.
![Frontend version](https://github.com/user-attachments/assets/561fb7c3-3012-457c-a494-9bdc1ff035c0)
</details>
description: 'What is the frontend version you are using? You can check this in the settings dialog'
validations:
required: true
- type: textarea
@@ -92,8 +72,7 @@ body:
- Other
- type: textarea
attributes:
label: Other Information
description: 'Any other context, details, or screenshots that might help solve the issue.'
placeholder: 'Add any other relevant information here...'
label: Other
description: 'Any other additional information you think might be helpful.'
validations:
required: false

View File

@@ -1,8 +1,7 @@
name: Feature Request
description: Suggest an idea for this project
title: '[Feature Request]: '
labels: ['enhancement']
type: Feature
title: "[Feature Request]: "
labels: ["enhancement"]
body:
- type: checkboxes

View File

@@ -1,37 +0,0 @@
Use the Vue 3 Composition API instead of the Options API when writing Vue components. An exception is when overriding or extending a PrimeVue component for compatibility, you may use the Options API.
Use setup() function for component logic
Utilize ref and reactive for reactive state
Implement computed properties with computed()
Use watch and watchEffect for side effects
Implement lifecycle hooks with onMounted, onUpdated, etc.
Utilize provide/inject for dependency injection
Use vue 3.5 style of default prop declaration.
Use Tailwind CSS for styling
Leverage VueUse functions for performance-enhancing styles
Use lodash for utility functions
Use TypeScript for type safety
Implement proper props and emits definitions
Utilize Vue 3's Teleport component when needed
Use Suspense for async components
Implement proper error handling
Follow Vue 3 style guide and naming conventions
Use Vite for fast development and building
Use vue-i18n in composition API for any string literals. Place new translation entries in src/locales/en/main.json.

View File

@@ -1,75 +0,0 @@
name: Claude PR Review
permissions:
contents: read
pull-requests: write
issues: write
on:
pull_request:
types: [labeled]
jobs:
wait-for-ci:
runs-on: ubuntu-latest
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@v1.3.1
with:
ref: ${{ github.event.pull_request.head.sha }}
check-regexp: '^(ESLint|Prettier Check|Tests CI|Vitest Tests)'
wait-interval: 30
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Check if we should proceed
id: check-status
run: |
# Get all check runs for this commit
CHECK_RUNS=$(gh api repos/${{ github.repository }}/commits/${{ github.event.pull_request.head.sha }}/check-runs --jq '.check_runs[] | select(.name | test("ESLint|Prettier Check|Tests CI|Vitest Tests")) | {name, conclusion}')
# Check if any required checks failed
if echo "$CHECK_RUNS" | grep -q '"conclusion": "failure"'; 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
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies for analysis tools
run: |
npm install -g typescript @vue/compiler-sfc
- name: Run Claude PR Review
uses: anthropics/claude-code-action@main
with:
prompt_file: .claude/commands/comprehensive-pr-review.md
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
max_turns: 1
timeout_minutes: 30
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
REPOSITORY: ${{ github.repository }}

View File

@@ -1,72 +0,0 @@
name: Create Dev PyPI Package
on:
workflow_dispatch:
inputs:
devVersion:
description: 'Dev version'
required: true
type: number
jobs:
build:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.current_version.outputs.version }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 'lts/*'
- name: Get current version
id: current_version
run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
- name: Build project
env:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }}
ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }}
USE_PROD_CONFIG: 'true'
run: |
npm ci
npm run build
npm run zipdist
- name: Upload dist artifact
uses: actions/upload-artifact@v4
with:
name: dist-files
path: |
dist/
dist.zip
publish_pypi:
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download dist artifact
uses: actions/download-artifact@v4
with:
name: dist-files
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Install build dependencies
run: python -m pip install build
- name: Setup pypi package
run: |
mkdir -p comfyui_frontend_package/comfyui_frontend_package/static/
cp -r dist/* comfyui_frontend_package/comfyui_frontend_package/static/
- name: Build pypi package
run: python -m build
working-directory: comfyui_frontend_package
env:
COMFYUI_FRONTEND_VERSION: ${{ format('{0}.dev{1}', needs.build.outputs.version, inputs.devVersion) }}
- name: Publish pypi package
uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc
with:
password: ${{ secrets.PYPI_TOKEN }}
packages-dir: comfyui_frontend_package/dist

View File

@@ -2,7 +2,10 @@ name: ESLint
on:
pull_request:
branches: [ main, master, dev*, core/*, desktop/* ]
branches:
- main
- master
- 'dev*'
jobs:
eslint:
@@ -10,8 +13,8 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v3
with:
node-version: 'lts/*'
node-version: lts/*
- run: npm ci
- run: npm run lint
- run: npm run lint

View File

@@ -2,7 +2,7 @@ name: Prettier Check
on:
pull_request:
branches: [ main, master, dev*, core/*, desktop/* ]
branches: [ main, master, dev* ]
jobs:
prettier:
@@ -12,12 +12,12 @@ jobs:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v3
with:
node-version: 'lts/*'
node-version: lts/*
- name: Install dependencies
run: npm ci
- name: Run Prettier check
run: npm run format:check
run: npx prettier --check './**/*.{js,ts,tsx,vue}'

View File

@@ -1,159 +0,0 @@
name: Update Locales for given custom node repository
on:
workflow_dispatch:
inputs:
owner:
description: 'Owner of the repository to update locales for'
required: true
type: string
repository:
description: 'Repository to update locales for'
required: true
type: string
fork_owner:
description: 'Owner of the forked repository'
required: false
type: string
default: 'Comfy-Org'
jobs:
update-locales:
runs-on: ubuntu-latest
steps:
- name: Checkout ComfyUI
uses: actions/checkout@v4
with:
repository: comfyanonymous/ComfyUI
path: ComfyUI
ref: master
- name: Checkout ComfyUI_frontend
uses: actions/checkout@v4
with:
repository: Comfy-Org/ComfyUI_frontend
path: ComfyUI_frontend
- name: Checkout ComfyUI_devtools
uses: actions/checkout@v4
with:
repository: Comfy-Org/ComfyUI_devtools
path: ComfyUI/custom_nodes/ComfyUI_devtools
- name: Checkout custom node repository
uses: actions/checkout@v4
with:
repository: ${{ inputs.owner }}/${{ inputs.repository }}
path: 'ComfyUI/custom_nodes/${{ inputs.repository }}'
- uses: actions/setup-node@v4
with:
node-version: 'lts/*'
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install ComfyUI requirements
run: |
python -m pip install --upgrade pip
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
pip install -r requirements.txt
pip install wait-for-it
working-directory: ComfyUI
- name: Install custom node requirements
run: |
if [ -f "requirements.txt" ]; then
pip install -r requirements.txt
fi
working-directory: ComfyUI/custom_nodes/${{ inputs.repository }}
- name: Build & Install ComfyUI_frontend
run: |
npm ci
npm run build
rm -rf ../ComfyUI/web/*
mv dist/* ../ComfyUI/web/
working-directory: ComfyUI_frontend
- name: Start ComfyUI server
run: |
python main.py --cpu --multi-user &
wait-for-it --service 127.0.0.1:8188 -t 600
working-directory: ComfyUI
- name: Install Playwright Browsers
run: npx playwright install chromium --with-deps
working-directory: ComfyUI_frontend
- name: Start dev server
# Run electron dev server as it is a superset of the web dev server
# We do want electron specific UIs to be translated.
run: npm run dev:electron &
working-directory: ComfyUI_frontend
- name: Capture base i18n
run: npx tsx scripts/diff-i18n capture
working-directory: ComfyUI_frontend
- name: Update en.json
run: npm run collect-i18n
env:
PLAYWRIGHT_TEST_URL: http://localhost:5173
working-directory: ComfyUI_frontend
- name: Update translations
run: npm run locale
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
working-directory: ComfyUI_frontend
- name: Diff base vs updated i18n
run: npx tsx scripts/diff-i18n diff
working-directory: ComfyUI_frontend
- name: Update i18n in custom node repository
run: |
LOCALE_DIR=ComfyUI/custom_nodes/${{ inputs.repository }}/locales/
install -d "$LOCALE_DIR"
cp -rf ComfyUI_frontend/temp/diff/* "$LOCALE_DIR"
- name: Check and create fork of custom node repository
run: |
# Try to fork the repository
gh repo fork ${{ inputs.owner }}/${{ inputs.repository }} --clone=false || {
echo "Fork failed - repository might already be forked"
# Exit 0 to prevent the workflow from failing
exit 0
}
# Enable workflows on the forked repository
gh api \
--method PUT \
-H "Accept: application/vnd.github+json" \
"/repos/${{ inputs.fork_owner }}/${{ inputs.repository }}/actions/permissions/workflow" \
-F can_approve_pull_request_reviews=true \
-F default_workflow_permissions="write" \
-F enabled=true
env:
GH_TOKEN: ${{ secrets.PR_GH_TOKEN }}
- name: Commit changes
working-directory: ComfyUI/custom_nodes/${{ inputs.repository }}
run: |
git config --global user.name 'github-actions'
git config --global user.email 'github-actions@github.com'
# Create and switch to new branch
git checkout -b update-locales
# Stage and commit changes
git add -A
git commit -m "Update locales"
- name: Install SSH key For PUSH
uses: shimataro/ssh-key-action@d4fffb50872869abe2d9a9098a6d9c5aa7d16be4
with:
# PR private key from action server
key: ${{ secrets.PR_SSH_PRIVATE_KEY }}
# github public key to confirm it's github server
known_hosts: github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==
- name: Push changes
working-directory: ComfyUI/custom_nodes/${{ inputs.repository }}
run: |
# Force push to create the branch
echo "Pushing changes to ${{ inputs.fork_owner }}/${{ inputs.repository }}"
git push -f git@github.com:${{ inputs.fork_owner }}/${{ inputs.repository }}.git update-locales
- name: Create PR
working-directory: ComfyUI/custom_nodes/${{ inputs.repository }}
run: |
# Create PR using gh cli
gh pr create --title "Update locales for ${{ inputs.repository }}" --repo ${{ inputs.owner }}/${{ inputs.repository }} --head ${{ inputs.fork_owner }}:update-locales --body "Update locales for ${{ inputs.repository }}"
env:
GH_TOKEN: ${{ secrets.PR_GH_TOKEN }}

View File

@@ -1,48 +0,0 @@
name: Update Node Definitions Locales
on:
workflow_dispatch:
inputs:
trigger_type:
description: 'Type of trigger (manual or automatic)'
required: false
type: string
default: 'manual'
jobs:
update-locales:
runs-on: ubuntu-latest
steps:
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v2.3
- name: Install Playwright Browsers
run: npx playwright install chromium --with-deps
working-directory: ComfyUI_frontend
- name: Start dev server
# Run electron dev server as it is a superset of the web dev server
# We do want electron specific UIs to be translated.
run: npm run dev:electron &
working-directory: ComfyUI_frontend
- name: Update en.json
run: npm run collect-i18n -- scripts/collect-i18n-node-defs.ts
env:
PLAYWRIGHT_TEST_URL: http://localhost:5173
working-directory: ComfyUI_frontend
- name: Update translations
run: npm run locale
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
working-directory: ComfyUI_frontend
- name: Create Pull Request
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e
with:
token: ${{ secrets.PR_GH_TOKEN }}
commit-message: "Update locales for node definitions"
title: "Update locales for node definitions"
body: |
Automated PR to update locales for node definitions
This PR was created automatically by the frontend update workflow.
branch: update-locales-node-defs-${{ github.event.inputs.trigger_type }}-${{ github.run_id }}
base: main
labels: dependencies
path: ComfyUI_frontend

View File

@@ -1,51 +0,0 @@
name: Update Locales
on:
pull_request:
branches: [ main, master, dev* ]
paths-ignore:
- '.github/**'
- '.husky/**'
- '.vscode/**'
- 'browser_tests/**'
- 'tests-ui/**'
jobs:
update-locales:
# Don't run on fork PRs
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v2.3
- name: Install Playwright Browsers
run: npx playwright install chromium --with-deps
working-directory: ComfyUI_frontend
- name: Start dev server
# Run electron dev server as it is a superset of the web dev server
# We do want electron specific UIs to be translated.
run: npm run dev:electron &
working-directory: ComfyUI_frontend
- name: Update en.json
run: npm run collect-i18n -- scripts/collect-i18n-general.ts
env:
PLAYWRIGHT_TEST_URL: http://localhost:5173
working-directory: ComfyUI_frontend
- name: Update translations
run: npm run locale
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
working-directory: ComfyUI_frontend
- name: Commit updated locales
run: |
git config --global user.name 'github-actions'
git config --global user.email 'github-actions@github.com'
git fetch origin ${{ github.head_ref }}
# Stash any local changes before checkout
git stash -u
git checkout -B ${{ github.head_ref }} origin/${{ github.head_ref }}
# Apply the stashed changes if any
git stash pop || true
git add src/locales/
git diff --staged --quiet || git commit -m "Update locales [skip ci]"
git push origin HEAD:${{ github.head_ref }}
working-directory: ComfyUI_frontend

View File

@@ -1,154 +0,0 @@
name: PR Checks
on:
pull_request:
types: [opened, edited, synchronize, reopened]
permissions:
contents: read
pull-requests: read
jobs:
analyze:
runs-on: ubuntu-latest
outputs:
should_run: ${{ steps.check-changes.outputs.should_run }}
has_browser_tests: ${{ steps.check-coverage.outputs.has_browser_tests }}
has_screen_recording: ${{ steps.check-recording.outputs.has_recording }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Ensure base branch is available
run: |
# Fetch the specific base commit to ensure it's available for git diff
git fetch origin ${{ github.event.pull_request.base.sha }}
- name: Check if significant changes exist
id: check-changes
run: |
# Get list of changed files
CHANGED_FILES=$(git diff --name-only ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }})
# Filter for src/ files
SRC_FILES=$(echo "$CHANGED_FILES" | grep '^src/' || true)
if [ -z "$SRC_FILES" ]; then
echo "No src/ files changed"
echo "should_run=false" >> "$GITHUB_OUTPUT"
exit 0
fi
# Count lines changed in src files
TOTAL_LINES=0
for file in $SRC_FILES; do
if [ -f "$file" ]; then
# Count added lines (non-empty)
ADDED=$(git diff ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }} -- "$file" | grep '^+' | grep -v '^+++' | grep -v '^+$' | wc -l)
# Count removed lines (non-empty)
REMOVED=$(git diff ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }} -- "$file" | grep '^-' | grep -v '^---' | grep -v '^-$' | wc -l)
TOTAL_LINES=$((TOTAL_LINES + ADDED + REMOVED))
fi
done
echo "Total lines changed in src/: $TOTAL_LINES"
if [ $TOTAL_LINES -gt 3 ]; then
echo "should_run=true" >> "$GITHUB_OUTPUT"
else
echo "should_run=false" >> "$GITHUB_OUTPUT"
fi
- name: Check browser test coverage
id: check-coverage
if: steps.check-changes.outputs.should_run == 'true'
run: |
# Check if browser tests were updated
BROWSER_TEST_CHANGES=$(git diff --name-only ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }} | grep '^browser_tests/.*\.ts$' || true)
if [ -n "$BROWSER_TEST_CHANGES" ]; then
echo "has_browser_tests=true" >> "$GITHUB_OUTPUT"
else
echo "has_browser_tests=false" >> "$GITHUB_OUTPUT"
fi
- name: Check for screen recording
id: check-recording
if: steps.check-changes.outputs.should_run == 'true'
env:
PR_BODY: ${{ github.event.pull_request.body }}
run: |
# Check PR body for screen recording
# Check for GitHub user attachments or YouTube links
if echo "$PR_BODY" | grep -qiE 'github\.com/user-attachments/assets/[a-f0-9-]+|youtube\.com/watch|youtu\.be/'; then
echo "has_recording=true" >> "$GITHUB_OUTPUT"
else
echo "has_recording=false" >> "$GITHUB_OUTPUT"
fi
- name: Final check and create results
id: final-check
if: always()
run: |
# Initialize results
WARNINGS_JSON=""
# Only run checks if should_run is true
if [ "${{ steps.check-changes.outputs.should_run }}" == "true" ]; then
# Check browser test coverage
if [ "${{ steps.check-coverage.outputs.has_browser_tests }}" != "true" ]; then
if [ -n "$WARNINGS_JSON" ]; then
WARNINGS_JSON="${WARNINGS_JSON},"
fi
WARNINGS_JSON="${WARNINGS_JSON}{\"message\":\"⚠️ **Warning: E2E Test Coverage Missing**\\n\\nIf this PR modifies behavior that can be covered by browser-based E2E tests, those tests are required. PRs lacking applicable test coverage may not be reviewed until added. Please add or update browser tests to ensure code quality and prevent regressions.\"}"
fi
# Check screen recording
if [ "${{ steps.check-recording.outputs.has_recording }}" != "true" ]; then
if [ -n "$WARNINGS_JSON" ]; then
WARNINGS_JSON="${WARNINGS_JSON},"
fi
WARNINGS_JSON="${WARNINGS_JSON}{\"message\":\"⚠️ **Warning: Visual Documentation Missing**\\n\\nIf this PR changes user-facing behavior, visual proof (screen recording or screenshot) is required. PRs without applicable visual documentation may not be reviewed until provided.\\nYou can add it by:\\n\\n- GitHub: Drag & drop media directly into the PR description\\n\\n- YouTube: Include a link to a short demo\"}"
fi
fi
# Create results JSON
if [ -n "$WARNINGS_JSON" ]; then
# Create JSON with warnings
cat > pr-check-results.json << EOF
{
"fails": [],
"warnings": [$WARNINGS_JSON],
"messages": [],
"markdowns": []
}
EOF
echo "failed=false" >> "$GITHUB_OUTPUT"
else
# Create JSON with success
cat > pr-check-results.json << 'EOF'
{
"fails": [],
"warnings": [],
"messages": [],
"markdowns": []
}
EOF
echo "failed=false" >> "$GITHUB_OUTPUT"
fi
# Write PR metadata
echo "${{ github.event.pull_request.number }}" > pr-number.txt
echo "${{ github.event.pull_request.head.sha }}" > pr-sha.txt
- name: Upload results
uses: actions/upload-artifact@v4
if: always()
with:
name: pr-check-results-${{ github.run_id }}
path: |
pr-check-results.json
pr-number.txt
pr-sha.txt
retention-days: 1

View File

@@ -1,149 +0,0 @@
name: PR Comment
on:
workflow_run:
workflows: ["PR Checks"]
types: [completed]
permissions:
pull-requests: write
issues: write
statuses: write
jobs:
comment:
if: github.event.workflow_run.event == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: pr-check-results-${{ github.event.workflow_run.id }}
path: /tmp/pr-artifacts
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
- name: Post results
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const path = require('path');
// Helper function to safely read files
function safeReadFile(filePath) {
try {
if (!fs.existsSync(filePath)) return null;
return fs.readFileSync(filePath, 'utf8').trim();
} catch (e) {
console.error(`Error reading ${filePath}:`, e);
return null;
}
}
// Read artifact files
const artifactDir = '/tmp/pr-artifacts';
const prNumber = safeReadFile(path.join(artifactDir, 'pr-number.txt'));
const prSha = safeReadFile(path.join(artifactDir, 'pr-sha.txt'));
const resultsJson = safeReadFile(path.join(artifactDir, 'pr-check-results.json'));
// Validate PR number
if (!prNumber || isNaN(parseInt(prNumber))) {
throw new Error('Invalid or missing PR number');
}
// Parse and validate results
let results;
try {
results = JSON.parse(resultsJson || '{}');
} catch (e) {
console.error('Failed to parse check results:', e);
// Post error comment
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: parseInt(prNumber),
body: `⚠️ PR checks failed to complete properly. Error parsing results: ${e.message}`
});
return;
}
// Format check messages
const messages = [];
if (results.fails && results.fails.length > 0) {
messages.push('### ❌ Failures\n' + results.fails.map(f => f.message).join('\n\n'));
}
if (results.warnings && results.warnings.length > 0) {
messages.push('### ⚠️ Warnings\n' + results.warnings.map(w => w.message).join('\n\n'));
}
if (results.messages && results.messages.length > 0) {
messages.push('### 💬 Messages\n' + results.messages.map(m => m.message).join('\n\n'));
}
if (results.markdowns && results.markdowns.length > 0) {
messages.push(...results.markdowns.map(m => m.message));
}
// Find existing bot comment
const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: parseInt(prNumber)
});
const botComment = comments.data.find(comment =>
comment.user.type === 'Bot' &&
comment.body.includes('<!-- pr-checks-comment -->')
);
// Post comment if there are any messages
if (messages.length > 0) {
const body = messages.join('\n\n');
const commentBody = `<!-- pr-checks-comment -->\n${body}`;
if (botComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: commentBody
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: parseInt(prNumber),
body: commentBody
});
}
} else {
// No messages - delete existing comment if present
if (botComment) {
await github.rest.issues.deleteComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id
});
}
}
// Set commit status based on failures
if (prSha) {
const hasFailures = results.fails && results.fails.length > 0;
const hasWarnings = results.warnings && results.warnings.length > 0;
await github.rest.repos.createCommitStatus({
owner: context.repo.owner,
repo: context.repo.repo,
sha: prSha,
state: hasFailures ? 'failure' : 'success',
context: 'pr-checks',
description: hasFailures
? `${results.fails.length} check(s) failed`
: hasWarnings
? `${results.warnings.length} warning(s)`
: 'All checks passed'
});
}

View File

@@ -2,125 +2,42 @@ name: Create Release Draft
on:
pull_request:
types: [ closed ]
branches: [ main, core/* ]
types: [closed]
branches:
- main
- master
paths:
- 'package.json'
- "package.json"
jobs:
build:
draft_release:
runs-on: ubuntu-latest
if: >
github.event.pull_request.merged == true &&
contains(github.event.pull_request.labels.*.name, 'Release')
outputs:
version: ${{ steps.current_version.outputs.version }}
is_prerelease: ${{ steps.check_prerelease.outputs.is_prerelease }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/setup-node@v3
with:
node-version: 'lts/*'
node-version: lts/*
- name: Get current version
id: current_version
run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
- name: Check if prerelease
id: check_prerelease
run: |
VERSION=${{ steps.current_version.outputs.version }}
if [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+- ]]; then
echo "is_prerelease=true" >> $GITHUB_OUTPUT
else
echo "is_prerelease=false" >> $GITHUB_OUTPUT
fi
run: echo ::set-output name=version::$(node -p "require('./package.json').version")
- name: Build project
env:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }}
ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }}
USE_PROD_CONFIG: 'true'
run: |
npm ci
npm run build
npm run zipdist
- name: Upload dist artifact
uses: actions/upload-artifact@v4
with:
name: dist-files
path: |
dist/
dist.zip
draft_release:
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download dist artifact
uses: actions/download-artifact@v4
with:
name: dist-files
- name: Create release
id: create_release
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631
uses: softprops/action-gh-release@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
files: |
dist.zip
tag_name: v${{ needs.build.outputs.version }}
target_commitish: ${{ github.event.pull_request.base.ref }}
make_latest: ${{ github.event.pull_request.base.ref == 'main' && needs.build.outputs.is_prerelease == 'false' }}
draft: ${{ github.event.pull_request.base.ref != 'main' || needs.build.outputs.is_prerelease == 'true' }}
prerelease: ${{ needs.build.outputs.is_prerelease == 'true' }}
generate_release_notes: true
publish_pypi:
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download dist artifact
uses: actions/download-artifact@v4
with:
name: dist-files
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Install build dependencies
run: python -m pip install build
- name: Setup pypi package
run: |
mkdir -p comfyui_frontend_package/comfyui_frontend_package/static/
cp -r dist/* comfyui_frontend_package/comfyui_frontend_package/static/
- name: Build pypi package
run: python -m build
working-directory: comfyui_frontend_package
env:
COMFYUI_FRONTEND_VERSION: ${{ needs.build.outputs.version }}
- name: Publish pypi package
uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc
with:
password: ${{ secrets.PYPI_TOKEN }}
packages-dir: comfyui_frontend_package/dist
publish_types:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 'lts/*'
registry-url: https://registry.npmjs.org
- run: npm ci
- run: npm run build:types
- name: Publish package
run: npm publish --access public
working-directory: dist
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
tag_name: v${{ steps.current_version.outputs.version }}
draft: true
prerelease: false
make_latest: "true"

View File

@@ -1,4 +1,5 @@
# Setting test expectation screenshots for Playwright
name: Update Playwright Expectations
on:
@@ -10,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
if: github.event.label.name == 'New Browser Test Expectations'
steps:
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v2.3
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v2.1
- name: Install Playwright Browsers
run: npx playwright install chromium --with-deps
working-directory: ComfyUI_frontend

View File

@@ -2,107 +2,74 @@ name: Tests CI
on:
push:
branches: [main, master, core/*, desktop/*]
branches:
- main
- master
pull_request:
branches: [main, master, dev*, core/*, desktop/*]
branches:
- main
- master
- 'dev*'
jobs:
setup:
jest-tests:
runs-on: ubuntu-latest
outputs:
cache-key: ${{ steps.cache-key.outputs.key }}
steps:
- name: Checkout ComfyUI
uses: actions/checkout@v4
with:
repository: 'comfyanonymous/ComfyUI'
path: 'ComfyUI'
ref: master
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v2.1
- name: Run Jest tests
run: |
npm run test:generate
npm run test:generate:examples
npm test -- --verbose
working-directory: ComfyUI_frontend
- name: Checkout ComfyUI_frontend
uses: actions/checkout@v4
with:
repository: 'Comfy-Org/ComfyUI_frontend'
path: 'ComfyUI_frontend'
- name: Checkout ComfyUI_devtools
uses: actions/checkout@v4
with:
repository: 'Comfy-Org/ComfyUI_devtools'
path: 'ComfyUI/custom_nodes/ComfyUI_devtools'
ref: 'd05fd48dd787a4192e16802d4244cfcc0e2f9684'
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Build ComfyUI_frontend
run: |
npm ci
npm run build
working-directory: ComfyUI_frontend
- name: Generate cache key
id: cache-key
run: echo "key=$(date +%s)" >> $GITHUB_OUTPUT
- name: Save cache
uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684
with:
path: |
ComfyUI
ComfyUI_frontend
key: comfyui-setup-${{ steps.cache-key.outputs.key }}
playwright-tests:
needs: setup
playwright-tests-chromium:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
browser: [chromium, chromium-2x, mobile-chrome]
steps:
- name: Wait for cache propagation
run: sleep 10
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v2.1
- name: Install Playwright Browsers
run: npx playwright install chromium --with-deps
working-directory: ComfyUI_frontend
- name: Run Playwright tests (Chromium)
run: npx playwright test --project=chromium
working-directory: ComfyUI_frontend
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report-chromium
path: ComfyUI_frontend/playwright-report/
retention-days: 30
- name: Restore cached setup
uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684
with:
fail-on-cache-miss: true
path: |
ComfyUI
ComfyUI_frontend
key: comfyui-setup-${{ needs.setup.outputs.cache-key }}
playwright-tests-chromium-2x:
runs-on: ubuntu-latest
steps:
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v2.1
- name: Install Playwright Browsers
run: npx playwright install chromium --with-deps
working-directory: ComfyUI_frontend
- name: Run Playwright tests (Chromium 2x)
run: npx playwright test --project=chromium-2x
working-directory: ComfyUI_frontend
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report-chromium-2x
path: ComfyUI_frontend/playwright-report/
retention-days: 30
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install requirements
run: |
python -m pip install --upgrade pip
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
pip install -r requirements.txt
pip install wait-for-it
working-directory: ComfyUI
- name: Start ComfyUI server
run: |
python main.py --cpu --multi-user --front-end-root ../ComfyUI_frontend/dist &
wait-for-it --service 127.0.0.1:8188 -t 600
working-directory: ComfyUI
- name: Install Playwright Browsers
run: npx playwright install chromium --with-deps
working-directory: ComfyUI_frontend
- name: Run Playwright tests (${{ matrix.browser }})
run: npx playwright test --project=${{ matrix.browser }}
working-directory: ComfyUI_frontend
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report-${{ matrix.browser }}
path: ComfyUI_frontend/playwright-report/
retention-days: 30
playwright-tests-mobile-chrome:
runs-on: ubuntu-latest
steps:
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v2.1
- name: Install Playwright Browsers
run: npx playwright install chromium --with-deps
working-directory: ComfyUI_frontend
- name: Run Playwright tests (Mobile Chrome)
run: npx playwright test --project=mobile-chrome
working-directory: ComfyUI_frontend
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report-mobile-chrome
path: ComfyUI_frontend/playwright-report/
retention-days: 30

View File

@@ -1,44 +0,0 @@
name: Update Electron Types
on:
workflow_dispatch:
jobs:
update-electron-types:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: lts/*
cache: 'npm'
- name: Update electron types
run: npm install @comfyorg/comfyui-electron-types@latest
- name: Get new version
id: get-version
run: |
NEW_VERSION=$(node -e "console.log(JSON.parse(require('fs').readFileSync('./package-lock.json')).packages['node_modules/@comfyorg/comfyui-electron-types'].version)")
echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_OUTPUT
- name: Create Pull Request
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e
with:
token: ${{ secrets.PR_GH_TOKEN }}
commit-message: '[chore] Update electron-types to ${{ steps.get-version.outputs.NEW_VERSION }}'
title: '[chore] Update electron-types to ${{ steps.get-version.outputs.NEW_VERSION }}'
body: |
Automated update of desktop API types to version ${{ steps.get-version.outputs.NEW_VERSION }}.
branch: update-electron-types-${{ steps.get-version.outputs.NEW_VERSION }}
base: main
labels: |
dependencies
Electron

View File

@@ -1,43 +0,0 @@
name: Update Litegraph Dependency
on:
workflow_dispatch:
jobs:
update-litegraph:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Update litegraph
run: npm install @comfyorg/litegraph@latest
- name: Get new version
id: get-version
run: |
NEW_VERSION=$(node -e "console.log(JSON.parse(require('fs').readFileSync('./package-lock.json')).packages['node_modules/@comfyorg/litegraph'].version)")
echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_OUTPUT
- name: Create Pull Request
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e
with:
token: ${{ secrets.PR_GH_TOKEN }}
commit-message: '[chore] Update litegraph to ${{ steps.get-version.outputs.NEW_VERSION }}'
title: '[chore] Update litegraph to ${{ steps.get-version.outputs.NEW_VERSION }}'
body: |
Automated update of litegraph to version ${{ steps.get-version.outputs.NEW_VERSION }}.
Ref: https://github.com/Comfy-Org/litegraph.js/releases/tag/v${{ steps.get-version.outputs.NEW_VERSION }}
branch: update-litegraph-${{ steps.get-version.outputs.NEW_VERSION }}
base: main
labels: |
dependencies

View File

@@ -1,92 +0,0 @@
name: Update ComfyUI-Manager API Types
on:
# Manual trigger
workflow_dispatch:
jobs:
update-manager-types:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: lts/*
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Checkout ComfyUI-Manager repository
uses: actions/checkout@v4
with:
repository: Comfy-Org/ComfyUI-Manager
path: ComfyUI-Manager
clean: true
- name: Get Manager commit information
id: manager-info
run: |
cd ComfyUI-Manager
MANAGER_COMMIT=$(git rev-parse --short HEAD)
echo "commit=${MANAGER_COMMIT}" >> $GITHUB_OUTPUT
cd ..
- name: Generate Manager API types
run: |
echo "Generating TypeScript types from ComfyUI-Manager@${{ steps.manager-info.outputs.commit }}..."
npx openapi-typescript ./ComfyUI-Manager/openapi.yaml --output ./src/types/generatedManagerTypes.ts
- name: Validate generated types
run: |
if [ ! -f ./src/types/generatedManagerTypes.ts ]; then
echo "Error: Types file was not generated."
exit 1
fi
# Check if file is not empty
if [ ! -s ./src/types/generatedManagerTypes.ts ]; then
echo "Error: Generated types file is empty."
exit 1
fi
- name: Check for changes
id: check-changes
run: |
if [[ -z $(git status --porcelain ./src/types/generatedManagerTypes.ts) ]]; then
echo "No changes to ComfyUI-Manager API types detected."
echo "changed=false" >> $GITHUB_OUTPUT
exit 0
else
echo "Changes detected in ComfyUI-Manager API types."
echo "changed=true" >> $GITHUB_OUTPUT
fi
- name: Create Pull Request
if: steps.check-changes.outputs.changed == 'true'
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.PR_GH_TOKEN }}
commit-message: '[chore] Update ComfyUI-Manager API types from ComfyUI-Manager@${{ steps.manager-info.outputs.commit }}'
title: '[chore] Update ComfyUI-Manager API types from ComfyUI-Manager@${{ steps.manager-info.outputs.commit }}'
body: |
## Automated API Type Update
This PR updates the ComfyUI-Manager API types from the latest ComfyUI-Manager OpenAPI specification.
- Manager commit: ${{ steps.manager-info.outputs.commit }}
- Generated on: ${{ github.event.repository.updated_at }}
These types are automatically generated using openapi-typescript.
branch: update-manager-types-${{ steps.manager-info.outputs.commit }}
base: main
labels: Manager
delete-branch: true
add-paths: |
src/types/generatedManagerTypes.ts

View File

@@ -1,97 +0,0 @@
name: Update Comfy Registry API Types
on:
# Manual trigger
workflow_dispatch:
# Triggered from comfy-api repo
repository_dispatch:
types: [comfy-api-updated]
jobs:
update-registry-types:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: lts/*
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Checkout comfy-api repository
uses: actions/checkout@v4
with:
repository: Comfy-Org/comfy-api
path: comfy-api
token: ${{ secrets.COMFY_API_PAT }}
clean: true
- name: Get API commit information
id: api-info
run: |
cd comfy-api
API_COMMIT=$(git rev-parse --short HEAD)
echo "commit=${API_COMMIT}" >> $GITHUB_OUTPUT
cd ..
- name: Generate API types
run: |
echo "Generating TypeScript types from comfy-api@${{ steps.api-info.outputs.commit }}..."
npx openapi-typescript ./comfy-api/openapi.yml --output ./src/types/comfyRegistryTypes.ts
- name: Validate generated types
run: |
if [ ! -f ./src/types/comfyRegistryTypes.ts ]; then
echo "Error: Types file was not generated."
exit 1
fi
# Check if file is not empty
if [ ! -s ./src/types/comfyRegistryTypes.ts ]; then
echo "Error: Generated types file is empty."
exit 1
fi
- name: Check for changes
id: check-changes
run: |
if [[ -z $(git status --porcelain ./src/types/comfyRegistryTypes.ts) ]]; then
echo "No changes to Comfy Registry API types detected."
echo "changed=false" >> $GITHUB_OUTPUT
exit 0
else
echo "Changes detected in Comfy Registry API types."
echo "changed=true" >> $GITHUB_OUTPUT
fi
- name: Create Pull Request
if: steps.check-changes.outputs.changed == 'true'
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e
with:
token: ${{ secrets.PR_GH_TOKEN }}
commit-message: '[chore] Update Comfy Registry API types from comfy-api@${{ steps.api-info.outputs.commit }}'
title: '[chore] Update Comfy Registry API types from comfy-api@${{ steps.api-info.outputs.commit }}'
body: |
## Automated API Type Update
This PR updates the Comfy Registry API types from the latest comfy-api OpenAPI specification.
- API commit: ${{ steps.api-info.outputs.commit }}
- Generated on: ${{ github.event.repository.updated_at }}
These types are automatically generated using openapi-typescript.
branch: update-registry-types-${{ steps.api-info.outputs.commit }}
base: main
labels: CNR
delete-branch: true
add-paths: |
src/types/comfyRegistryTypes.ts

View File

@@ -1,59 +0,0 @@
name: Version Bump
on:
workflow_dispatch:
inputs:
version_type:
description: 'Version increment type'
required: true
default: 'patch'
type: 'choice'
options: [patch, minor, major, prepatch, preminor, premajor, prerelease]
pre_release:
description: Pre-release ID (suffix)
required: false
default: ''
type: string
jobs:
bump-version:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: lts/*
cache: 'npm'
- name: Bump version
id: bump-version
run: |
npm version ${{ github.event.inputs.version_type }} --preid ${{ github.event.inputs.pre_release }} --no-git-tag-version
NEW_VERSION=$(node -p "require('./package.json').version")
echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_OUTPUT
- name: Format PR string
id: capitalised
run: |
CAPITALISED_TYPE=${{ github.event.inputs.version_type }}
echo "capitalised=${CAPITALISED_TYPE@u}" >> $GITHUB_OUTPUT
- name: Create Pull Request
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e
with:
token: ${{ secrets.PR_GH_TOKEN }}
commit-message: '[release] Increment version to ${{ steps.bump-version.outputs.NEW_VERSION }}'
title: ${{ steps.bump-version.outputs.NEW_VERSION }}
body: |
${{ steps.capitalised.outputs.capitalised }} version increment to ${{ steps.bump-version.outputs.NEW_VERSION }}
branch: version-bump-${{ steps.bump-version.outputs.NEW_VERSION }}
base: main
labels: |
Release

View File

@@ -2,9 +2,15 @@ name: Vitest Tests
on:
push:
branches: [ main, master, dev*, core/*, desktop/* ]
branches:
- main
- master
- 'dev*'
pull_request:
branches: [ main, master, dev*, core/*, desktop/* ]
branches:
- main
- master
- 'dev*'
jobs:
test:
@@ -14,14 +20,12 @@ jobs:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v3
with:
node-version: 'lts/*'
node-version: lts/*
- name: Install dependencies
run: npm ci
- name: Run Vitest tests
run: |
npm run test:component
npm run test:unit
run: npm run test:component

22
.gitignore vendored
View File

@@ -16,9 +16,6 @@ dist-ssr
.vscode/*
*.code-workspace
!.vscode/extensions.json
!.vscode/tailwind.json
!.vscode/settings.json.default
!.vscode/launch.json.default
.idea
.DS_Store
*.suo
@@ -38,23 +35,8 @@ tests-ui/workflows/examples
/playwright-report/
/blob-report/
/playwright/.cache/
browser_tests/**/*-win32.png
browser_tests/*/*-win32.png
.env
dist.zip
/temp/
# Generated JSON Schemas
/schemas/
# Workflow templates assets
# Hosted on https://github.com/Comfy-Org/workflow_templates
/public/templates/
# Temporary repository directory
templates_repo/
# Vites timestamped config modules
vite.config.mts.timestamp-*.mjs
dist.zip

View File

@@ -1,9 +1,5 @@
if [[ "$OS" == "Windows_NT" ]]; then
npx.cmd lint-staged
# Check for unused i18n keys in staged files
npx.cmd tsx scripts/check-unused-i18n-keys.ts
else
npx lint-staged
# Check for unused i18n keys in staged files
npx tsx scripts/check-unused-i18n-keys.ts
fi

View File

@@ -1,18 +0,0 @@
// This file is intentionally kept in CommonJS format (.cjs)
// to resolve compatibility issues with dependencies that require CommonJS.
// Do not convert this file to ESModule format unless all dependencies support it.
const { defineConfig } = require('@lobehub/i18n-cli');
module.exports = defineConfig({
modelName: 'gpt-4.1',
splitToken: 1024,
entry: 'src/locales/en',
entryLocale: 'en',
output: 'src/locales',
outputLocales: ['zh', 'zh-TW', 'ru', 'ja', 'ko', 'fr', 'es'],
reference: `Special names to keep untranslated: flux, photomaker, clip, vae, cfg, stable audio, stable cascade, stable zero, controlnet, lora, HiDream.
'latent' is the short form of 'latent space'.
'mask' is in the context of image processing.
Note: For Traditional Chinese (Taiwan), use Taiwan-specific terminology and traditional characters.
`
});

View File

@@ -1,8 +0,0 @@
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["-y", "@executeautomation/playwright-mcp-server"]
}
}
}

View File

@@ -3,16 +3,5 @@
"tabWidth": 2,
"semi": false,
"trailingComma": "none",
"printWidth": 80,
"importOrder": ["^@core/(.*)$", "<THIRD_PARTY_MODULES>", "^@/(.*)$", "^[./]"],
"importOrderSeparation": true,
"importOrderSortSpecifiers": true,
"overrides": [
{
"files": "*.{js,cjs,mjs,ts,cts,mts,tsx,vue}",
"options": {
"plugins": ["@trivago/prettier-plugin-sort-imports"]
}
}
]
}
"printWidth": 80
}

View File

@@ -1,25 +0,0 @@
{
"recommendations": [
"austenc.tailwind-docs",
"bradlc.vscode-tailwindcss",
"davidanson.vscode-markdownlint",
"dbaeumer.vscode-eslint",
"eamodio.gitlens",
"esbenp.prettier-vscode",
"figma.figma-vscode-extension",
"github.vscode-github-actions",
"github.vscode-pull-request-github",
"hbenl.vscode-test-explorer",
"lokalise.i18n-ally",
"ms-playwright.playwright",
"vitest.explorer",
"vue.volar",
"sonarsource.sonarlint-vscode",
"deque-systems.vscode-axe-linter",
"kisstkondoros.vscode-codemetrics",
"donjayamanne.githistory",
"wix.vscode-import-cost",
"prograhammer.tslint-vue",
"antfu.vite"
]
}

View File

@@ -1,16 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome on frontend dev",
"url": "http://localhost:5173",
"webRoot": "${workspaceFolder}/src",
"sourceMaps": true,
}
]
}

View File

@@ -1,5 +0,0 @@
{
"css.customData": [
".vscode/tailwind.json"
]
}

55
.vscode/tailwind.json vendored
View File

@@ -1,55 +0,0 @@
{
"version": 1.1,
"atDirectives": [
{
"name": "@tailwind",
"description": "Use the `@tailwind` directive to insert Tailwind's `base`, `components`, `utilities` and `screens` styles into your CSS.",
"references": [
{
"name": "Tailwind Documentation",
"url": "https://tailwindcss.com/docs/functions-and-directives#tailwind"
}
]
},
{
"name": "@apply",
"description": "Use the `@apply` directive to inline any existing utility classes into your own custom CSS. This is useful when you find a common utility pattern in your HTML that youd like to extract to a new component.",
"references": [
{
"name": "Tailwind Documentation",
"url": "https://tailwindcss.com/docs/functions-and-directives#apply"
}
]
},
{
"name": "@responsive",
"description": "You can generate responsive variants of your own classes by wrapping their definitions in the `@responsive` directive:\n```css\n@responsive {\n .alert {\n background-color: #E53E3E;\n }\n}\n```\n",
"references": [
{
"name": "Tailwind Documentation",
"url": "https://tailwindcss.com/docs/functions-and-directives#responsive"
}
]
},
{
"name": "@screen",
"description": "The `@screen` directive allows you to create media queries that reference your breakpoints by **name** instead of duplicating their values in your own CSS:\n```css\n@screen sm {\n /* ... */\n}\n```\n…gets transformed into this:\n```css\n@media (min-width: 640px) {\n /* ... */\n}\n```\n",
"references": [
{
"name": "Tailwind Documentation",
"url": "https://tailwindcss.com/docs/functions-and-directives#screen"
}
]
},
{
"name": "@variants",
"description": "Generate `hover`, `focus`, `active` and other **variants** of your own utilities by wrapping their definitions in the `@variants` directive:\n```css\n@variants hover, focus {\n .btn-brand {\n background-color: #3182CE;\n }\n}\n```\n",
"references": [
{
"name": "Tailwind Documentation",
"url": "https://tailwindcss.com/docs/functions-and-directives#variants"
}
]
}
]
}

View File

@@ -1,58 +0,0 @@
- use `npm run` to see what commands are available
- For component communication, prefer Vue's event-based pattern (emit/@event-name) for state changes and notifications; use defineExpose with refs only for imperative operations that need direct control (like form.validate(), modal.open(), or editor.focus()); events promote loose coupling and are better for reusable components, while exposed methods are acceptable for tightly-coupled component pairs or when wrapping third-party libraries that require imperative APIs
- After making code changes, follow this general process: (1) Create unit tests, component tests, browser tests (if appropriate for each), (2) run unit tests, component tests, and browser tests until passing, (3) run typecheck, lint, format (with prettier) -- you can use `npm run` command to see the scripts available, (4) check if any READMEs (including nested) or documentation needs to be updated, (5) Decide whether the changes are worth adding new content to the external documentation for (or would requires changes to the external documentation) at https://docs.comfy.org, then present your suggestion
- When referencing PrimeVue, you can get all the docs here: https://primevue.org. Do this instead of making up or inferring names of Components
- When trying to set tailwind classes for dark theme, use "dark-theme:" prefix rather than "dark:"
- Never add lines to PR descriptions or commit messages that say "Generated with Claude Code"
- When making PR names and commit messages, if you are going to add a prefix like "docs:", "feat:", "bugfix:", use square brackets around the prefix term and do not use a colon (e.g., should be "[docs]" rather than "docs:").
- When I reference GitHub Repos related to Comfy-Org, you should proactively fetch or read the associated information in the repo. To do so, you should exhaust all options: (1) Check if we have a local copy of the repo, (2) Use the GitHub API to fetch the information; you may want to do this IN ADDITION to the other options, especially for reading specific branches/PRs/comments/reviews/metadata, and (3) curl the GitHub website and parse the html or json responses
- For information about ComfyUI, ComfyUI_frontend, or ComfyUI-Manager, you can web search or download these wikis: https://deepwiki.com/Comfy-Org/ComfyUI-Manager, https://deepwiki.com/Comfy-Org/ComfyUI_frontend/1-overview, https://deepwiki.com/comfyanonymous/ComfyUI/2-core-architecture
- If a question/project is related to Comfy-Org, Comfy, or ComfyUI ecosystem, you should proactively use the Comfy docs to answer the question. The docs may be referenced with URLs like https://docs.comfy.org
- When operating inside a repo, check for README files at key locations in the repo detailing info about the contents of that folder. E.g., top-level key folders like tests-ui, browser_tests, composables, extensions/core, stores, services often have their own README.md files. When writing code, make sure to frequently reference these README files to understand the overall architecture and design of the project. Pay close attention to the snippets to learn particular patterns that seem to be there for a reason, as you should emulate those.
- Prefer running single tests, and not the whole test suite, for performance
- If using a lesser known or complex CLI tool, run the --help to see the documentation before deciding what to run, even if just for double-checking or verifying things.
- IMPORTANT: the most important goal when writing code is to create clean, best-practices, sustainable, and scalable public APIs and interfaces. Our app is used by thousands of users and we have thousands of mods/extensions that are constantly changing and updating; and we are also always updating. That's why it is IMPORTANT that we design systems and write code that follows practices of domain-driven design, object-oriented design, and design patterns (such that you can assure stability while allowing for all components around you to change and evolve). We ABSOLUTELY prioritize clean APIs and public interfaces that clearly define and restrict how/what the mods/extensions can access.
- If any of these technologies are referenced, you can proactively read their docs at these locations: https://primevue.org/theming, https://primevue.org/forms/, https://www.electronjs.org/docs/latest/api/browser-window, https://vitest.dev/guide/browser/, https://atlassian.design/components/pragmatic-drag-and-drop/core-package/drop-targets/, https://playwright.dev/docs/api/class-test, https://playwright.dev/docs/api/class-electron, https://www.algolia.com/doc/api-reference/rest-api/, https://pyav.org/docs/develop/cookbook/basics.html
- IMPORTANT: Never add Co-Authored by Claude or any reference to Claude or Claude Code in commit messages, PR descriptions, titles, or any documentation whatsoever
- The npm script to type check is called "typecheck" NOT "type check"
- Use the Vue 3 Composition API instead of the Options API when writing Vue components. An exception is when overriding or extending a PrimeVue component for compatibility, you may use the Options API.
- when we are solving an issue we know the link/number for, we should add "Fixes #n" (where n is the issue number) to the PR description.
- Never write css if you can accomplish the same thing with tailwind utility classes
- Utilize ref and reactive for reactive state
- Implement computed properties with computed()
- Use watch and watchEffect for side effects
- Implement lifecycle hooks with onMounted, onUpdated, etc.
- Utilize provide/inject for dependency injection
- Use vue 3.5 style of default prop declaration. Do not define a `props` variable; instead, destructure props. Since vue 3.5, destructuring props does not strip them of reactivity.
- Use Tailwind CSS for styling
- Leverage VueUse functions for performance-enhancing styles
- Use lodash for utility functions
- Implement proper props and emits definitions
- Utilize Vue 3's Teleport component when needed
- Use Suspense for async components
- Implement proper error handling
- Follow Vue 3 style guide and naming conventions
- IMPORTANT: Use vue-i18n for ALL user-facing strings - no hard-coded text in services/utilities. Place new translation entries in src/locales/en/main.json
- Avoid using `@ts-expect-error` to work around type issues. We needed to employ it to migrate to TypeScript, but it should not be viewed as an accepted practice or standard.
- DO NOT use deprecated PrimeVue components. Use these replacements instead:
* `Dropdown` → Use `Select` (import from 'primevue/select')
* `OverlayPanel` → Use `Popover` (import from 'primevue/popover')
* `Calendar` → Use `DatePicker` (import from 'primevue/datepicker')
* `InputSwitch` → Use `ToggleSwitch` (import from 'primevue/toggleswitch')
* `Sidebar` → Use `Drawer` (import from 'primevue/drawer')
* `Chips` → Use `AutoComplete` with multiple enabled and typeahead disabled
* `TabMenu` → Use `Tabs` without panels
* `Steps` → Use `Stepper` without panels
* `InlineMessage` → Use `Message` component
* Use `api.apiURL()` for all backend API calls and routes
- Actual API endpoints like /prompt, /queue, /view, etc.
- Image previews: `api.apiURL('/view?...')`
- Any backend-generated content or dynamic routes
* Use `api.fileURL()` for static files served from the public folder:
- Templates: `api.fileURL('/templates/default.json')`
- Extensions: `api.fileURL(extensionPath)` for loading JS modules
- Any static assets that exist in the public directory
- When implementing code that outputs raw HTML (e.g., using v-html directive), always ensure dynamic content has been properly sanitized with DOMPurify or validated through trusted sources. Prefer Vue templates over v-html when possible.
- For any async operations (API calls, timers, etc), implement cleanup/cancellation in component unmount to prevent memory leaks
- Extract complex template conditionals into separate components or computed properties
- Error messages should be actionable and user-friendly (e.g., "Failed to load data. Please refresh the page." instead of "Unknown error")

View File

@@ -1,17 +0,0 @@
# Admins
* @Comfy-Org/comfy_frontend_devs
# Maintainers
*.md @Comfy-Org/comfy_maintainer
/tests-ui/ @Comfy-Org/comfy_maintainer
/browser_tests/ @Comfy-Org/comfy_maintainer
/.env_example @Comfy-Org/comfy_maintainer
# Translations (AIGODLIKE team + shinshin86)
/src/locales/ @Yorha4D @KarryCharon @DorotaLuna @shinshin86 @Comfy-Org/comfy_maintainer
# Load 3D extension
/src/extensions/core/load3d.ts @jtydhr88 @Comfy-Org/comfy_frontend_devs
# Mask Editor extension
/src/extensions/core/maskeditor.ts @trsommer @Comfy-Org/comfy_frontend_devs

335
README.md
View File

@@ -31,22 +31,8 @@
## Release Schedule
The project follows a structured release process for each minor version, consisting of three distinct phases:
### Nightly Release
1. **Development Phase** - 1 week
- Active development of new features
- Code changes merged to the development branch
2. **Feature Freeze** - 1 week
- No new features accepted
- Only bug fixes are cherry-picked to the release branch
- Testing and stabilization of the codebase
3. **Publication**
- Release is published at the end of the freeze period
- Version is finalized and made available to all users
### Nightly Releases
Nightly releases are published daily at [https://github.com/Comfy-Org/ComfyUI_frontend/releases](https://github.com/Comfy-Org/ComfyUI_frontend/releases).
To use the latest nightly release, add the following command line argument to your ComfyUI launch script:
@@ -55,41 +41,28 @@ To use the latest nightly release, add the following command line argument to yo
--front-end-version Comfy-Org/ComfyUI_frontend@latest
```
## Overlapping Release Cycles
The development of successive minor versions overlaps. For example, while version 1.1 is in feature freeze, development for version 1.2 begins simultaneously.
#### For Windows Stand-alone Build Users
### Example Release Cycle
Edit your `run_cpu.bat` or `run_nvidia_gpu.bat` file as follows:
| Week | Date Range | Version 1.1 | Version 1.2 | Version 1.3 | Patch Releases |
|------|------------|-------------|-------------|-------------|----------------|
| 1 | Mar 1-7 | Development | - | - | - |
| 2 | Mar 8-14 | Feature Freeze | Development | - | 1.1.0 through 1.1.6 (daily) |
| 3 | Mar 15-21 | Released | Feature Freeze | Development | 1.1.7 through 1.1.13 (daily)<br>1.2.0 through 1.2.6 (daily) |
| 4 | Mar 22-28 | - | Released | Feature Freeze | 1.2.7 through 1.2.13 (daily)<br>1.3.0 through 1.3.6 (daily) |
```bat
.\python_embeded\python.exe -s ComfyUI\main.py --windows-standalone-build --front-end-version Comfy-Org/ComfyUI_frontend@latest
pause
```
### Stable Release
Stable releases are published weekly in the ComfyUI main repository, aligned with ComfyUI backend's stable release schedule.
#### Feature Freeze
There will be a 2-day feature freeze before each stable release. During this period, no new major features will be merged.
## Release Summary
### Major features
<details id='feature-native-translation'>
<summary>v1.5: Native translation (i18n)</summary>
ComfyUI now includes built-in translation support, replacing the need for third-party translation extensions. Select your language
in `Comfy > Locale > Language` to translate the interface into English, Chinese (Simplified), Russian, Japanese, or Korean. This native
implementation offers better performance, reliability, and maintainability compared to previous solutions.<br>
More details available here: https://blog.comfy.org/p/native-localization-support-i18n
</details>
<details id='feature-mask-editor'>
<summary>v1.4: New mask editor</summary>
https://github.com/Comfy-Org/ComfyUI_frontend/pull/1284 implements a new mask editor.
![image](https://github.com/user-attachments/assets/f0ea6ee5-00ee-4e5d-a09c-6938e86a1f17)
</details>
<details id='feature-integrated-server-terminal'>
<details>
<summary>v1.3.22: Integrated server terminal</summary>
Press Ctrl + ` to toggle integrated terminal.
@@ -97,7 +70,7 @@ Press Ctrl + ` to toggle integrated terminal.
https://github.com/user-attachments/assets/eddedc6a-07a3-4a83-9475-63b3977f6d94
</details>
<details id='feature-keybinding-customization'>
<details>
<summary>v1.3.7: Keybinding customization</summary>
## Basic UI
@@ -114,7 +87,7 @@ https://github.com/user-attachments/assets/eddedc6a-07a3-4a83-9475-63b3977f6d94
</details>
<details id='feature-node-library-sidebar'>
<details>
<summary>v1.2.4: Node library sidebar tab</summary>
#### Drag & Drop
@@ -124,13 +97,13 @@ https://github.com/user-attachments/assets/853e20b7-bc0e-49c9-bbce-a2ba7566f92f
https://github.com/user-attachments/assets/4bbca3ee-318f-4cf0-be32-a5a5541066cf
</details>
<details id='feature-queue-sidebar'>
<details>
<summary>v1.2.0: Queue/History sidebar tab</summary>
https://github.com/user-attachments/assets/86e264fe-4d26-4f07-aa9a-83bdd2d02b8f
</details>
<details id='feature-node-search'>
<details>
<summary>v1.1.0: Node search box</summary>
#### Fuzzy search & Node preview
@@ -142,26 +115,26 @@ https://github.com/user-attachments/assets/4bbca3ee-318f-4cf0-be32-a5a5541066cf
### QoL changes
<details id='feature-nested-group'>
<details>
<summary>v1.3.32: **Litegraph** Nested group</summary>
https://github.com/user-attachments/assets/f51adeb1-028e-40af-81e4-0ac13075198a
</details>
<details id='feature-group-selection'>
<details>
<summary>v1.3.24: **Litegraph** Group selection</summary>
https://github.com/user-attachments/assets/e6230a94-411e-4fba-90cb-6c694200adaa
</details>
<details id='feature-toggle-link-visibility'>
<details>
<summary>v1.3.6: **Litegraph** Toggle link visibility</summary>
[rec.webm](https://github.com/user-attachments/assets/34e460ac-fbbc-44ef-bfbb-99a84c2ae2be)
</details>
<details id='feature-auto-widget-conversion'>
<details>
<summary>v1.3.4: **Litegraph** Auto widget to input conversion</summary>
Dropping a link of correct type on node widget will automatically convert the widget to input.
@@ -170,7 +143,7 @@ Dropping a link of correct type on node widget will automatically convert the wi
</details>
<details id='feature-pan-mode'>
<details>
<summary>v1.3.4: **Litegraph** Canvas pan mode</summary>
The canvas becomes readonly in pan mode. Pan mode is activated by clicking the pan mode button on the canvas menu
@@ -180,42 +153,42 @@ or by holding the space key.
</details>
<details id='feature-shift-drag-link-creation'>
<details>
<summary>v1.3.1: **Litegraph** Shift drag link to create a new link</summary>
[rec.webm](https://github.com/user-attachments/assets/7e73aaf9-79e2-4c3c-a26a-911cba3b85e4)
</details>
<details id='feature-optional-input-donuts'>
<details>
<summary>v1.2.62: **Litegraph** Show optional input slots as donuts</summary>
![GYEIRidb0AYGO-v](https://github.com/user-attachments/assets/e6cde0b6-654b-4afd-a117-133657a410b1)
</details>
<details id='feature-group-title-edit'>
<details>
<summary>v1.2.44: **Litegraph** Double click group title to edit</summary>
https://github.com/user-attachments/assets/5bf0e2b6-8b3a-40a7-b44f-f0879e9ad26f
</details>
<details id='feature-group-selection-shortcut'>
<details>
<summary>v1.2.39: **Litegraph** Group selected nodes with Ctrl + G</summary>
https://github.com/user-attachments/assets/7805dc54-0854-4a28-8bcd-4b007fa01151
</details>
<details id='feature-node-title-edit'>
<details>
<summary>v1.2.38: **Litegraph** Double click node title to edit</summary>
https://github.com/user-attachments/assets/d61d5d0e-f200-4153-b293-3e3f6a212b30
</details>
<details id='feature-drag-multi-link'>
<details>
<summary>v1.2.7: **Litegraph** drags multiple links with shift pressed</summary>
https://github.com/user-attachments/assets/68826715-bb55-4b2a-be6e-675cfc424afe
@@ -224,7 +197,7 @@ https://github.com/user-attachments/assets/c142c43f-2fe9-4030-8196-b3bfd4c6977d
</details>
<details id='feature-auto-connect-link'>
<details>
<summary>v1.2.2: **Litegraph** auto connects to correct slot</summary>
#### Before
@@ -234,7 +207,7 @@ https://github.com/user-attachments/assets/c142c43f-2fe9-4030-8196-b3bfd4c6977d
https://github.com/user-attachments/assets/b6360ac0-f0d2-447c-9daa-8a2e20c0dc1d
</details>
<details id='feature-hide-text-overflow'>
<details>
<summary>v1.1.8: **Litegraph** hides text overflow on widget value</summary>
https://github.com/user-attachments/assets/5696a89d-4a47-4fcc-9e8c-71e1264943f2
@@ -242,55 +215,6 @@ https://github.com/user-attachments/assets/c142c43f-2fe9-4030-8196-b3bfd4c6977d
### Developer APIs
<details>
<summary>v1.6.13: prompt/confirm/alert replacements for ComfyUI desktop</summary>
Several browser-only APIs are not available in ComfyUI desktop's electron environment.
- `window.prompt`
- `window.confirm`
- `window.alert`
Please use the following APIs as replacements.
```js
// window.prompt
window['app'].extensionManager.dialog
.prompt({
title: 'Test Prompt',
message: 'Test Prompt Message'
})
.then((value: string) => {
// Do something with the value user entered
})
```
![image](https://github.com/user-attachments/assets/c73f74d0-9bb4-4555-8d56-83f1be4a1d7e)
```js
// window.confirm
window['app'].extensionManager.dialog
.confirm({
title: 'Test Confirm',
message: 'Test Confirm Message'
})
.then((value: boolean) => {
// Do something with the value user entered
})
```
![image](https://github.com/user-attachments/assets/8dec7a42-7443-4245-85be-ceefb1116e96)
```js
// window.alert
window['app'].extensionManager.toast
.addAlert("Test Alert")
```
![image](https://github.com/user-attachments/assets/9b18bdca-76ef-4432-95de-5cd2369684f2)
</details>
<details>
<summary>v1.3.34: Register about panel badges</summary>
@@ -311,7 +235,7 @@ app.registerExtension({
</details>
<details id='extension-api-bottom-panel-tabs'>
<details>
<summary>v1.3.22: Register bottom panel tabs</summary>
```js
@@ -334,7 +258,7 @@ app.registerExtension({
</details>
<details id='extension-api-settings'>
<details>
<summary>v1.3.22: New settings API</summary>
Legacy settings API.
@@ -380,7 +304,7 @@ app.extensionManager.setting.set('TestSetting', 'Hello, universe!')
</details>
<details id='extension-api-commands-keybindings'>
<details>
<summary>v1.3.7: Register commands and keybindings</summary>
Extensions can call the following API to register commands and keybindings. Do
@@ -409,7 +333,7 @@ app.extensionManager.setting.set('TestSetting', 'Hello, universe!')
</details>
<details id='extension-api-topbar-menu'>
<details>
<summary>v1.3.1: Extension API to register custom topbar menu items</summary>
Extensions can call the following API to register custom topbar menu items.
@@ -438,7 +362,7 @@ app.extensionManager.setting.set('TestSetting', 'Hello, universe!')
![image](https://github.com/user-attachments/assets/ae7b082f-7ce9-4549-a446-4563567102fe)
</details>
<details id='extension-api-toast'>
<details>
<summary>v1.2.27: Extension API to add toast message</summary>i
Extensions can call the following API to add toast messages.
@@ -456,7 +380,7 @@ Documentation of all supported options can be found here: <https://primevue.org/
![image](https://github.com/user-attachments/assets/de02cd7e-cd81-43d1-a0b0-bccef92ff487)
</details>
<details id='extension-api-sidebar-tab'>
<details>
<summary>v1.2.4: Extension API to register custom sidebar tab</summary>
Extensions now can call the following API to register a sidebar tab.
@@ -481,91 +405,15 @@ We will support custom icons later.
![image](https://github.com/user-attachments/assets/7bff028a-bf91-4cab-bf97-55c243b3f5e0)
</details>
<details id='extension-api-selection-toolbox'>
<summary>v1.10.9: Selection Toolbox API</summary>
Extensions can register commands that appear in the selection toolbox when specific items are selected on the canvas.
```js
app.registerExtension({
name: 'TestExtension1',
commands: [
{
id: 'test.selection.command',
label: 'Test Command',
icon: 'pi pi-star',
function: () => {
// Command logic here
}
}
],
// Return an array of command IDs to show in the selection toolbox
// when an item is selected
getSelectionToolboxCommands: (selectedItem) => ['test.selection.command']
})
```
The selection toolbox will display the command button when items are selected:
![Image](https://github.com/user-attachments/assets/28d91267-c0a9-4bd5-a7c4-36e8ec44c9bd)
</details>
## Contributing
We're building this frontend together and would love your help — no matter how you'd like to pitch in! You don't need to write code to make a difference.
Here are some ways to get involved:
- **Pull Requests:** Add features, fix bugs, or improve code health. Browse [issues](https://github.com/Comfy-Org/ComfyUI_frontend/issues) for inspiration.
- **Vote on Features:** Give a 👍 to the feature requests you care about to help us prioritize.
- **Verify Bugs:** Try reproducing reported issues and share your results (even if the bug doesn't occur!).
- **Community Support:** Hop into our [Discord](https://www.comfy.org/discord) to answer questions or get help.
- **Share & Advocate:** Tell your friends, tweet about us, or share tips to support the project.
Have another idea? Drop into Discord or open an issue, and let's chat!
## Development
### Prerequisites & Technology Stack
### Tech Stack
- **Required Software**:
- Node.js (v16 or later; v20/v22 strongly recommended) and npm
- Git for version control
- A running ComfyUI backend instance
- **Tech Stack**:
- [Vue 3](https://vuejs.org/) with [TypeScript](https://www.typescriptlang.org/)
- [Pinia](https://pinia.vuejs.org/) for state management
- [PrimeVue](https://primevue.org/) with [TailwindCSS](https://tailwindcss.com/) for UI
- [litegraph.js](https://github.com/Comfy-Org/litegraph.js) for node editor
- [zod](https://zod.dev/) for schema validation
- [vue-i18n](https://github.com/intlify/vue-i18n) for internationalization
### Initial Setup
1. Clone the repository:
```bash
git clone https://github.com/Comfy-Org/ComfyUI_frontend.git
cd ComfyUI_frontend
```
2. Install dependencies:
```bash
npm install
```
3. Configure environment (optional):
Create a `.env` file in the project root based on the provided [.env.example](.env.example) file.
**Note about ports**: By default, the dev server expects the ComfyUI backend at `localhost:8188`. If your ComfyUI instance runs on a different port, update this in your `.env` file.
### Dev Server Configuration
To launch ComfyUI and have it connect to your development server:
```bash
python main.py --port 8188
```
- [Vue 3](https://vuejs.org/) with [TypeScript](https://www.typescriptlang.org/)
- [Pinia](https://pinia.vuejs.org/) for state management
- [PrimeVue](https://primevue.org/) with [TailwindCSS](https://tailwindcss.com/) for UI
- [Litegraph](https://github.com/Comfy-Org/litegraph.js) for node editor
- [zod](https://zod.dev/) for schema validation
### Git pre-commit hooks
@@ -583,8 +431,6 @@ core extensions will be loaded.
#### Access dev server on touch devices
Enable remote access to the dev server by setting `VITE_REMOTE_DEV` in `.env` to `true`.
After you start the dev server, you should see following logs:
```
@@ -603,78 +449,13 @@ After you start the dev server, you should see following logs:
Make sure your desktop machine and touch device are on the same network. On your touch device,
navigate to `http://<server_ip>:5173` (e.g. `http://192.168.2.20:5173` here), to access the ComfyUI frontend.
### Recommended Code Editor Configuration
This project includes `.vscode/launch.json.default` and `.vscode/settings.json.default` files with recommended launch and workspace settings for editors that use the `.vscode` directory (e.g., VS Code, Cursor, etc.).
We've also included a list of recommended extensions in `.vscode/extensions.json`. Your editor should detect this file and show a human friendly list in the Extensions panel, linking each entry to its marketplace page.
### Recommended MCP Integrations
#### Playwright Browser Automation
The Playwright MCP server enables LLM coding assistants (like Copilot, Claude Code, etc.) to control and inspect web browsers programmatically. This allows assistants to navigate websites, take screenshots, and interact with web pages on your behalf.
For ComfyUI_frontend development, you can ask coding assistants to use Playwright screenshots to continuously verify that your code changes produce the expected visual results.
##### Setup for Claude Code
After installing dependencies with `npm i`, the Playwright MCP server will be automatically available when you start Claude Code locally.
Here's how Claude Code can use the Playwright MCP server to inspect the interface of the local development server (assuming you're running the dev server at `localhost:5173`):
```
> navigate to localhost:5173 and take a screenshot.
tell me the items in the menubar
● I'll navigate to localhost:5173 and take a
screenshot to see the menubar items.
● playwright:playwright_navigate (MCP)(url: …
"http://local
host:5173")
⎿ Navigated to http://localhost:5173
● playwright:playwright_screenshot (MCP)(name: "lo…
alhost_5173
_menubar")
⎿ Screenshot saved to: ../../../Downloads/localho
st_5173_menubar-2025-05-30T17-23-00-942Z.png
⎿ Screenshot also stored in memory with name:
'localhost_5173_menubar'
● Read(../../../Downloads/localhost_5173_menubar-2…
25-05-30T17-23-00-942Z.png)
⎿ Read image (94.3KB)
● Based on the screenshot, I can see the menubar at
the top of the ComfyUI interface contains the
following items:
Main Menubar Items:
- Workflow - Menu for workflow-related actions
- Edit - Menu for editing operations
- Help - Menu for help and documentation
Additional UI Elements:
- ComfyUI Logo - On the far left
- Workflow Tab - Shows "Unsaved Workflow" with a
dropdown and close button
- Layout Controls - On the far right (grid view
and hamburger menu icons)
The interface shows a typical ComfyUI workflow
graph with nodes like "Load Checkpoint", "CLIP
Text Encode (Prompt)", "KSampler", and "Empty
Latent Image" connected with colored cables.
```
### Unit Test
- `git clone https://github.com/comfyanonymous/ComfyUI_examples.git` to `tests-ui/ComfyUI_examples` or the EXAMPLE_REPO_PATH location specified in .env
- `npm i` to install all dependencies
- `npm run test:unit` to execute all unit tests.
- `npm run test:generate` to fetch `tests-ui/data/object_info.json`
- `npm run test:generate:examples` to extract the example workflows
- `npm run test` to execute all unit tests.
### Component Test
@@ -686,25 +467,25 @@ Component test verifies Vue components in `src/components/`.
Playwright test verifies the whole app. See <https://github.com/Comfy-Org/ComfyUI_frontend/blob/main/browser_tests/README.md> for details.
### litegraph.js
### LiteGraph
This repo is using litegraph package hosted on <https://github.com/Comfy-Org/litegraph.js>. Any changes to litegraph should be submitted in that repo instead.
#### Test litegraph.js changes
### Test litegraph changes
- Run `npm link` in the local litegraph repo.
- Run `npm link @comfyorg/litegraph` in this repo.
This will replace the litegraph package in this repo with the local litegraph repo.
### i18n
## Deploy
See [locales/README.md](src/locales/README.md) for details.
- Option 1: Set `DEPLOY_COMFYUI_DIR` in `.env` and run `npm run deploy`.
- Option 2: Copy everything under `dist/` to `ComfyUI/web/` in your ComfyUI checkout manually.
## Troubleshooting
## Publish release to ComfyUI main repo
For comprehensive troubleshooting and technical support, please refer to our official documentation:
Run following command to publish a release to ComfyUI main repo. The script will create a new branch and do a commit to `web/` folder by checkout `dist.zip`
from GitHub release.
- **[General Troubleshooting Guide](https://docs.comfy.org/troubleshooting/overview)** - Common issues, performance optimization, and reporting bugs
- **[Custom Node Issues](https://docs.comfy.org/troubleshooting/custom-node-issues)** - Debugging custom node problems and conflicts
- **[Desktop Installation Guide](https://docs.comfy.org/installation/desktop/windows)** - Desktop-specific installation and troubleshooting
- `python scripts/main_repo_release.py <path_to_comfyui_main_repo> <version>`

8
babel.config.json Normal file
View File

@@ -0,0 +1,8 @@
{
"presets": [
"@babel/preset-env"
],
"plugins": [
"babel-plugin-transform-import-meta"
]
}

View File

@@ -1,6 +1,6 @@
# Playwright Testing for ComfyUI_frontend
This document outlines the setup, usage, and common patterns for Playwright browser tests in the ComfyUI_frontend project.
This document outlines the setup and usage of Playwright for testing the ComfyUI_frontend project.
## WARNING
@@ -9,39 +9,18 @@ If `TEST_COMFYUI_DIR` in `.env` isn't set to your `(Comfy Path)/ComfyUI` directo
## Setup
### ComfyUI devtools
Clone <https://github.com/Comfy-Org/ComfyUI_devtools> to your `custom_nodes` directory.
_ComfyUI_devtools adds additional API endpoints and nodes to ComfyUI for browser testing._
Clone <https://github.com/Comfy-Org/ComfyUI_devtools> to your `custom_nodes` directory.
ComfyUI_devtools adds additional API endpoints and nodes to ComfyUI for browser testing.
Ensure you have Node.js v20 or later installed. Then, set up the Chromium test driver:
### Node.js & Playwright Prerequisites
Ensure you have Node.js v20 or v22 installed. Then, set up the Chromium test driver:
```bash
npx playwright install chromium --with-deps
```
### Environment Variables
Ensure the environment variables in `.env` are set correctly according to your setup.
The `.env` file will not exist until you create it yourself.
A template with helpful information can be found in `.env_example`.
### Multiple Tests
If you are running Playwright tests in parallel or running the same test multiple times, the flag `--multi-user` must be added to the main ComfyUI process.
### Release API Mocking
By default, all tests mock the release API (`api.comfy.org/releases`) to prevent release notification popups from interfering with test execution. This is necessary because the release notifications can appear over UI elements and block test interactions.
To test with real release data, you can disable mocking:
```typescript
await comfyPage.setup({ mockReleases: false });
```
For tests that specifically need to test release functionality, see the example in `tests/releaseNotifications.spec.ts`.
## Running Tests
There are multiple ways to run the tests:
There are two ways to run the tests:
1. **Headless mode with report generation:**
```bash
@@ -57,239 +36,14 @@ There are multiple ways to run the tests:
![Playwright UI Mode](https://github.com/user-attachments/assets/6a1ebef0-90eb-4157-8694-f5ee94d03755)
3. **Running specific tests:**
```bash
npx playwright test widget.spec.ts
```
## Test Structure
Browser tests in this project follow a specific organization pattern:
- **Fixtures**: Located in `fixtures/` - These provide test setup and utilities
- `ComfyPage.ts` - The main fixture for interacting with ComfyUI
- `ComfyMouse.ts` - Utility for mouse interactions with the canvas
- Components fixtures in `fixtures/components/` - Page object models for UI components
- **Tests**: Located in `tests/` - The actual test specifications
- Organized by functionality (e.g., `widget.spec.ts`, `interaction.spec.ts`)
- Snapshot directories (e.g., `widget.spec.ts-snapshots/`) contain reference screenshots
- **Utilities**: Located in `utils/` - Common utility functions
- `litegraphUtils.ts` - Utilities for working with LiteGraph nodes
## Writing Effective Tests
When writing new tests, follow these patterns:
### Test Structure
```typescript
// Import the test fixture
import { comfyPageFixture as test } from '../fixtures/ComfyPage';
test.describe('Feature Name', () => {
// Set up test environment if needed
test.beforeEach(async ({ comfyPage }) => {
// Common setup
});
test('should do something specific', async ({ comfyPage }) => {
// Test implementation
});
});
```
### Leverage Existing Fixtures and Helpers
Always check for existing helpers and fixtures before implementing new ones:
- **ComfyPage**: Main fixture with methods for canvas interaction and node management
- **ComfyMouse**: Helper for precise mouse operations on the canvas
- **Helpers**: Check `browser_tests/helpers/` for specialized helpers like:
- `actionbar.ts`: Interact with the action bar
- `manageGroupNode.ts`: Group node management operations
- `templates.ts`: Template workflows operations
- **Component Fixtures**: Check `browser_tests/fixtures/components/` for UI component helpers
- **Utility Functions**: Check `browser_tests/utils/` and `browser_tests/fixtures/utils/` for shared utilities
Most common testing needs are already addressed by these helpers, which will make your tests more consistent and reliable.
### Key Testing Patterns
1. **Focus elements explicitly**:
Canvas-based elements often need explicit focus before interaction:
```typescript
// Click the canvas first to focus it before pressing keys
await comfyPage.canvas.click();
await comfyPage.page.keyboard.press('a');
```
2. **Mark canvas as dirty if needed**:
Some interactions need explicit canvas updates:
```typescript
// After programmatically changing node state, mark canvas dirty
await comfyPage.page.evaluate(() => {
window['app'].graph.setDirtyCanvas(true, true);
});
```
3. **Use node references over coordinates**:
Node references from `fixtures/utils/litegraphUtils.ts` provide stable ways to interact with nodes:
```typescript
// Prefer this:
const node = await comfyPage.getNodeRefsByType('LoadImage')[0];
await node.click('title');
// Over this:
await comfyPage.canvas.click({ position: { x: 100, y: 100 } });
```
4. **Wait for canvas to render after UI interactions**:
```typescript
await comfyPage.nextFrame();
```
5. **Clean up persistent server state**:
While most state is reset between tests, anything stored on the server persists:
```typescript
// Reset settings that affect other tests (these are stored on server)
await comfyPage.setSetting('Comfy.ColorPalette', 'dark');
await comfyPage.setSetting('Comfy.NodeBadge.NodeIdBadgeMode', 'None');
// Clean up uploaded files if needed
await comfyPage.request.delete(`${comfyPage.url}/api/delete/image.png`);
```
6. **Prefer functional assertions over screenshots**:
Use screenshots only when visual verification is necessary:
```typescript
// Prefer this:
expect(await node.isPinned()).toBe(true);
expect(await node.getProperty('title')).toBe('Expected Title');
// Over this - only use when needed:
await expect(comfyPage.canvas).toHaveScreenshot('state.png');
```
7. **Use minimal test workflows**:
When creating test workflows, keep them as minimal as possible:
```typescript
// Include only the components needed for the test
await comfyPage.loadWorkflow('single_ksampler');
```
## Common Patterns and Utilities
### Page Object Pattern
Tests use the Page Object pattern to create abstractions over the UI:
```typescript
// Using the ComfyPage fixture
test('Can toggle boolean widget', async ({ comfyPage }) => {
await comfyPage.loadWorkflow('widgets/boolean_widget')
const node = (await comfyPage.getFirstNodeRef())!
const widget = await node.getWidget(0)
await widget.click()
});
```
### Node References
The `NodeReference` class provides helpers for interacting with LiteGraph nodes:
```typescript
// Getting node by type and interacting with it
const nodes = await comfyPage.getNodeRefsByType('LoadImage')
const loadImageNode = nodes[0]
const widget = await loadImageNode.getWidget(0)
await widget.click()
```
### Visual Regression Testing
Tests use screenshot comparisons to verify UI state:
```typescript
// Take a screenshot and compare to reference
await expect(comfyPage.canvas).toHaveScreenshot('boolean_widget_toggled.png')
```
### Waiting for Animations
Always call `nextFrame()` after actions that trigger animations:
```typescript
await comfyPage.canvas.click({ position: { x: 100, y: 100 } })
await comfyPage.nextFrame() // Wait for canvas to redraw
```
### Mouse Interactions
Canvas operations use special helpers to ensure proper timing:
```typescript
// Using ComfyMouse for drag and drop
await comfyMouse.dragAndDrop(
{ x: 100, y: 100 }, // From
{ x: 200, y: 200 } // To
)
// Standard ComfyPage helpers
await comfyPage.drag({ x: 100, y: 100 }, { x: 200, y: 200 })
await comfyPage.pan({ x: 200, y: 200 })
await comfyPage.zoom(-100) // Zoom in
```
### Workflow Management
Tests use workflows stored in `assets/` for consistent starting points:
```typescript
// Load a test workflow
await comfyPage.loadWorkflow('single_ksampler')
// Wait for workflow to load and stabilize
await comfyPage.nextFrame()
```
### Custom Assertions
The project includes custom Playwright assertions through `comfyExpect`:
```typescript
// Check if a node is in a specific state
await expect(node).toBePinned()
await expect(node).toBeBypassed()
await expect(node).toBeCollapsed()
```
## Troubleshooting Common Issues
### Flaky Tests
- **Timing Issues**: Always wait for animations to complete with `nextFrame()`
- **Coordinate Sensitivity**: Canvas coordinates are viewport-relative; use node references when possible
- **Test Isolation**: Tests run in parallel; avoid dependencies between tests
- **Screenshots vary**: Ensure your OS and browser match the reference environment (Linux)
- **Async / await**: Race conditions are a very common cause of test flakiness
## Screenshot Expectations
Due to variations in system font rendering, screenshot expectations are platform-specific. Please note:
- **DO NOT commit local screenshot expectations** to the repository
- We maintain Linux screenshot expectations as our GitHub Action runner operates in a Linux environment
- While developing, you can generate local screenshots for your tests, but these will differ from CI-generated ones
- We maintain Linux screenshot expectations as our GitHub Action runner operates in a Linux environment.
- To set new test expectations:
1. Create a pull request from a `Comfy-Org/ComfyUI_frontend` branch.
2. Add the `New Browser Test Expectation` tag to your pull request.
3. This will trigger a GitHub action to update the screenshot expectations automatically.
To set new test expectations for PR:
1. Write your test with screenshot assertions using `toHaveScreenshot(filename)`
2. Create a pull request from a `Comfy-Org/ComfyUI_frontend` branch
3. Add the `New Browser Test Expectation` tag to your pull request
4. The GitHub CI will automatically generate and commit the reference screenshots
This approach ensures consistent screenshot expectations across all PRs and avoids issues with platform-specific rendering.
> **Note:** If you're making a pull request from a forked repository, the GitHub action won't be able to commit updated screenshot expectations directly to your PR branch.
> **Note:** If you're making a pull request from a forked repository, the GitHub action won't be able to commit updated screenshot expectations directly to your PR branch.

View File

@@ -1,9 +1,8 @@
import type { Response } from '@playwright/test'
import type { StatusWsMessage } from '../src/types/apiTypes.ts'
import { expect, mergeTests } from '@playwright/test'
import type { StatusWsMessage } from '../../src/schemas/apiSchema.ts'
import { comfyPageFixture } from '../fixtures/ComfyPage.ts'
import { webSocketFixture } from '../fixtures/ws.ts'
import { comfyPageFixture } from './fixtures/ComfyPage'
import { webSocketFixture } from './fixtures/ws.ts'
const test = mergeTests(comfyPageFixture, webSocketFixture)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -1,126 +0,0 @@
{
"id": "51b9b184-770d-40ac-a478-8cc31667ff23",
"revision": 0,
"last_node_id": 5,
"last_link_id": 3,
"nodes": [
{
"id": 4,
"type": "KSampler",
"pos": [
867.4669799804688,
347.22369384765625
],
"size": [
315,
262
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": null
},
{
"name": "positive",
"type": "CONDITIONING",
"link": null
},
{
"name": "negative",
"type": "CONDITIONING",
"link": null
},
{
"name": "latent_image",
"type": "LATENT",
"link": null
},
{
"name": "steps",
"type": "INT",
"widget": {
"name": "steps"
},
"link": 3
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": null
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
0,
"randomize",
20,
8,
"euler",
"normal",
1
]
},
{
"id": 5,
"type": "PrimitiveInt",
"pos": [
443.0852355957031,
441.131591796875
],
"size": [
315,
82
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "INT",
"type": "INT",
"links": [
3
]
}
],
"properties": {
"Node name for S&R": "PrimitiveInt"
},
"widgets_values": [
0,
"randomize"
]
}
],
"links": [
[
3,
5,
0,
4,
5,
"INT"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1.9487171000000016,
"offset": [
-325.57196748514497,
-168.13150517966463
]
}
},
"version": 0.4
}

View File

@@ -1,244 +0,0 @@
{
"id": "fe4562c0-3a0b-4614-bdec-7039a58d75b8",
"revision": 0,
"last_node_id": 2,
"last_link_id": 0,
"nodes": [
{
"id": 2,
"type": "e5fb1765-9323-4548-801a-5aead34d879e",
"pos": [
627.5973510742188,
423.0972900390625
],
"size": [
144.15234375,
46
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
{
"name": "positive",
"type": "CONDITIONING",
"link": null
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": null
}
],
"properties": {},
"widgets_values": []
}
],
"links": [],
"groups": [],
"definitions": {
"subgraphs": [
{
"id": "e5fb1765-9323-4548-801a-5aead34d879e",
"version": 1,
"state": {
"lastGroupId": 0,
"lastNodeId": 2,
"lastLinkId": 4,
"lastRerouteId": 0
},
"revision": 0,
"config": {},
"name": "New Subgraph",
"inputNode": {
"id": -10,
"bounding": [
347.90441582814213,
417.3822440655296,
120,
60
]
},
"outputNode": {
"id": -20,
"bounding": [
892.5973510742188,
416.0972900390625,
120,
60
]
},
"inputs": [
{
"id": "c5cc99d8-a2b6-4bf3-8be7-d4949ef736cd",
"name": "positive",
"type": "CONDITIONING",
"linkIds": [
1
],
"pos": {
"0": 447.9044189453125,
"1": 437.3822326660156
}
}
],
"outputs": [
{
"id": "9bd488b9-e907-4c95-a7a4-85c5597a87af",
"name": "LATENT",
"type": "LATENT",
"linkIds": [
2
],
"pos": {
"0": 912.5973510742188,
"1": 436.0972900390625
}
}
],
"widgets": [],
"nodes": [
{
"id": 1,
"type": "KSampler",
"pos": [
554.8743286132812,
100.95539093017578
],
"size": [
270,
262
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"localized_name": "model",
"name": "model",
"type": "MODEL",
"link": null
},
{
"localized_name": "positive",
"name": "positive",
"type": "CONDITIONING",
"link": 1
},
{
"localized_name": "negative",
"name": "negative",
"type": "CONDITIONING",
"link": null
},
{
"localized_name": "latent_image",
"name": "latent_image",
"type": "LATENT",
"link": null
}
],
"outputs": [
{
"localized_name": "LATENT",
"name": "LATENT",
"type": "LATENT",
"links": [
2
]
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
0,
"randomize",
20,
8,
"euler",
"simple",
1
]
},
{
"id": 2,
"type": "VAEEncode",
"pos": [
685.1265869140625,
439.1734619140625
],
"size": [
140,
46
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
{
"localized_name": "pixels",
"name": "pixels",
"type": "IMAGE",
"link": null
},
{
"localized_name": "vae",
"name": "vae",
"type": "VAE",
"link": null
}
],
"outputs": [
{
"localized_name": "LATENT",
"name": "LATENT",
"type": "LATENT",
"links": [
4
]
}
],
"properties": {
"Node name for S&R": "VAEEncode"
}
}
],
"groups": [],
"links": [
{
"id": 1,
"origin_id": -10,
"origin_slot": 0,
"target_id": 1,
"target_slot": 1,
"type": "CONDITIONING"
},
{
"id": 2,
"origin_id": 1,
"origin_slot": 0,
"target_id": -20,
"target_slot": 0,
"type": "LATENT"
}
],
"extra": {}
}
]
},
"config": {},
"extra": {
"ds": {
"scale": 0.8894351682943402,
"offset": [
58.7671207025881,
137.7124650620126
]
},
"frontendVersion": "1.24.1"
},
"version": 0.4
}

View File

@@ -1,43 +0,0 @@
{
"last_node_id": 1,
"last_link_id": 0,
"nodes": [
{
"id": 1,
"type": "CLIPTextEncode",
"pos": [20, 50],
"size": [400, 200],
"flags": { "collapsed": true },
"order": 0,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": null,
"localized_name": "clip"
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": null,
"localized_name": "CONDITIONING"
}
],
"properties": {},
"widgets_values": ["Should not be displayed."]
}
],
"links": [],
"groups": [],
"config": {},
"extra": {
"ds": {
"offset": [0, 0],
"scale": 1
}
},
"version": 0.4
}

View File

@@ -114,7 +114,7 @@
{ "name": "VAE", "type": "VAE", "links": [8], "slot_index": 2 }
],
"properties": {},
"widgets_values": ["v1-5-pruned-emaonly-fp16.safetensors"]
"widgets_values": ["v1-5-pruned-emaonly.ckpt"]
}
],
"links": [
@@ -130,11 +130,6 @@
],
"groups": [],
"config": {},
"extra": {
"ds": {
"offset": [0, 0],
"scale": 1
}
},
"extra": {},
"version": 0.4
}

View File

@@ -1,53 +0,0 @@
{
"id": "9bcb9451-8319-492a-88d4-fb711d8c3d25",
"revision": 0,
"last_node_id": 6,
"last_link_id": 0,
"nodes": [
{
"id": 6,
"type": "DevToolsNodeWithDefaultInput",
"pos": [
8.39722728729248,
29.727279663085938
],
"size": [
315,
82
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
{
"name": "float_input",
"shape": 7,
"type": "FLOAT",
"link": null
}
],
"outputs": [],
"properties": {
"Node name for S&R": "DevToolsNodeWithDefaultInput"
},
"widgets_values": [
0,
1,
0
]
}
],
"links": [],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1,
"offset": [
0,
0
]
}
},
"version": 0.4
}

View File

@@ -1,82 +0,0 @@
{
"last_node_id": 9,
"last_link_id": 13,
"nodes": [
{
"id": 3,
"type": "KSampler",
"pos": [
0,
30
],
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": null
},
{
"name": "positive",
"type": "CONDITIONING",
"link": null
},
{
"name": "negative",
"type": "CONDITIONING",
"link": null
},
{
"name": "latent_image",
"type": "LATENT",
"link": null
} ,
{
"name": "dynamic_input",
"type": "FLOAT",
"link": null,
"_meta": "Dynamically added input via frontend JS logic"
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
156680208700286,
"randomize",
20,
8,
"euler",
"normal",
1
]
}
],
"links": [],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1,
"offset": [
0,
0
]
}
},
"version": 0.4
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 200 KiB

View File

@@ -499,11 +499,6 @@
],
"groups": [],
"config": {},
"extra": {
"ds": {
"offset": [0, 0],
"scale": 1
}
},
"extra": {},
"version": 0.4
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 828 B

View File

@@ -1,85 +0,0 @@
{
"id": "1a95532f-c8aa-4c9d-a7f6-f928ba2d4862",
"revision": 0,
"last_node_id": 4,
"last_link_id": 3,
"nodes": [
{
"id": 4,
"type": "PreviewAny",
"pos": [946.2566528320312, 598.4373168945312],
"size": [140, 76],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "source",
"type": "*",
"link": 3
}
],
"outputs": [],
"properties": {
"Node name for S&R": "PreviewAny"
},
"widgets_values": []
},
{
"id": 1,
"type": "PreviewAny",
"pos": [951.0236206054688, 421.3861083984375],
"size": [140, 76],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "source",
"type": "*",
"link": 2
}
],
"outputs": [],
"properties": {
"Node name for S&R": "PreviewAny"
},
"widgets_values": []
},
{
"id": 3,
"type": "PrimitiveString",
"pos": [575.1760864257812, 504.5214538574219],
"size": [270, 58],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": [2, 3]
}
],
"properties": {
"Node name for S&R": "PrimitiveString"
},
"widgets_values": ["foo"]
}
],
"links": [
[2, 3, 0, 1, 0, "*"],
[3, 3, 0, 4, 0, "*"]
],
"groups": [],
"config": {},
"extra": {
"frontendVersion": "1.19.1",
"ds": {
"offset": [0, 0],
"scale": 1
}
},
"version": 0.4
}

View File

@@ -1,163 +0,0 @@
{
"last_node_id": 19,
"last_link_id": 14,
"nodes": [
{
"id": 19,
"type": "workflow>two_VAE_decode",
"pos": [
1368.800048828125,
768.7999877929688
],
"size": [
418.1999816894531,
86
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": null
},
{
"name": "vae",
"type": "VAE",
"link": null
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": null
},
{
"name": "VAEDecode IMAGE",
"type": "IMAGE",
"links": null
}
],
"properties": {}
}
],
"links": [],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1,
"offset": [
0,
0
]
},
"node_versions": {},
"ue_links": [],
"groupNodes": {
"two_VAE_decode": {
"nodes": [
{
"id": -1,
"type": "VAEDecode",
"pos": [
1368.800048828125,
768.7999877929688
],
"size": [
210,
46
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": null,
"localized_name": "samples"
},
{
"name": "vae",
"type": "VAE",
"link": null,
"localized_name": "vae"
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": null,
"localized_name": "IMAGE"
}
],
"properties": {
"Node name for S&R": "VAEDecode"
},
"index": 0
},
{
"id": -1,
"type": "VAEDecode",
"pos": [
1368.800048828125,
873.7999877929688
],
"size": [
210,
46
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": null,
"localized_name": "samples"
},
{
"name": "vae",
"type": "VAE",
"link": null,
"localized_name": "vae"
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": null,
"localized_name": "IMAGE"
}
],
"properties": {
"Node name for S&R": "VAEDecode"
},
"index": 1
}
],
"links": [],
"external": [],
"config": {
"1": {
"input": {
"samples": {
"visible": false
},
"vae": {
"visible": false
}
}
}
}
}
}
},
"version": 0.4
}

View File

@@ -43,10 +43,6 @@
"groups": [],
"config": {},
"extra": {
"ds": {
"offset": [0, 0],
"scale": 1
},
"groupNodes": {
"group_node": {
"nodes": [
@@ -405,4 +401,4 @@
}
},
"version": 0.4
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 488 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,74 +0,0 @@
{
"id": "51b9b184-770d-40ac-a478-8cc31667ff23",
"revision": 0,
"last_node_id": 2,
"last_link_id": 1,
"nodes": [
{
"id": 1,
"type": "CLIPTextEncode",
"pos": [904, 466],
"size": [400, 200],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "text",
"type": "STRING",
"widget": {
"name": "text"
},
"link": 1
},
{
"name": "clip",
"type": "CLIP",
"link": null
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": null
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [""]
},
{
"id": 2,
"type": "PrimitiveString",
"pos": [556.8589477539062, 472.94342041015625],
"size": [315, 58],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": [1]
}
],
"properties": {
"Node name for S&R": "PrimitiveString"
},
"widgets_values": ["foo"]
}
],
"links": [[1, 2, 0, 1, 0, "STRING"]],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1.7715610000000013,
"offset": [-388.521484375, -162.31336975097656]
}
},
"version": 0.4
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 260 KiB

After

Width:  |  Height:  |  Size: 316 KiB

View File

@@ -110,10 +110,6 @@
"groups": [],
"config": {},
"extra": {
"ds": {
"offset": [0, 0],
"scale": 1
},
"groupNodes": {
"hello": {
"nodes": [
@@ -253,4 +249,4 @@
}
},
"version": 0.4
}
}

View File

@@ -18,7 +18,7 @@
{
"name": "fake_model.safetensors",
"url": "http://localhost:8188/api/devtools/fake_model.safetensors",
"directory": "text_encoders"
"directory": "clip"
}
],
"version": 0.4

View File

@@ -1,54 +0,0 @@
{
"last_node_id": 1,
"last_link_id": 1,
"nodes": [
{
"id": 1,
"type": "CheckpointLoaderSimple",
"pos": [256, 256],
"size": [315, 98],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": null
},
{
"name": "CLIP",
"type": "CLIP",
"links": null
},
{
"name": "VAE",
"type": "VAE",
"links": null
}
],
"properties": {
"Node name for S&R": "CheckpointLoaderSimple",
"models": [
{
"name": "fake_model.safetensors",
"url": "http://localhost:8188/api/devtools/fake_model.safetensors",
"directory": "text_encoders"
}
]
},
"widgets_values": ["fake_model.safetensors"]
}
],
"links": [],
"groups": [],
"config": {},
"extra": {
"ds": {
"offset": [0, 0],
"scale": 1
}
},
"version": 0.4
}

View File

@@ -1,71 +0,0 @@
{
"last_node_id": 1,
"last_link_id": 0,
"nodes": [
{
"id": 1,
"type": "UNKNOWN NODE",
"pos": [
48,
86
],
"size": {
"0": 358.80780029296875,
"1": 314.7989501953125
},
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": null,
"slot_index": 0
},
{
"name": "foo",
"type": "STRING",
"link": null,
"slot_index": 1,
"widget": {
"name": "foo"
}
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": [],
"slot_index": 0,
"shape": 6
}
],
"properties": {
"Node name for S&R": "UNKNOWN NODE"
},
"widgets_values": [
"wd-v1-4-moat-tagger-v2",
0.35,
0.85,
false,
false,
"",
{
"foo": "bar"
}
]
}
],
"links": [],
"groups": [],
"config": {},
"extra": {
"ds": {
"offset": [0, 0],
"scale": 1
}
},
"version": 0.4
}

View File

@@ -1,59 +0,0 @@
{
"last_node_id": 1,
"last_link_id": 0,
"nodes": [
{
"id": 1,
"type": "CheckpointLoaderSimple",
"pos": [256, 256],
"size": [315, 98],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": null
},
{
"name": "CLIP",
"type": "CLIP",
"links": null
},
{
"name": "VAE",
"type": "VAE",
"links": null
}
],
"properties": {
"Node name for S&R": "CheckpointLoaderSimple",
"models": [
{
"name": "outdated_model.safetensors",
"url": "http://localhost:8188/api/devtools/fake_model.safetensors",
"directory": "text_encoders"
},
{
"name": "another_outdated_model.safetensors",
"url": "http://localhost:8188/api/devtools/fake_model.safetensors",
"directory": "text_encoders"
}
]
},
"widgets_values": ["current_selected_model.safetensors"]
}
],
"links": [],
"groups": [],
"config": {},
"extra": {
"ds": {
"offset": [0, 0],
"scale": 1
}
},
"version": 0.4
}

View File

@@ -1,716 +0,0 @@
{
"id": "976d6e9a-927d-42db-abd4-96bfc0ecf8d9",
"revision": 0,
"last_node_id": 10,
"last_link_id": 11,
"nodes": [
{
"id": 10,
"type": "8beb610f-ddd1-4489-ae0d-2f732a4042ae",
"pos": [
532,
412.5
],
"size": [
140,
46
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
10
]
},
{
"name": "VAE",
"type": "VAE",
"links": [
11
]
}
],
"title": "subgraph 2",
"properties": {},
"widgets_values": []
},
{
"id": 8,
"type": "VAEDecode",
"pos": [
758.2109985351562,
398.3681335449219
],
"size": [
210,
46
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 10
},
{
"name": "vae",
"type": "VAE",
"link": 11
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"slot_index": 0,
"links": [
9
]
}
],
"properties": {
"Node name for S&R": "VAEDecode"
},
"widgets_values": []
},
{
"id": 9,
"type": "SaveImage",
"pos": [
1028.9615478515625,
381.83746337890625
],
"size": [
210,
270
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 9
}
],
"outputs": [],
"properties": {},
"widgets_values": [
"ComfyUI"
]
}
],
"links": [
[
9,
8,
0,
9,
0,
"IMAGE"
],
[
10,
10,
0,
8,
0,
"LATENT"
],
[
11,
10,
1,
8,
1,
"VAE"
]
],
"groups": [],
"definitions": {
"subgraphs": [
{
"id": "8beb610f-ddd1-4489-ae0d-2f732a4042ae",
"version": 1,
"state": {
"lastGroupId": 0,
"lastNodeId": 10,
"lastLinkId": 14,
"lastRerouteId": 0
},
"revision": 0,
"config": {},
"name": "subgraph 2",
"inputNode": {
"id": -10,
"bounding": [
-154,
415.5,
120,
40
]
},
"outputNode": {
"id": -20,
"bounding": [
1238,
395.5,
120,
80
]
},
"inputs": [],
"outputs": [
{
"id": "4d6c7e4e-971e-4f78-9218-9a604db53a4b",
"name": "LATENT",
"type": "LATENT",
"linkIds": [
7
],
"localized_name": "LATENT",
"pos": {
"0": 1258,
"1": 415.5
}
},
{
"id": "f8201d4f-7fc6-4a1b-b8c9-9f0716d9c09a",
"name": "VAE",
"type": "VAE",
"linkIds": [
14
],
"localized_name": "VAE",
"pos": {
"0": 1258,
"1": 435.5
}
}
],
"widgets": [],
"nodes": [
{
"id": 6,
"type": "CLIPTextEncode",
"pos": [
415,
186
],
"size": [
422.84503173828125,
164.31304931640625
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
{
"localized_name": "clip",
"name": "clip",
"type": "CLIP",
"link": 13
}
],
"outputs": [
{
"localized_name": "CONDITIONING",
"name": "CONDITIONING",
"type": "CONDITIONING",
"slot_index": 0,
"links": [
4
]
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"
]
},
{
"id": 3,
"type": "KSampler",
"pos": [
863,
186
],
"size": [
315,
262
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"localized_name": "model",
"name": "model",
"type": "MODEL",
"link": 12
},
{
"localized_name": "positive",
"name": "positive",
"type": "CONDITIONING",
"link": 4
},
{
"localized_name": "negative",
"name": "negative",
"type": "CONDITIONING",
"link": 10
},
{
"localized_name": "latent_image",
"name": "latent_image",
"type": "LATENT",
"link": 11
}
],
"outputs": [
{
"localized_name": "LATENT",
"name": "LATENT",
"type": "LATENT",
"slot_index": 0,
"links": [
7
]
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
32115495257102,
"randomize",
20,
8,
"euler",
"normal",
1
]
},
{
"id": 10,
"type": "dbe5763f-440b-47b4-82ac-454f1f98b0e3",
"pos": [
194.13900756835938,
657.3333740234375
],
"size": [
140,
106
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [],
"outputs": [
{
"localized_name": "CONDITIONING",
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
10
]
},
{
"localized_name": "LATENT",
"name": "LATENT",
"type": "LATENT",
"links": [
11
]
},
{
"localized_name": "MODEL",
"name": "MODEL",
"type": "MODEL",
"links": [
12
]
},
{
"localized_name": "CLIP",
"name": "CLIP",
"type": "CLIP",
"links": [
13
]
},
{
"localized_name": "VAE",
"name": "VAE",
"type": "VAE",
"links": [
14
]
}
],
"title": "subgraph 3",
"properties": {},
"widgets_values": []
}
],
"groups": [],
"links": [
{
"id": 4,
"origin_id": 6,
"origin_slot": 0,
"target_id": 3,
"target_slot": 1,
"type": "CONDITIONING"
},
{
"id": 7,
"origin_id": 3,
"origin_slot": 0,
"target_id": -20,
"target_slot": 0,
"type": "LATENT"
},
{
"id": 10,
"origin_id": 10,
"origin_slot": 0,
"target_id": 3,
"target_slot": 2,
"type": "CONDITIONING"
},
{
"id": 11,
"origin_id": 10,
"origin_slot": 1,
"target_id": 3,
"target_slot": 3,
"type": "LATENT"
},
{
"id": 12,
"origin_id": 10,
"origin_slot": 2,
"target_id": 3,
"target_slot": 0,
"type": "MODEL"
},
{
"id": 13,
"origin_id": 10,
"origin_slot": 3,
"target_id": 6,
"target_slot": 0,
"type": "CLIP"
},
{
"id": 14,
"origin_id": 10,
"origin_slot": 4,
"target_id": -20,
"target_slot": 1,
"type": "VAE"
}
],
"extra": {}
},
{
"id": "dbe5763f-440b-47b4-82ac-454f1f98b0e3",
"version": 1,
"state": {
"lastGroupId": 0,
"lastNodeId": 9,
"lastLinkId": 9,
"lastRerouteId": 0
},
"revision": 0,
"config": {},
"name": "subgraph 3",
"inputNode": {
"id": -10,
"bounding": [
-154,
517,
120,
40
]
},
"outputNode": {
"id": -20,
"bounding": [
898.2780151367188,
467,
128.6640625,
140
]
},
"inputs": [],
"outputs": [
{
"id": "b4882169-329b-43f6-a373-81abfbdea55b",
"name": "CONDITIONING",
"type": "CONDITIONING",
"linkIds": [
6
],
"localized_name": "CONDITIONING",
"pos": {
"0": 918.2780151367188,
"1": 487
}
},
{
"id": "01f51f96-a741-428e-8772-9557ee50b609",
"name": "LATENT",
"type": "LATENT",
"linkIds": [
2
],
"localized_name": "LATENT",
"pos": {
"0": 918.2780151367188,
"1": 507
}
},
{
"id": "47fa906e-d80b-45c3-a596-211a0e59d4a1",
"name": "MODEL",
"type": "MODEL",
"linkIds": [
1
],
"localized_name": "MODEL",
"pos": {
"0": 918.2780151367188,
"1": 527
}
},
{
"id": "f03dccd7-10e8-4513-9994-15854a92d192",
"name": "CLIP",
"type": "CLIP",
"linkIds": [
3
],
"localized_name": "CLIP",
"pos": {
"0": 918.2780151367188,
"1": 547
}
},
{
"id": "a666877f-e34f-49bc-8a78-b26156656b83",
"name": "VAE",
"type": "VAE",
"linkIds": [
8
],
"localized_name": "VAE",
"pos": {
"0": 918.2780151367188,
"1": 567
}
}
],
"widgets": [],
"nodes": [
{
"id": 7,
"type": "CLIPTextEncode",
"pos": [
413,
389
],
"size": [
425.27801513671875,
180.6060791015625
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"localized_name": "clip",
"name": "clip",
"type": "CLIP",
"link": 5
}
],
"outputs": [
{
"localized_name": "CONDITIONING",
"name": "CONDITIONING",
"type": "CONDITIONING",
"slot_index": 0,
"links": [
6
]
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"text, watermark"
]
},
{
"id": 5,
"type": "EmptyLatentImage",
"pos": [
473,
609
],
"size": [
315,
106
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"localized_name": "LATENT",
"name": "LATENT",
"type": "LATENT",
"slot_index": 0,
"links": [
2
]
}
],
"properties": {
"Node name for S&R": "EmptyLatentImage"
},
"widgets_values": [
512,
512,
1
]
},
{
"id": 4,
"type": "CheckpointLoaderSimple",
"pos": [
26,
474
],
"size": [
315,
98
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [],
"outputs": [
{
"localized_name": "MODEL",
"name": "MODEL",
"type": "MODEL",
"slot_index": 0,
"links": [
1
]
},
{
"localized_name": "CLIP",
"name": "CLIP",
"type": "CLIP",
"slot_index": 1,
"links": [
3,
5
]
},
{
"localized_name": "VAE",
"name": "VAE",
"type": "VAE",
"slot_index": 2,
"links": [
8
]
}
],
"properties": {
"Node name for S&R": "CheckpointLoaderSimple"
},
"widgets_values": [
"v1-5-pruned-emaonly-fp16.safetensors"
]
}
],
"groups": [],
"links": [
{
"id": 5,
"origin_id": 4,
"origin_slot": 1,
"target_id": 7,
"target_slot": 0,
"type": "CLIP"
},
{
"id": 6,
"origin_id": 7,
"origin_slot": 0,
"target_id": -20,
"target_slot": 0,
"type": "CONDITIONING"
},
{
"id": 2,
"origin_id": 5,
"origin_slot": 0,
"target_id": -20,
"target_slot": 1,
"type": "LATENT"
},
{
"id": 1,
"origin_id": 4,
"origin_slot": 0,
"target_id": -20,
"target_slot": 2,
"type": "MODEL"
},
{
"id": 3,
"origin_id": 4,
"origin_slot": 1,
"target_id": -20,
"target_slot": 3,
"type": "CLIP"
},
{
"id": 8,
"origin_id": 4,
"origin_slot": 2,
"target_id": -20,
"target_slot": 4,
"type": "VAE"
}
],
"extra": {}
}
]
},
"config": {},
"extra": {
"frontendVersion": "1.24.0-1"
},
"version": 0.4
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 B

After

Width:  |  Height:  |  Size: 195 KiB

View File

@@ -0,0 +1,10 @@
[
{
"name": "Three Nodes Template",
"data": "{\"nodes\":[{\"id\":7,\"type\":\"CLIPTextEncode\",\"pos\":[413,389],\"size\":[425.27801513671875,180.6060791015625],\"flags\":{},\"order\":3,\"mode\":0,\"inputs\":[{\"name\":\"clip\",\"type\":\"CLIP\",\"link\":null}],\"outputs\":[{\"name\":\"CONDITIONING\",\"type\":\"CONDITIONING\",\"links\":[],\"slot_index\":0}],\"properties\":{\"Node name for S&R\":\"CLIPTextEncode\"},\"widgets_values\":[\"text, watermark\"]},{\"id\":6,\"type\":\"CLIPTextEncode\",\"pos\":[415,186],\"size\":[422.84503173828125,164.31304931640625],\"flags\":{},\"order\":2,\"mode\":0,\"inputs\":[{\"name\":\"clip\",\"type\":\"CLIP\",\"link\":null}],\"outputs\":[{\"name\":\"CONDITIONING\",\"type\":\"CONDITIONING\",\"links\":[],\"slot_index\":0}],\"properties\":{\"Node name for S&R\":\"CLIPTextEncode\"},\"widgets_values\":[\"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,\"]},{\"id\":4,\"type\":\"CheckpointLoaderSimple\",\"pos\":[26,474],\"size\":[315,98],\"flags\":{},\"order\":1,\"mode\":0,\"inputs\":[],\"outputs\":[{\"name\":\"MODEL\",\"type\":\"MODEL\",\"links\":[],\"slot_index\":0},{\"name\":\"CLIP\",\"type\":\"CLIP\",\"links\":[],\"slot_index\":1},{\"name\":\"VAE\",\"type\":\"VAE\",\"links\":[],\"slot_index\":2}],\"properties\":{\"Node name for S&R\":\"CheckpointLoaderSimple\"},\"widgets_values\":[\"v1-5-pruned-emaonly.ckpt\"]}],\"groups\":[],\"reroutes\":[],\"links\":[{\"id\":5,\"origin_id\":4,\"origin_slot\":1,\"target_id\":7,\"target_slot\":0,\"type\":\"CLIP\"},{\"id\":3,\"origin_id\":4,\"origin_slot\":1,\"target_id\":6,\"target_slot\":0,\"type\":\"CLIP\"}]}"
},
{
"name": "Completely empty template",
"data": "{\"nodes\":[],\"groups\":[],\"reroutes\":[],\"links\":[]}"
}
]

View File

@@ -1,36 +0,0 @@
{
"id": "5635564e-189f-49e4-9b25-6b7634bcd595",
"revision": 0,
"last_node_id": 78,
"last_link_id": 53,
"nodes": [
{
"id": 78,
"type": "DevToolsNodeWithV2ComboInput",
"pos": [1320, 904],
"size": [270.3199157714844, 58],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "COMBO",
"type": "COMBO",
"links": null
}
],
"properties": {
"Node name for S&R": "DevToolsNodeWithV2ComboInput"
},
"widgets_values": ["A"]
}
],
"links": [],
"groups": [],
"config": {},
"extra": {
"frontendVersion": "1.19.7"
},
"version": 0.4
}

View File

@@ -1,60 +0,0 @@
{
"last_node_id": 2,
"last_link_id": 0,
"nodes": [
{
"id": 1,
"type": "Note",
"pos": [
50, 50
],
"size": [
322.3645935058594,
167.91612243652344
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [],
"properties": {},
"widgets_values": [
"Foo\n123"
],
"color": "#432",
"bgcolor": "#653"
},
{
"id": 2,
"type": "MarkdownNote",
"pos": [
50, 300
],
"size": [
320.9985656738281,
179.52735900878906
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [],
"outputs": [],
"properties": {},
"widgets_values": [
"# Bar\n123"
],
"color": "#432",
"bgcolor": "#653"
}
],
"links": [],
"groups": [],
"config": {},
"extra": {
"ds": {
"offset": [0, 0],
"scale": 1
}
},
"version": 0.4
}

View File

@@ -38,11 +38,7 @@
"groups": [],
"config": {},
"extra": {
"groupNodes": {},
"ds": {
"offset": [0, 0],
"scale": 1
}
"groupNodes": {}
},
"version": 0.4
}
}

View File

@@ -1,37 +0,0 @@
{
"last_node_id": 16,
"last_link_id": 17,
"nodes": [
{
"id": 16,
"type": "DevToolsNodeWithOptionalComboInput",
"pos": [1605, 480],
"size": [378, 58],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": null
}
],
"properties": {
"Node name for S&R": "DevToolsNodeWithOptionalComboInput"
},
"widgets_values": ["Random Unique Option 1740551583.3507228"]
}
],
"links": [],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 2.0875710456451313,
"offset": [-1311.5753953400676, -176.7620403697558]
}
},
"version": 0.4
}

View File

@@ -1,90 +0,0 @@
{
"last_node_id": 9,
"last_link_id": 9,
"nodes": [
{
"id": 3,
"type": "KSampler",
"pos": [
37,
98
],
"size": [
315,
262
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": null
},
{
"name": "positive",
"type": "CONDITIONING",
"link": null
},
{
"name": "negative",
"type": "CONDITIONING",
"link": null
},
{
"name": "latent_image",
"type": "LATENT",
"link": null
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
156680208700286,
"randomize",
20,
8,
"euler",
"normal",
1
]
}
],
"links": [],
"groups": [
{
"id": 1,
"title": "Group",
"bounding": [
23,
23,
900,
825
],
"color": "#3f789e",
"font_size": 24,
"flags": {}
}
],
"config": {},
"extra": {
"ds": {
"scale": 1,
"offset": [
0,
0
]
}
},
"version": 0.4
}

View File

@@ -1,64 +0,0 @@
{
"last_node_id": 3,
"last_link_id": 1,
"nodes": [
{
"id": 1,
"type": "PrimitiveNode",
"pos": [14, 43],
"size": [203.1999969482422, 40.36840057373047],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "connect to widget input",
"type": "*",
"links": [],
"slot_index": 0
}
],
"properties": {
"Run widget replace on values": false
}
},
{
"id": 2,
"type": "CLIPTextEncode",
"pos": [306.2463684082031, 45.30042266845703],
"size": [400, 200],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": null
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": null
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [""]
}
],
"links": [],
"groups": [],
"config": {},
"extra": {
"ds": {
"offset": [0, 0],
"scale": 1
}
},
"version": 0.4
}

View File

@@ -1,104 +0,0 @@
{
"last_node_id": 2,
"last_link_id": 1,
"nodes": [
{
"id": 2,
"type": "KSampler",
"pos": {
"0": 304.3653259277344,
"1": 42.15586471557617
},
"size": [
315,
262
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": null
},
{
"name": "positive",
"type": "CONDITIONING",
"link": null
},
{
"name": "negative",
"type": "CONDITIONING",
"link": null
},
{
"name": "latent_image",
"type": "LATENT",
"link": null
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
0,
"randomize",
20,
8,
"euler",
"normal",
1
]
},
{
"id": 1,
"type": "PrimitiveInt",
"pos": {
"0": 14,
"1": 43
},
"size": [
203.1999969482422,
40.368401303242536
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "value",
"type": "INT",
"links": [],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "Int"
},
"widgets_values": [10]
}
],
"links": [],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1,
"offset": [
0,
0
]
}
},
"version": 0.4
}

View File

@@ -1,48 +0,0 @@
{
"last_node_id": 15,
"last_link_id": 10,
"nodes": [
{
"id": 15,
"type": "DevToolsRemoteWidgetNode",
"pos": [
495,
735
],
"size": [
315,
58
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": null
}
],
"properties": {
"Node name for S&R": "DevToolsRemoteWidgetNode"
},
"widgets_values": [
"v1-5-pruned-emaonly-fp16.safetensors"
]
}
],
"links": [],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 0.8008869919566275,
"offset": [
538.9801226576359,
-55.24554581806672
]
}
},
"version": 0.4
}

View File

@@ -1,105 +0,0 @@
{
"last_node_id": 4,
"last_link_id": 2,
"nodes": [
{
"id": 3,
"type": "EmptyLatentImage",
"pos": [
380.51641845703125,
191.39659118652344
],
"size": [
315,
106
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "breadth",
"type": "INT",
"widget": {
"name": "breadth"
},
"link": 2
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": null
}
],
"properties": {
"Node name for S&R": "EmptyLatentImage"
},
"widgets_values": [
512,
512,
1
]
},
{
"id": 4,
"type": "PrimitiveNode",
"pos": [
73.6164321899414,
197.9966278076172
],
"size": [
210,
82
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "INT",
"type": "INT",
"widget": {
"name": "bredth"
},
"links": [
2
]
}
],
"title": "breadth",
"properties": {
"Run widget replace on values": false
},
"widgets_values": [
512,
"fixed"
]
}
],
"links": [
[
2,
4,
0,
3,
0,
"INT"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"offset": [0, 0],
"scale": 1
},
"VHS_latentpreview": true,
"VHS_latentpreviewrate": 0,
"VHS_MetadataImage": false,
"VHS_KeepIntermediate": false
},
"version": 0.4
}

View File

@@ -1,114 +0,0 @@
{
"last_node_id": 25,
"last_link_id": 33,
"nodes": [
{
"id": 4,
"type": "CheckpointLoaderSimple",
"pos": [160, 240],
"size": [315, 98],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"slot_index": 0,
"links": []
},
{
"name": "CLIP",
"type": "CLIP",
"slot_index": 1,
"links": []
},
{
"name": "VAE",
"type": "VAE",
"slot_index": 2,
"links": [33]
}
],
"properties": {
"Node name for S&R": "CheckpointLoaderSimple"
},
"widgets_values": ["v1-5-pruned-emaonly.safetensors"]
},
{
"id": 19,
"type": "VAEDecode",
"pos": [623.0897216796875, 324.64453125],
"size": [210, 46],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": null
},
{
"name": "vae",
"type": "VAE",
"link": 33
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": null
}
],
"properties": {
"Node name for S&R": "VAEDecode"
},
"widgets_values": []
}
],
"links": [[33, 4, 2, 19, 1, "VAE"]],
"floatingLinks": [
{
"id": 6,
"origin_id": 4,
"origin_slot": 2,
"target_id": -1,
"target_slot": -1,
"type": "VAE",
"parentId": 1
}
],
"groups": [],
"config": {},
"extra": {
"ds": {
"offset": [0, 0],
"scale": 1
},
"reroutes": [
{
"id": 1,
"pos": [545.4541015625, 295.85760498046875],
"linkIds": [],
"floating": {
"slotType": "output"
}
},
{
"id": 2,
"pos": [543.8283081054688, 355.45849609375],
"linkIds": [33]
}
],
"linkExtensions": [
{
"id": 33,
"parentId": 2
}
]
},
"version": 0.4
}

View File

@@ -1,49 +0,0 @@
{
"last_node_id": 12,
"last_link_id": 9,
"nodes": [
{
"id": 12,
"type": "DevToolsSimpleSlider",
"pos": [
50,
50
],
"size": [
315,
58
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "FLOAT",
"type": "FLOAT",
"links": null,
"label": "FLOAT"
}
],
"properties": {
"Node name for S&R": "DevToolsSimpleSlider"
},
"widgets_values": [
0.5
]
}
],
"links": [],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1,
"offset": [
0,
0
]
}
},
"version": 0.4
}

View File

@@ -1,113 +0,0 @@
{
"last_node_id": 16,
"last_link_id": 18,
"nodes": [
{
"id": 12,
"type": "VAEDecode",
"pos": [620, 260],
"size": [210, 46],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": null
},
{
"name": "vae",
"type": "VAE",
"link": 18
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": null
}
],
"properties": {
"Node name for S&R": "VAEDecode"
},
"widgets_values": []
},
{
"id": 13,
"type": "Reroute",
"pos": [510, 280],
"size": [75, 26],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "",
"type": "*",
"link": 13
}
],
"outputs": [
{
"name": "",
"type": "VAE",
"links": [18],
"slot_index": 0
}
],
"properties": {
"showOutputText": false,
"horizontal": false
}
},
{
"id": 4,
"type": "CheckpointLoaderSimple",
"pos": [160, 240],
"size": [315, 98],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [],
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [],
"slot_index": 1
},
{
"name": "VAE",
"type": "VAE",
"links": [13],
"slot_index": 2
}
],
"properties": {
"Node name for S&R": "CheckpointLoaderSimple"
},
"widgets_values": ["v1-5-pruned-emaonly.safetensors"]
}
],
"links": [
[13, 4, 2, 13, 0, "*"],
[18, 13, 0, 12, 1, "VAE"]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1,
"offset": [0, 0]
}
},
"version": 0.4
}

View File

@@ -368,10 +368,10 @@
"ds": {
"scale": 1,
"offset": [
0,
0
149.9747408641311,
383.8593224280729
]
}
},
"version": 0.4
}
}

View File

@@ -1,412 +0,0 @@
{
"id": "c4a254bb-935e-4013-b380-5e36954de4b0",
"revision": 0,
"last_node_id": 11,
"last_link_id": 9,
"nodes": [
{
"id": 11,
"type": "422723e8-4bf6-438c-823f-881ca81acead",
"pos": [
791.59912109375,
386.13336181640625
],
"size": [
210,
202
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": null
},
{
"name": "positive",
"type": "CONDITIONING",
"link": null
},
{
"name": "negative",
"type": "CONDITIONING",
"link": null
},
{
"name": "latent_image",
"type": "LATENT",
"link": null
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": null
}
],
"properties": {},
"widgets_values": [
"",
""
]
}
],
"links": [],
"groups": [],
"definitions": {
"subgraphs": [
{
"id": "422723e8-4bf6-438c-823f-881ca81acead",
"version": 1,
"state": {
"lastGroupId": 0,
"lastNodeId": 12,
"lastLinkId": 16,
"lastRerouteId": 0
},
"revision": 0,
"config": {},
"name": "New Subgraph",
"inputNode": {
"id": -10,
"bounding": [
481.59912109375,
379.13336181640625,
120,
160
]
},
"outputNode": {
"id": -20,
"bounding": [
1121.59912109375,
379.13336181640625,
128.6640625,
60
]
},
"inputs": [
{
"id": "0f07c10e-5705-4764-9b24-b69606c6dbcc",
"name": "text",
"type": "STRING",
"linkIds": [
10
],
"pos": {
"0": 581.59912109375,
"1": 399.13336181640625
}
},
{
"id": "736e5a03-0f7f-4e48-93e4-fd66ea6c30f1",
"name": "text_1",
"type": "STRING",
"linkIds": [
11
],
"pos": {
"0": 581.59912109375,
"1": 419.13336181640625
}
},
{
"id": "b62e7a0b-cc7e-4ca5-a4e1-c81607a13f58",
"name": "model",
"type": "MODEL",
"linkIds": [
13
],
"pos": {
"0": 581.59912109375,
"1": 439.13336181640625
}
},
{
"id": "7a2628da-4879-4f82-a7d3-7b1c00db50a5",
"name": "positive",
"type": "CONDITIONING",
"linkIds": [
14
],
"pos": {
"0": 581.59912109375,
"1": 459.13336181640625
}
},
{
"id": "651cf4ad-e8bf-47f6-b181-8f8aeacd6669",
"name": "negative",
"type": "CONDITIONING",
"linkIds": [
15
],
"pos": {
"0": 581.59912109375,
"1": 479.13336181640625
}
},
{
"id": "c41765ea-61ef-4a77-8cc6-74113903078f",
"name": "latent_image",
"type": "LATENT",
"linkIds": [
16
],
"pos": {
"0": 581.59912109375,
"1": 499.13336181640625
}
}
],
"outputs": [
{
"id": "55dd1505-12bd-4cb4-8e75-031a97bb4387",
"name": "CONDITIONING",
"type": "CONDITIONING",
"linkIds": [
12
],
"pos": {
"0": 1141.59912109375,
"1": 399.13336181640625
}
}
],
"widgets": [],
"nodes": [
{
"id": 10,
"type": "CLIPTextEncode",
"pos": [
661.59912109375,
314.13336181640625
],
"size": [
400,
200
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"localized_name": "clip",
"name": "clip",
"type": "CLIP",
"link": null
},
{
"localized_name": "text",
"name": "text",
"type": "STRING",
"widget": {
"name": "text"
},
"link": 10
}
],
"outputs": [
{
"localized_name": "CONDITIONING",
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": null
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
""
]
},
{
"id": 11,
"type": "CLIPTextEncode",
"pos": [
668.755859375,
571.7766723632812
],
"size": [
400,
200
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"localized_name": "clip",
"name": "clip",
"type": "CLIP",
"link": null
},
{
"localized_name": "text",
"name": "text",
"type": "STRING",
"widget": {
"name": "text"
},
"link": 11
}
],
"outputs": [
{
"localized_name": "CONDITIONING",
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
12
]
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
""
]
},
{
"id": 12,
"type": "KSampler",
"pos": [
671.7379760742188,
1.621593713760376
],
"size": [
270,
262
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
{
"localized_name": "model",
"name": "model",
"type": "MODEL",
"link": 13
},
{
"localized_name": "positive",
"name": "positive",
"type": "CONDITIONING",
"link": 14
},
{
"localized_name": "negative",
"name": "negative",
"type": "CONDITIONING",
"link": 15
},
{
"localized_name": "latent_image",
"name": "latent_image",
"type": "LATENT",
"link": 16
}
],
"outputs": [
{
"localized_name": "LATENT",
"name": "LATENT",
"type": "LATENT",
"links": null
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
0,
"randomize",
20,
8,
"euler",
"simple",
1
]
}
],
"groups": [],
"links": [
{
"id": 10,
"origin_id": -10,
"origin_slot": 0,
"target_id": 10,
"target_slot": 1,
"type": "STRING"
},
{
"id": 11,
"origin_id": -10,
"origin_slot": 1,
"target_id": 11,
"target_slot": 1,
"type": "STRING"
},
{
"id": 12,
"origin_id": 11,
"origin_slot": 0,
"target_id": -20,
"target_slot": 0,
"type": "CONDITIONING"
},
{
"id": 13,
"origin_id": -10,
"origin_slot": 2,
"target_id": 12,
"target_slot": 0,
"type": "MODEL"
},
{
"id": 14,
"origin_id": -10,
"origin_slot": 3,
"target_id": 12,
"target_slot": 1,
"type": "CONDITIONING"
},
{
"id": 15,
"origin_id": -10,
"origin_slot": 4,
"target_id": 12,
"target_slot": 2,
"type": "CONDITIONING"
},
{
"id": 16,
"origin_id": -10,
"origin_slot": 5,
"target_id": 12,
"target_slot": 3,
"type": "LATENT"
}
],
"extra": {}
}
]
},
"config": {},
"extra": {
"ds": {
"scale": 0.9581355200690549,
"offset": [
184.687451089395,
80.38288288288285
]
},
"frontendVersion": "1.24.1"
},
"version": 0.4
}

View File

@@ -1,341 +0,0 @@
{
"id": "c4a254bb-935e-4013-b380-5e36954de4b0",
"revision": 0,
"last_node_id": 11,
"last_link_id": 9,
"nodes": [
{
"id": 11,
"type": "422723e8-4bf6-438c-823f-881ca81acead",
"pos": [
400,
300
],
"size": [
210,
168
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": null
},
{
"name": "model",
"type": "MODEL",
"link": null
},
{
"name": "positive",
"type": "CONDITIONING",
"link": null
},
{
"name": "negative",
"type": "CONDITIONING",
"link": null
},
{
"name": "latent_image",
"type": "LATENT",
"link": null
}
],
"outputs": [],
"properties": {},
"widgets_values": [
""
]
}
],
"links": [],
"groups": [],
"definitions": {
"subgraphs": [
{
"id": "422723e8-4bf6-438c-823f-881ca81acead",
"version": 1,
"state": {
"lastGroupId": 0,
"lastNodeId": 11,
"lastLinkId": 15,
"lastRerouteId": 0
},
"revision": 0,
"config": {},
"name": "New Subgraph",
"inputNode": {
"id": -10,
"bounding": [
481.59912109375,
379.13336181640625,
120,
160
]
},
"outputNode": {
"id": -20,
"bounding": [
1121.59912109375,
379.13336181640625,
120,
40
]
},
"inputs": [
{
"id": "0f07c10e-5705-4764-9b24-b69606c6dbcc",
"name": "text",
"type": "STRING",
"linkIds": [
10
],
"pos": {
"0": 581.59912109375,
"1": 399.13336181640625
}
},
{
"id": "214a5060-24dd-4299-ab78-8027dc5b9c59",
"name": "clip",
"type": "CLIP",
"linkIds": [
11
],
"pos": {
"0": 581.59912109375,
"1": 419.13336181640625
}
},
{
"id": "8ab94c5d-e7df-433c-9177-482a32340552",
"name": "model",
"type": "MODEL",
"linkIds": [
12
],
"pos": {
"0": 581.59912109375,
"1": 439.13336181640625
}
},
{
"id": "8a4cd719-8c67-473b-9b44-ac0582d02641",
"name": "positive",
"type": "CONDITIONING",
"linkIds": [
13
],
"pos": {
"0": 581.59912109375,
"1": 459.13336181640625
}
},
{
"id": "a78d6b3a-ad40-4300-b0a5-2cdbdb8dc135",
"name": "negative",
"type": "CONDITIONING",
"linkIds": [
14
],
"pos": {
"0": 581.59912109375,
"1": 479.13336181640625
}
},
{
"id": "4c7abe0c-902d-49ef-a5b0-cbf02b50b693",
"name": "latent_image",
"type": "LATENT",
"linkIds": [
15
],
"pos": {
"0": 581.59912109375,
"1": 499.13336181640625
}
}
],
"outputs": [],
"widgets": [],
"nodes": [
{
"id": 10,
"type": "CLIPTextEncode",
"pos": [
661.59912109375,
314.13336181640625
],
"size": [
400,
200
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"localized_name": "clip",
"name": "clip",
"type": "CLIP",
"link": 11
},
{
"localized_name": "text",
"name": "text",
"type": "STRING",
"widget": {
"name": "text"
},
"link": 10
}
],
"outputs": [
{
"localized_name": "CONDITIONING",
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": null
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
""
]
},
{
"id": 11,
"type": "KSampler",
"pos": [
674.1234741210938,
570.5839233398438
],
"size": [
270,
262
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
{
"localized_name": "model",
"name": "model",
"type": "MODEL",
"link": 12
},
{
"localized_name": "positive",
"name": "positive",
"type": "CONDITIONING",
"link": 13
},
{
"localized_name": "negative",
"name": "negative",
"type": "CONDITIONING",
"link": 14
},
{
"localized_name": "latent_image",
"name": "latent_image",
"type": "LATENT",
"link": 15
}
],
"outputs": [
{
"localized_name": "LATENT",
"name": "LATENT",
"type": "LATENT",
"links": null
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
0,
"randomize",
20,
8,
"euler",
"simple",
1
]
}
],
"groups": [],
"links": [
{
"id": 10,
"origin_id": -10,
"origin_slot": 0,
"target_id": 10,
"target_slot": 1,
"type": "STRING"
},
{
"id": 11,
"origin_id": -10,
"origin_slot": 1,
"target_id": 10,
"target_slot": 0,
"type": "CLIP"
},
{
"id": 12,
"origin_id": -10,
"origin_slot": 2,
"target_id": 11,
"target_slot": 0,
"type": "MODEL"
},
{
"id": 13,
"origin_id": -10,
"origin_slot": 3,
"target_id": 11,
"target_slot": 1,
"type": "CONDITIONING"
},
{
"id": 14,
"origin_id": -10,
"origin_slot": 4,
"target_id": 11,
"target_slot": 2,
"type": "CONDITIONING"
},
{
"id": 15,
"origin_id": -10,
"origin_slot": 5,
"target_id": 11,
"target_slot": 3,
"type": "LATENT"
}
],
"extra": {}
}
]
},
"config": {},
"extra": {
"ds": {
"scale": 0.9581355200690549,
"offset": [
258.6405769416877,
147.17927927927929
]
},
"frontendVersion": "1.24.1"
},
"version": 0.4
}

View File

@@ -1,153 +0,0 @@
{
"id": "c4a254bb-935e-4013-b380-5e36954de4b0",
"revision": 0,
"last_node_id": 11,
"last_link_id": 9,
"nodes": [
{
"id": 11,
"type": "422723e8-4bf6-438c-823f-881ca81acead",
"pos": [
791.59912109375,
386.13336181640625
],
"size": [
140,
26
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": null
}
],
"outputs": [],
"properties": {},
"widgets_values": []
}
],
"links": [],
"groups": [],
"definitions": {
"subgraphs": [
{
"id": "422723e8-4bf6-438c-823f-881ca81acead",
"version": 1,
"state": {
"lastGroupId": 0,
"lastNodeId": 10,
"lastLinkId": 10,
"lastRerouteId": 0
},
"revision": 0,
"config": {},
"name": "New Subgraph",
"inputNode": {
"id": -10,
"bounding": [
481.59912109375,
379.13336181640625,
120,
60
]
},
"outputNode": {
"id": -20,
"bounding": [
1121.59912109375,
379.13336181640625,
120,
40
]
},
"inputs": [
{
"id": "79e69fca-ad12-499b-8d9b-9f1656b85354",
"name": "clip",
"type": "CLIP",
"linkIds": [
10
],
"pos": {
"0": 581.59912109375,
"1": 399.13336181640625
}
}
],
"outputs": [],
"widgets": [],
"nodes": [
{
"id": 10,
"type": "CLIPTextEncode",
"pos": [
661.59912109375,
314.13336181640625
],
"size": [
400,
200
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
{
"localized_name": "clip",
"name": "clip",
"type": "CLIP",
"link": 10
}
],
"outputs": [
{
"localized_name": "CONDITIONING",
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": null
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
""
]
}
],
"groups": [],
"links": [
{
"id": 10,
"origin_id": -10,
"origin_slot": 0,
"target_id": 10,
"target_slot": 0,
"type": "CLIP"
}
],
"extra": {}
}
]
},
"config": {},
"extra": {
"frontendVersion": "1.24.1",
"VHS_latentpreview": false,
"VHS_latentpreviewrate": 0,
"VHS_MetadataImage": true,
"VHS_KeepIntermediate": true,
"ds": {
"scale": 0.9581355200690549,
"offset": [
258.6405769416877,
147.17927927927929
]
}
},
"version": 0.4
}

View File

@@ -0,0 +1,6 @@
[
{
"name": "vintageClipboard Template",
"data": "{\"nodes\":[{\"id\":-1,\"type\":\"CheckpointLoaderSimple\",\"pos\":[26,474],\"size\":[315,98],\"flags\":{},\"order\":1,\"mode\":0,\"inputs\":[],\"outputs\":[{\"name\":\"MODEL\",\"type\":\"MODEL\",\"links\":[],\"slot_index\":0},{\"name\":\"CLIP\",\"type\":\"CLIP\",\"links\":[],\"slot_index\":1},{\"name\":\"VAE\",\"type\":\"VAE\",\"links\":[],\"slot_index\":2}],\"properties\":{\"Node name for S&R\":\"CheckpointLoaderSimple\"},\"widgets_values\":[\"v1-5-pruned-emaonly.ckpt\"]},{\"id\":-1,\"type\":\"CLIPTextEncode\",\"pos\":[415,186],\"size\":[422.84503173828125,164.31304931640625],\"flags\":{},\"order\":2,\"mode\":0,\"inputs\":[{\"name\":\"clip\",\"type\":\"CLIP\",\"link\":null}],\"outputs\":[{\"name\":\"CONDITIONING\",\"type\":\"CONDITIONING\",\"links\":[],\"slot_index\":0}],\"properties\":{\"Node name for S&R\":\"CLIPTextEncode\"},\"widgets_values\":[\"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,\"]},{\"id\":-1,\"type\":\"CLIPTextEncode\",\"pos\":[413,389],\"size\":[425.27801513671875,180.6060791015625],\"flags\":{},\"order\":3,\"mode\":0,\"inputs\":[{\"name\":\"clip\",\"type\":\"CLIP\",\"link\":null}],\"outputs\":[{\"name\":\"CONDITIONING\",\"type\":\"CONDITIONING\",\"links\":[],\"slot_index\":0}],\"properties\":{\"Node name for S&R\":\"CLIPTextEncode\"},\"widgets_values\":[\"text, watermark\"]}],\"links\":[[0,1,1,0,4],[0,1,2,0,4]]}"
}
]

View File

@@ -1,41 +0,0 @@
{
"last_node_id": 11,
"last_link_id": 9,
"nodes": [
{
"id": 11,
"type": "DevToolsNodeWithBooleanInput",
"pos": [
0,
30
],
"size": [
315,
58
],
"flags": {
"collapsed": false
},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [],
"properties": {
"Node name for S&R": "DevToolsNodeWithBooleanInput"
},
"widgets_values": [
false
]
}
],
"links": [],
"groups": [],
"config": {},
"extra": {
"ds": {
"offset": [0, 0],
"scale": 1
}
},
"version": 0.4
}

View File

@@ -1,11 +0,0 @@
{
"8": {
"inputs": {
"image": "animated_web.webp"
},
"class_type": "DevToolsLoadAnimatedImageTest",
"_meta": {
"title": "Load Animated Image"
}
}
}

View File

@@ -1,37 +0,0 @@
{
"last_node_id": 1,
"last_link_id": 0,
"nodes": [
{
"id": 1,
"type": "LoadAudio",
"pos": [41.5296516418457, 16.930862426757812],
"size": [315, 82],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "AUDIO",
"type": "AUDIO",
"links": null
}
],
"properties": {
"Node name for S&R": "LoadAudio"
},
"widgets_values": [null, ""]
}
],
"links": [],
"groups": [],
"config": {},
"extra": {
"ds": {
"offset": [0, 0],
"scale": 1
}
},
"version": 0.4
}

View File

@@ -1,51 +0,0 @@
{
"last_node_id": 10,
"last_link_id": 10,
"nodes": [
{
"id": 10,
"type": "LoadImage",
"pos": [
50,
50
],
"size": [
315,
314
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": null
},
{
"name": "MASK",
"type": "MASK",
"links": null
}
],
"properties": {
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"example.png",
"image"
]
}
],
"links": [],
"groups": [],
"config": {},
"extra": {
"ds": {
"offset": [0, 0],
"scale": 1
}
},
"version": 0.4
}

View File

@@ -1,64 +0,0 @@
{
"id": "3f1fcbf9-f9de-4935-8fad-401813f61b13",
"revision": 0,
"last_node_id": 10,
"last_link_id": 4,
"nodes": [
{
"id": 9,
"type": "SaveAnimatedWEBP",
"pos": [336, 104],
"size": [210, 368],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 4
}
],
"outputs": [],
"properties": {},
"widgets_values": ["ComfyUI", 6, true, 80, "default"]
},
{
"id": 10,
"type": "DevToolsLoadAnimatedImageTest",
"pos": [64, 104],
"size": [210, 316],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [4]
},
{
"name": "MASK",
"type": "MASK",
"links": null
}
],
"properties": {
"Node name for S&R": "DevToolsLoadAnimatedImageTest"
},
"widgets_values": ["animated_web.webp", "image"]
}
],
"links": [[4, 10, 0, 9, 0, "IMAGE"]],
"groups": [],
"config": {},
"extra": {
"frontendVersion": "1.17.0",
"ds": {
"offset": [0, 0],
"scale": 1
}
},
"version": 0.4
}

View File

@@ -1,43 +0,0 @@
{
"last_node_id": 10,
"last_link_id": 9,
"nodes": [
{
"id": 10,
"type": "DevToolsNodeWithSeedInput",
"pos": [
20,
50
],
"size": [
315,
82
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [],
"outputs": [],
"properties": {
"Node name for S&R": "DevToolsNodeWithSeedInput"
},
"widgets_values": [
0,
"randomize"
]
}
],
"links": [],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1,
"offset": [
0,
0
]
}
},
"version": 0.4
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 75 KiB

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