Compare commits

..

1 Commits

Author SHA1 Message Date
Jin Yi
1474f9bbfa feat: Add getImportFailInfoBulk function to comfyManagerService
- Added new bulk API endpoint route for v2/customnode/import_fail_info_bulk
- Implemented getImportFailInfoBulk function with proper validation and error handling
- Added temporary type definitions (to be replaced with generated types after ComfyUI-Manager PR merge)
- Includes JSDoc documentation and follows existing service patterns
- Validates that either cnr_ids or urls arrays are provided

This prepares the frontend for the new bulk import failure info API endpoint.
Once the ComfyUI-Manager PR is merged and types are generated, the temporary types
will be replaced with the auto-generated ones from the OpenAPI specification.
2025-07-31 15:42:32 +09:00
309 changed files with 794 additions and 48049 deletions

View File

@@ -171,7 +171,53 @@ echo "Last stable release: $LAST_STABLE"
### Step 7: Analyze Dependency Updates
1. **Check significant dependency updates:**
1. **Check for dependency version changes:**
```bash
# Compare package.json between versions to detect dependency updates
PREV_PACKAGE_JSON=$(git show ${BASE_TAG}:package.json 2>/dev/null || echo '{}')
CURRENT_PACKAGE_JSON=$(cat package.json)
# Extract litegraph versions
PREV_LITEGRAPH=$(echo "$PREV_PACKAGE_JSON" | grep -o '"@comfyorg/litegraph": "[^"]*"' | grep -o '[0-9][^"]*' || echo "not found")
CURRENT_LITEGRAPH=$(echo "$CURRENT_PACKAGE_JSON" | grep -o '"@comfyorg/litegraph": "[^"]*"' | grep -o '[0-9][^"]*' || echo "not found")
echo "Litegraph version change: ${PREV_LITEGRAPH} → ${CURRENT_LITEGRAPH}"
```
2. **Generate litegraph changelog if version changed:**
```bash
if [ "$PREV_LITEGRAPH" != "$CURRENT_LITEGRAPH" ] && [ "$PREV_LITEGRAPH" != "not found" ]; then
echo "📦 Fetching litegraph changes between v${PREV_LITEGRAPH} and v${CURRENT_LITEGRAPH}..."
# Clone or update litegraph repo for changelog analysis
if [ ! -d ".temp-litegraph" ]; then
git clone https://github.com/comfyanonymous/litegraph.js.git .temp-litegraph
else
cd .temp-litegraph && git fetch --all && cd ..
fi
# Get litegraph changelog between versions
LITEGRAPH_CHANGES=$(cd .temp-litegraph && git log v${PREV_LITEGRAPH}..v${CURRENT_LITEGRAPH} --oneline --no-merges 2>/dev/null || \
git log --oneline --no-merges --since="$(git log -1 --format=%ci ${BASE_TAG})" --until="$(git log -1 --format=%ci HEAD)" 2>/dev/null || \
echo "Unable to fetch litegraph changes")
# Categorize litegraph changes
LITEGRAPH_FEATURES=$(echo "$LITEGRAPH_CHANGES" | grep -iE "(feat|feature|add)" || echo "")
LITEGRAPH_FIXES=$(echo "$LITEGRAPH_CHANGES" | grep -iE "(fix|bug)" || echo "")
LITEGRAPH_BREAKING=$(echo "$LITEGRAPH_CHANGES" | grep -iE "(break|breaking)" || echo "")
LITEGRAPH_OTHER=$(echo "$LITEGRAPH_CHANGES" | grep -viE "(feat|feature|add|fix|bug|break|breaking)" || echo "")
# Clean up temp directory
rm -rf .temp-litegraph
echo "✅ Litegraph changelog extracted"
else
echo " No litegraph version change detected"
LITEGRAPH_CHANGES=""
fi
```
3. **Check other significant dependency updates:**
```bash
# Extract all dependency changes for major version bumps
OTHER_DEP_CHANGES=""
@@ -348,14 +394,6 @@ echo "Workflow triggered. Waiting for PR creation..."
sleep 10
gh run list --workflow=release.yaml --limit=1
```
4. **For Minor/Major Version Releases**: The create-release-candidate-branch workflow will automatically:
- Create a `core/x.yy` branch for the PREVIOUS minor version
- Apply branch protection rules
- Document the feature freeze policy
```bash
# Monitor branch creation (for minor/major releases)
gh run list --workflow=create-release-candidate-branch.yaml --limit=1
```
4. If workflow didn't trigger due to [skip ci]:
```bash
echo "ERROR: Release workflow didn't trigger!"

View File

@@ -1,21 +0,0 @@
---
description: Creating unit tests
globs:
alwaysApply: false
---
# Creating unit tests
- This project uses `vitest` for unit testing
- Tests are stored in the `test/` directory
- Tests should be cross-platform compatible; able to run on Windows, macOS, and linux
- e.g. the use of `path.resolve`, or `path.join` and `path.sep` to ensure that tests work the same on all platforms
- Tests should be mocked properly
- Mocks should be cleanly written and easy to understand
- Mocks should be re-usable where possible
## Unit test style
- Prefer the use of `test.extend` over loose variables
- To achieve this, import `test as baseTest` from `vitest`
- Never use `it`; `test` should be used in place of this

View File

@@ -1,106 +1,99 @@
name: Bug Report
description: 'Report something that is not working correctly'
description: 'Something is not behaving as expected.'
title: '[Bug]: '
labels: ['Potential Bug']
type: Bug
body:
- type: checkboxes
attributes:
label: Prerequisites
options:
- label: I am running the latest version of ComfyUI
required: true
- label: I have searched existing issues to make sure this isn't a duplicate
required: true
- label: I have tested with all custom nodes disabled ([see how](https://docs.comfy.org/troubleshooting/custom-node-issues#step-1%3A-test-with-all-custom-nodes-disabled))
required: true
- type: textarea
id: description
attributes:
label: What happened?
description: A clear and concise description of the bug. Include screenshots or videos if helpful.
placeholder: |
Example: "When I connect a VAE Decode node to a KSampler, the connection line appears but the workflow fails to execute with an error message..."
validations:
required: true
- type: textarea
id: reproduce
attributes:
label: Steps to Reproduce
description: How can we reproduce this issue? Please attach your workflow (JSON or PNG).
placeholder: |
1. Add a KSampler node
2. Connect it to...
3. Click Queue Prompt
4. See error
value: |
1.
2.
3.
validations:
required: true
- type: dropdown
id: severity
attributes:
label: How is this affecting you?
options:
- Crashes ComfyUI completely
- Workflow won't execute
- Feature doesn't work as expected
- Visual/UI issue only
- Minor inconvenience
validations:
required: true
- type: input
id: version
attributes:
label: ComfyUI Frontend Version
description: Found in Settings > About (e.g., "1.3.45")
placeholder: "1.3.45"
validations:
required: true
- type: dropdown
id: browser
attributes:
label: Browser
description: Which browser are you using?
options:
- Chrome/Chromium
- Firefox
- Safari
- Edge
- Other
validations:
required: true
- type: markdown
attributes:
value: |
## Additional Information (Optional)
*The following fields help us debug complex issues but are not required for most bug reports.*
Before submitting a **Bug Report**, please ensure the following:
- **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
- type: textarea
id: console-errors
attributes:
label: Console Errors
description: If you see red error messages in the browser console (F12), paste them here
render: javascript
label: Frontend Version
description: |
What is the frontend version you are using? You can check this in the settings dialog.
- type: textarea
id: logs
attributes:
label: Logs
description: If relevant, paste any terminal/server logs here
render: shell
<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>
validations:
required: true
- type: textarea
id: additional
attributes:
label: Additional Context
description: Any other information that might help (OS, GPU, specific nodes involved, etc.)
label: Expected Behavior
description: 'What you expected to happen.'
validations:
required: true
- type: textarea
attributes:
label: Actual Behavior
description: 'What actually happened. Please include a screenshot / video clip of the issue if possible.'
validations:
required: true
- type: textarea
attributes:
label: Steps to Reproduce
description: "Describe how to reproduce the issue. Please be sure to attach a workflow JSON or PNG, ideally one that doesn't require custom nodes to test. If the bug open happens when certain custom nodes are used, most likely that custom node is what has the bug rather than ComfyUI, in which case it should be reported to the node's author."
validations:
required: true
- type: textarea
attributes:
label: Debug Logs
description: 'Please copy the output from your terminal logs here.'
render: powershell
validations:
required: true
- type: textarea
attributes:
label: Browser Logs
description: 'Please copy the output from your browser logs here. You can access this by pressing F12 to toggle the developer tools, then navigating to the Console tab.'
validations:
required: true
- type: textarea
attributes:
label: Setting JSON
description: 'Please upload the setting file here. The setting file is located at `user/default/comfy.settings.json`'
validations:
required: true
- type: dropdown
id: browsers
attributes:
label: What browsers do you use to access the UI ?
multiple: true
options:
- Mozilla Firefox
- Google Chrome
- Brave
- Apple Safari
- Microsoft Edge
- Android
- iOS
- 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...'
validations:
required: false

View File

@@ -1,6 +1,6 @@
name: Feature Request
description: Report a problem or limitation you're experiencing
title: '[Feature]: '
description: Suggest an idea for this project
title: '[Feature Request]: '
labels: ['enhancement']
type: Feature
@@ -8,74 +8,34 @@ body:
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an issue already exists for the problem you're experiencing, and that it's not addressed in a recent build/commit.
description: Please search to see if an issue already exists for the feature you want, and that it's not implemented in a recent build/commit.
options:
- label: I have searched the existing issues and checked the recent builds/commits
required: true
- type: markdown
attributes:
value: |
*Please focus on describing the problem you're experiencing rather than proposing specific solutions. This helps us design the best possible solution for you and other users.*
*Please fill this form with as much information as possible, provide screenshots and/or illustrations of the feature if possible*
- type: textarea
id: problem
id: feature
attributes:
label: What problem are you experiencing?
description: Describe the issue or limitation you're facing in your workflow
placeholder: |
Example: "I frequently lose work when switching between different projects because there's no way to save my current workspace state"
NOT: "Add a save button that exports the workspace"
label: What would your feature do ?
description: Tell us about your feature in a very clear and simple way, and what problem it would solve
validations:
required: true
- type: textarea
id: context
id: workflow
attributes:
label: When does this problem occur?
description: Describe the specific situations or workflows where you encounter this issue
placeholder: |
- When working with large node graphs...
- During batch processing workflows...
- While collaborating with team members...
validations:
required: true
- type: dropdown
id: frequency
attributes:
label: How often do you encounter this problem?
options:
- Multiple times per day
- Daily
- Several times per week
- Weekly
- Occasionally
- Rarely
validations:
required: true
- type: dropdown
id: impact
attributes:
label: How much does this problem affect your workflow?
description: Help us understand the severity of this issue for you
options:
- Blocks me from completing tasks
- Significantly slows down my work
- Causes moderate inconvenience
- Minor annoyance
label: Proposed workflow
description: Please provide us with step by step information on how you'd like the feature to be accessed and used
value: |
1. Go to ....
2. Press ....
3. ...
validations:
required: true
- type: textarea
id: workaround
id: misc
attributes:
label: Current workarounds
description: How do you currently deal with this problem, if at all?
placeholder: |
Example: "I manually export and reimport nodes between projects, which takes 10-15 minutes each time"
- type: textarea
id: ideas
attributes:
label: Ideas for solutions (Optional)
description: If you have thoughts on potential solutions, feel free to share them here. However, we'll explore all possible options to find the best approach.
- type: textarea
id: additional
attributes:
label: Additional context
description: Add any other context, screenshots, or examples that help illustrate the problem.
label: Additional information
description: Add any other context or screenshots about the feature request here.

View File

@@ -1,214 +0,0 @@
name: Create Release Branch
on:
pull_request:
types: [closed]
branches: [main]
paths:
- 'package.json'
jobs:
create-release-branch:
runs-on: ubuntu-latest
if: >
github.event.pull_request.merged == true &&
contains(github.event.pull_request.labels.*.name, 'Release')
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.PR_GH_TOKEN || secrets.GITHUB_TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 'lts/*'
- name: Check version bump type
id: check_version
run: |
# Get current version from main
CURRENT_VERSION=$(node -p "require('./package.json').version")
# Remove 'v' prefix if present (shouldn't be in package.json, but defensive)
CURRENT_VERSION=${CURRENT_VERSION#v}
echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
# Validate version format
if ! [[ "$CURRENT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then
echo "ERROR: Invalid version format: $CURRENT_VERSION"
exit 1
fi
# Extract major and minor versions
MAJOR=$(echo $CURRENT_VERSION | cut -d. -f1)
MINOR=$(echo $CURRENT_VERSION | cut -d. -f2)
PATCH=$(echo $CURRENT_VERSION | cut -d. -f3 | cut -d- -f1)
echo "major=$MAJOR" >> $GITHUB_OUTPUT
echo "minor=$MINOR" >> $GITHUB_OUTPUT
echo "patch=$PATCH" >> $GITHUB_OUTPUT
# Get previous version from the commit before the merge
git checkout HEAD^1
PREV_VERSION=$(node -p "require('./package.json').version" 2>/dev/null || echo "0.0.0")
# Remove 'v' prefix if present
PREV_VERSION=${PREV_VERSION#v}
# Validate previous version format
if ! [[ "$PREV_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then
echo "WARNING: Invalid previous version format: $PREV_VERSION, using 0.0.0"
PREV_VERSION="0.0.0"
fi
PREV_MINOR=$(echo $PREV_VERSION | cut -d. -f2)
echo "prev_version=$PREV_VERSION" >> $GITHUB_OUTPUT
echo "prev_minor=$PREV_MINOR" >> $GITHUB_OUTPUT
# Get previous major version for comparison
PREV_MAJOR=$(echo $PREV_VERSION | cut -d. -f1)
# Check if current version is a pre-release
if [[ "$CURRENT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+- ]]; then
IS_PRERELEASE=true
else
IS_PRERELEASE=false
fi
# Check if this was a minor version bump or major version bump
# But skip if it's a pre-release version
if [[ "$IS_PRERELEASE" == "true" ]]; then
echo "is_minor_bump=false" >> $GITHUB_OUTPUT
echo "reason=prerelease version" >> $GITHUB_OUTPUT
elif [[ "$MAJOR" -gt "$PREV_MAJOR" && "$MINOR" == "0" && "$PATCH" == "0" ]]; then
# Major version bump (e.g., 1.99.x → 2.0.0)
echo "is_minor_bump=true" >> $GITHUB_OUTPUT
BRANCH_NAME="core/${PREV_MAJOR}.${PREV_MINOR}"
echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT
elif [[ "$MAJOR" == "$PREV_MAJOR" && "$MINOR" -gt "$PREV_MINOR" && "$PATCH" == "0" ]]; then
# Minor version bump (e.g., 1.23.x → 1.24.0)
echo "is_minor_bump=true" >> $GITHUB_OUTPUT
BRANCH_NAME="core/${MAJOR}.${PREV_MINOR}"
echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT
else
echo "is_minor_bump=false" >> $GITHUB_OUTPUT
fi
# Return to main branch
git checkout main
- name: Create release branch
if: steps.check_version.outputs.is_minor_bump == 'true'
run: |
BRANCH_NAME="${{ steps.check_version.outputs.branch_name }}"
# Check if branch already exists
if git ls-remote --heads origin "$BRANCH_NAME" | grep -q "$BRANCH_NAME"; then
echo "⚠️ Branch $BRANCH_NAME already exists, skipping creation"
echo "branch_exists=true" >> $GITHUB_ENV
exit 0
else
echo "branch_exists=false" >> $GITHUB_ENV
fi
# Create branch from the commit BEFORE the version bump
# This ensures the release branch has the previous minor version
git checkout -b "$BRANCH_NAME" HEAD^1
# Push the new branch
git push origin "$BRANCH_NAME"
echo "✅ Created release branch: $BRANCH_NAME"
echo "This branch is now in feature freeze and will only receive:"
echo "- Bug fixes"
echo "- Critical security patches"
echo "- Documentation updates"
- name: Create branch protection rules
if: steps.check_version.outputs.is_minor_bump == 'true' && env.branch_exists != 'true'
env:
GITHUB_TOKEN: ${{ secrets.PR_GH_TOKEN || secrets.GITHUB_TOKEN }}
run: |
BRANCH_NAME="${{ steps.check_version.outputs.branch_name }}"
# Create branch protection using GitHub API
echo "Setting up branch protection for $BRANCH_NAME..."
RESPONSE=$(curl -s -w "\n%{http_code}" -X PUT \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/repos/${{ github.repository }}/branches/$BRANCH_NAME/protection" \
-d '{
"required_status_checks": {
"strict": true,
"contexts": ["build", "test"]
},
"enforce_admins": false,
"required_pull_request_reviews": {
"required_approving_review_count": 1,
"dismiss_stale_reviews": true
},
"restrictions": null,
"allow_force_pushes": false,
"allow_deletions": false
}')
HTTP_CODE=$(echo "$RESPONSE" | tail -n 1)
BODY=$(echo "$RESPONSE" | sed '$d')
if [[ "$HTTP_CODE" -eq 200 ]] || [[ "$HTTP_CODE" -eq 201 ]]; then
echo "✅ Branch protection successfully applied"
else
echo "⚠️ Failed to apply branch protection (HTTP $HTTP_CODE)"
echo "Response: $BODY"
# Don't fail the workflow, just warn
fi
- name: Post summary
if: steps.check_version.outputs.is_minor_bump == 'true'
run: |
BRANCH_NAME="${{ steps.check_version.outputs.branch_name }}"
PREV_VERSION="${{ steps.check_version.outputs.prev_version }}"
CURRENT_VERSION="${{ steps.check_version.outputs.current_version }}"
if [[ "${{ env.branch_exists }}" == "true" ]]; then
cat >> $GITHUB_STEP_SUMMARY << EOF
## 🌿 Release Branch Already Exists
The release branch for the previous minor version already exists:
EOF
else
cat >> $GITHUB_STEP_SUMMARY << EOF
## 🌿 Release Branch Created
A new release branch has been created for the previous minor version:
EOF
fi
cat >> $GITHUB_STEP_SUMMARY << EOF
- **Branch**: \`$BRANCH_NAME\`
- **Version**: \`$PREV_VERSION\` (feature frozen)
- **Main branch**: \`$CURRENT_VERSION\` (active development)
### Branch Policy
The \`$BRANCH_NAME\` branch is now in **feature freeze** and will only accept:
- 🐛 Bug fixes
- 🔒 Security patches
- 📚 Documentation updates
All new features should continue to be developed against \`main\`.
### Backporting Changes
To backport a fix to this release branch:
1. Create your fix on \`main\` first
2. Cherry-pick to \`$BRANCH_NAME\`
3. Create a PR targeting \`$BRANCH_NAME\`
4. Use the \`Release\` label on the PR
EOF

43
.github/workflows/update-litegraph.yaml vendored Normal file
View File

@@ -0,0 +1,43 @@
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

@@ -3,11 +3,6 @@ name: Update ComfyUI-Manager API Types
on:
# Manual trigger
workflow_dispatch:
inputs:
target_branch:
description: 'Target branch for the PR'
required: true
default: 'main'
jobs:
update-manager-types:
@@ -90,7 +85,7 @@ jobs:
These types are automatically generated using openapi-typescript.
branch: update-manager-types-${{ steps.manager-info.outputs.commit }}
base: ${{ inputs.target_branch }}
base: main
labels: Manager
delete-branch: true
add-paths: |

2
.gitignore vendored
View File

@@ -11,8 +11,6 @@ node_modules
dist
dist-ssr
*.local
# Claude configuration
.claude/*.local.json
# Editor directories and files
.vscode/*

13
.husky/pre-commit Executable file → Normal file
View File

@@ -1,4 +1,9 @@
#!/usr/bin/env bash
npx lint-staged
npx tsx scripts/check-unused-i18n-keys.ts
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

@@ -3,10 +3,6 @@
"playwright": {
"command": "npx",
"args": ["-y", "@executeautomation/playwright-mcp-server"]
},
"context7": {
"command": "npx",
"args": ["-y", "@upstash/context7-mcp"]
}
}
}

119
CLAUDE.md
View File

@@ -1,61 +1,58 @@
# ComfyUI Frontend Project Guidelines
## Quick Commands
- `npm run`: See all available commands
- `npm run typecheck`: Type checking
- `npm run lint`: Linting
- `npm run format`: Prettier formatting
- `npm run test:component`: Run component tests with browser environment
- `npm run test:unit`: Run all unit tests
- `npm run test:unit -- tests-ui/tests/example.test.ts`: Run single test file
## Development Workflow
1. Make code changes
2. Run tests (see subdirectory CLAUDE.md files)
3. Run typecheck, lint, format
4. Check README updates
5. Consider docs.comfy.org updates
## Git Conventions
- Use [prefix] format: [feat], [bugfix], [docs]
- Add "Fixes #n" to PR descriptions
- Never mention Claude/AI in commits
## External Resources
- PrimeVue docs: <https://primevue.org>
- ComfyUI docs: <https://docs.comfy.org>
- Electron: <https://www.electronjs.org/docs/latest/>
- Wiki: <https://deepwiki.com/Comfy-Org/ComfyUI_frontend/1-overview>
## Project Philosophy
- Clean, stable public APIs
- Domain-driven design
- Thousands of users and extensions
- Prioritize clean interfaces that restrict extension access
## Repository Navigation
- Check README files in key folders (tests-ui, browser_tests, composables, etc.)
- Prefer running single tests for performance
- Use --help for unfamiliar CLI tools
## GitHub Integration
When referencing Comfy-Org repos:
1. Check for local copy
2. Use GitHub API for branches/PRs/metadata
3. Curl GitHub website if needed
## Common Pitfalls
- NEVER use `any` type - use proper TypeScript types
- NEVER use `as any` type assertions - fix the underlying type issue
- NEVER use `--no-verify` flag when committing
- NEVER delete or disable tests to make them pass
- NEVER circumvent quality checks
- 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

@@ -524,10 +524,6 @@ Here are some ways to get involved:
Have another idea? Drop into Discord or open an issue, and let's chat!
### Architecture Decision Records
We document significant architectural decisions using ADRs (Architecture Decision Records). See [docs/adr/](docs/adr/) for all ADRs and the template for creating new ones.
## Development
### Prerequisites & Technology Stack
@@ -698,15 +694,14 @@ For detailed instructions on adding and using custom icons, see [src/assets/icon
### litegraph.js
Since Aug 5, 2025, litegraph.js is now integrated directly into this repository. It was merged using git subtree to preserve the complete commit history ([PR #4667](https://github.com/Comfy-Org/ComfyUI_frontend/pull/4667), [ADR](docs/adr/0001-merge-litegraph-into-frontend.md)).
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.
#### Important Notes
#### Test litegraph.js changes
- **Issue References**: Commits from the original litegraph repository may contain issue/PR numbers (e.g., #4667) that refer to issues/PRs in the original litegraph.js repository, not this one.
- **File Paths**: When viewing historical commits, file paths may show the original structure before the subtree merge. In those cases, just consider the paths relative to the new litegraph folder.
- **Contributing**: All litegraph modifications should now be made directly in this repository.
- Run `npm link` in the local litegraph repo.
- Run `npm link @comfyorg/litegraph` in this repo.
The original litegraph repository (<https://github.com/Comfy-Org/litegraph.js>) is now archived.
This will replace the litegraph package in this repo with the local litegraph repo.
### i18n

View File

@@ -1,17 +0,0 @@
# E2E Testing Guidelines
## Browser Tests
- Test user workflows
- Use Playwright fixtures
- Follow naming conventions
## Best Practices
- Check assets/ for test data
- Prefer specific selectors
- Test across viewports
## Testing Process
After code changes:
1. Create browser tests as appropriate
2. Run tests until passing
3. Then run typecheck, lint, format

View File

@@ -1,182 +0,0 @@
{
"id": "test-missing-nodes-in-subgraph",
"revision": 0,
"last_node_id": 2,
"last_link_id": 0,
"nodes": [
{
"id": 1,
"type": "KSampler",
"pos": [100, 100],
"size": [270, 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
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": []
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [0, "randomize", 20, 8, "euler", "simple", 1]
},
{
"id": 2,
"type": "subgraph-with-missing-node",
"pos": [400, 100],
"size": [144, 46],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
{
"name": "input1",
"type": "CONDITIONING",
"link": null
}
],
"outputs": [
{
"name": "output1",
"type": "LATENT",
"links": null
}
],
"properties": {},
"widgets_values": []
}
],
"links": [],
"groups": [],
"definitions": {
"subgraphs": [
{
"id": "subgraph-with-missing-node",
"version": 1,
"state": {
"lastGroupId": 0,
"lastNodeId": 2,
"lastLinkId": 2,
"lastRerouteId": 0
},
"revision": 0,
"config": {},
"name": "Subgraph with Missing Node",
"inputNode": {
"id": -10,
"bounding": [100, 200, 120, 60]
},
"outputNode": {
"id": -20,
"bounding": [500, 200, 120, 60]
},
"inputs": [
{
"id": "input1-id",
"name": "input1",
"type": "CONDITIONING",
"linkIds": [1],
"pos": {
"0": 150,
"1": 220
}
}
],
"outputs": [
{
"id": "output1-id",
"name": "output1",
"type": "LATENT",
"linkIds": [2],
"pos": {
"0": 520,
"1": 220
}
}
],
"widgets": [],
"nodes": [
{
"id": 1,
"type": "MISSING_NODE_TYPE_IN_SUBGRAPH",
"pos": [250, 180],
"size": [200, 100],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
{
"name": "input",
"type": "CONDITIONING",
"link": 1
}
],
"outputs": [
{
"name": "output",
"type": "LATENT",
"links": [2]
}
],
"properties": {
"Node name for S&R": "MISSING_NODE_TYPE_IN_SUBGRAPH"
},
"widgets_values": ["some", "widget", "values"]
}
],
"links": [
{
"id": 1,
"origin_id": -10,
"origin_slot": 0,
"target_id": 1,
"target_slot": 0,
"type": "CONDITIONING"
},
{
"id": 2,
"origin_id": 1,
"origin_slot": 0,
"target_id": -20,
"target_slot": 0,
"type": "LATENT"
}
]
}
]
},
"config": {},
"extra": {
"ds": {
"scale": 1,
"offset": [0, 0]
}
},
"version": 0.4
}

View File

@@ -1,10 +1,10 @@
import type { LGraphNode } from '@comfyorg/litegraph'
import type { APIRequestContext, Locator, Page } from '@playwright/test'
import { expect } from '@playwright/test'
import { test as base } from '@playwright/test'
import dotenv from 'dotenv'
import * as fs from 'fs'
import type { LGraphNode } from '../../src/lib/litegraph/src/litegraph'
import type { NodeId } from '../../src/schemas/comfyWorkflowSchema'
import type { KeyCombo } from '../../src/schemas/keyBindingSchema'
import type { useWorkspaceStore } from '../../src/stores/workspaceStore'

View File

@@ -13,21 +13,6 @@ test.describe('Load workflow warning', () => {
const missingNodesWarning = comfyPage.page.locator('.comfy-missing-nodes')
await expect(missingNodesWarning).toBeVisible()
})
test('Should display a warning when loading a workflow with missing nodes in subgraphs', async ({
comfyPage
}) => {
await comfyPage.loadWorkflow('missing_nodes_in_subgraph')
// Wait for the element with the .comfy-missing-nodes selector to be visible
const missingNodesWarning = comfyPage.page.locator('.comfy-missing-nodes')
await expect(missingNodesWarning).toBeVisible()
// Verify the missing node text includes subgraph context
const warningText = await missingNodesWarning.textContent()
expect(warningText).toContain('MISSING_NODE_TYPE_IN_SUBGRAPH')
expect(warningText).toContain('in subgraph')
})
})
test('Does not report warning on undo/redo', async ({ comfyPage }) => {
@@ -384,7 +369,7 @@ test.describe('Signin dialog', () => {
await textBox.press('Control+c')
await comfyPage.page.evaluate(() => {
void window['app'].extensionManager.dialog.showSignInDialog()
window['app'].extensionManager.dialog.showSignInDialog()
})
const input = comfyPage.page.locator('#comfy-org-sign-in-password')

View File

@@ -1,65 +0,0 @@
# 1. Merge LiteGraph.js into ComfyUI Frontend
Date: 2025-08-05
## Status
Accepted
## Context
ComfyUI's frontend architecture currently depends on a forked version of litegraph.js maintained as a separate package (@comfyorg/litegraph). This separation has created several architectural and operational challenges:
**Architectural Issues:**
- The current split creates a distributed monolith where both packages handle rendering, user interactions, and data models without clear separation of responsibilities
- Both frontend and litegraph manipulate the same data structures, forcing tight coupling across the frontend's data model, views, and business logic
- The lack of clear boundaries prevents implementation of modern architectural patterns like MVC or event-sourcing
**Operational Issues:**
- ComfyUI is the only known user of the @comfyorg/litegraph fork
- Managing separate repositories significantly slows developer velocity due to coordination overhead
- Version mismatches between frontend and litegraph cause recurring issues
- No upstream contributions to consider (original litegraph.js is no longer maintained)
**Future Requirements:**
The following planned features are blocked by the current architecture:
- Multiplayer collaboration requiring CRDT-based state management
- Cloud-based backend support
- Alternative rendering backends
- Improved undo/redo system
- Clear API versioning and compatibility layers
## Decision
We will merge litegraph.js directly into the ComfyUI frontend repository using git subtree to preserve the complete commit history.
The merge will:
1. Move litegraph source to `src/lib/litegraph/`
2. Update all import paths from `@comfyorg/litegraph` to `@/lib/litegraph`
3. Remove the npm dependency on `@comfyorg/litegraph`
4. Preserve the full git history using subtree merge
This integration is the first step toward restructuring the application along clear Model-View-Controller boundaries, with state mutations going through a single CRDT-mediated access point.
## Consequences
### Positive
- **Enables architectural refactoring**: Direct integration allows restructuring along proper MVC boundaries
- **Unblocks new features**: Multiplayer, cloud features, and improved undo/redo can now be implemented
- **Faster development**: Eliminates overhead of coordinating changes across two tightly-coupled packages
- **Better developer experience**: No more version mismatch issues or cross-repository debugging
- **Simplified maintenance**: One less repository to maintain, release, and version
### Negative
- **Larger repository**: The frontend repository will increase in size
- **Loss of versioning**: No more semantic versioning for litegraph changes
- **Maintenance responsibility**: Must maintain litegraph code directly
- **Historical references**: Past commit messages may reference issues from the original litegraph repository
## Notes
- Git subtree was chosen over submodules to provide a cleaner developer experience
- The original litegraph repository will be archived after the merge
- Future litegraph improvements will be made directly in the frontend repository

View File

@@ -1,79 +0,0 @@
# Architecture Decision Records
This directory contains Architecture Decision Records (ADRs) for the ComfyUI Frontend project.
## What is an ADR?
An Architecture Decision Record captures an important architectural decision made along with its context and consequences. ADRs help future developers understand why certain decisions were made and provide a historical record of the project's evolution.
## ADR Index
| ADR | Title | Status | Date |
|-----|-------|--------|------|
| [0001](0001-merge-litegraph-into-frontend.md) | Merge LiteGraph.js into ComfyUI Frontend | Accepted | 2025-08-05 |
## Creating a New ADR
1. Copy the template below
2. Name it with the next number in sequence: `NNNN-descriptive-title.md`
3. Fill in all sections
4. Update this index
5. Submit as part of your PR
## ADR Template
```markdown
# N. Title
Date: YYYY-MM-DD
## Status
[Proposed | Accepted | Rejected | Deprecated | Superseded by [ADR-NNNN](NNNN-title.md)]
## Context
Describe the issue that motivated this decision and any context that influences or constrains the decision.
- What is the problem?
- Why does it need to be solved?
- What forces are at play (technical, business, team)?
## Decision
Describe the decision that was made and the key points that led to it.
- What are we going to do?
- How will we do it?
- What alternatives were considered?
## Consequences
### Positive
- What becomes easier or better?
- What opportunities does this create?
### Negative
- What becomes harder or worse?
- What risks are we accepting?
- What technical debt might we incur?
## Notes
Optional section for additional information, references, or clarifications.
```
## ADR Status Values
- **Proposed**: The decision is being discussed
- **Accepted**: The decision has been agreed upon
- **Rejected**: The decision was not accepted
- **Deprecated**: The decision is no longer relevant
- **Superseded**: The decision has been replaced by another ADR
## Further Reading
- [Documenting Architecture Decisions](https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions) by Michael Nygard
- [Architecture Decision Records](https://adr.github.io/) - Collection of ADR resources

11
package-lock.json generated
View File

@@ -1,17 +1,18 @@
{
"name": "@comfyorg/comfyui-frontend",
"version": "1.25.5",
"version": "1.25.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@comfyorg/comfyui-frontend",
"version": "1.25.5",
"version": "1.25.2",
"license": "GPL-3.0-only",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
"@comfyorg/comfyui-electron-types": "^0.4.43",
"@comfyorg/litegraph": "^0.16.20",
"@primevue/forms": "^4.2.5",
"@primevue/themes": "^4.2.5",
"@sentry/vue": "^8.48.0",
@@ -975,6 +976,12 @@
"integrity": "sha512-o6WFbYn9yAkGbkOwvhPF7pbKDvN0occZ21Tfyhya8CIsIqKpTHLft0aOqo4yhSh+kTxN16FYjsfrTH5Olk4WuA==",
"license": "GPL-3.0-only"
},
"node_modules/@comfyorg/litegraph": {
"version": "0.16.20",
"resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.16.20.tgz",
"integrity": "sha512-8iUBhKYkr9qV6vWxC3C9Wea9K7iHwyDHxxN6OrhE9sySYfUA14XuNpVMaC8eVUaIm5KBOSmr/Q1J2XVHsHEISg==",
"license": "MIT"
},
"node_modules/@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",

View File

@@ -1,7 +1,7 @@
{
"name": "@comfyorg/comfyui-frontend",
"private": true,
"version": "1.25.5",
"version": "1.25.2",
"type": "module",
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
"homepage": "https://comfy.org",
@@ -78,6 +78,7 @@
"@alloc/quick-lru": "^5.2.0",
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
"@comfyorg/comfyui-electron-types": "^0.4.43",
"@comfyorg/litegraph": "^0.16.20",
"@primevue/forms": "^4.2.5",
"@primevue/themes": "^4.2.5",
"@sentry/vue": "^8.48.0",

View File

@@ -16,7 +16,9 @@ const typesPackage = {
homepage: mainPackage.homepage,
description: `TypeScript definitions for ${mainPackage.name}`,
license: mainPackage.license,
dependencies: {},
dependencies: {
'@comfyorg/litegraph': mainPackage.dependencies['@comfyorg/litegraph']
},
peerDependencies: {
vue: mainPackage.dependencies.vue,
zod: mainPackage.dependencies.zod

View File

@@ -1,57 +0,0 @@
# Source Code Guidelines
## Service Layer
### API Calls
- Use `api.apiURL()` for backend endpoints
- Use `api.fileURL()` for static files
#### ✅ Correct Usage
```typescript
// Backend API call
const response = await api.get(api.apiURL('/prompt'))
// Static file
const template = await fetch(api.fileURL('/templates/default.json'))
```
#### ❌ Incorrect Usage
```typescript
// WRONG - Direct URL construction
const response = await fetch('/api/prompt')
const template = await fetch('/templates/default.json')
```
### Error Handling
- User-friendly and actionable messages
- Proper error propagation
### Security
- Sanitize HTML with DOMPurify
- Validate trusted sources
- Never log secrets
## State Management (Stores)
### Store Design
- Follow domain-driven design
- Clear public interfaces
- Restrict extension access
### Best Practices
- Use TypeScript for type safety
- Implement proper error handling
- Clean up subscriptions
- Avoid @ts-expect-error
## General Guidelines
- Use lodash for utility functions
- Implement proper TypeScript types
- Follow Vue 3 composition API style guide
- Use vue-i18n for ALL user-facing strings in `src/locales/en/main.json`

View File

@@ -1,45 +0,0 @@
# Component Guidelines
## Vue 3 Composition API
- Use setup() function
- Destructure props (Vue 3.5 style)
- Use ref/reactive for state
- Implement computed() for derived state
- Use provide/inject for dependency injection
## Component Communication
- Prefer `emit/@event-name` for state changes
- Use `defineExpose` only for imperative operations (`form.validate()`, `modal.open()`)
- Events promote loose coupling
## UI Framework
- Deprecated PrimeVue component replacements:
- Dropdown → Select
- OverlayPanel → Popover
- Calendar → DatePicker
- InputSwitch → ToggleSwitch
- Sidebar → Drawer
- Chips → AutoComplete with multiple enabled
- TabMenu → Tabs without panels
- Steps → Stepper without panels
- InlineMessage → Message
## Styling
- Use Tailwind CSS only (no custom CSS)
- Dark theme: use "dark-theme:" prefix
- For common operations, try to use existing VueUse composables that automatically handle effect scope
- Example: Use `useElementHover` instead of manually managing mouseover/mouseout event listeners
- Example: Use `useIntersectionObserver` for visibility detection instead of custom scroll handlers
## Best Practices
- Extract complex conditionals to computed
- Implement cleanup for async operations
- Use vue-i18n for ALL UI strings
- Use lifecycle hooks: onMounted, onUpdated
- Use Teleport/Suspense when needed
- Proper props and emits definitions

View File

@@ -42,11 +42,11 @@
</template>
<script setup lang="ts">
import type { LGraphNode } from '@comfyorg/litegraph'
import { whenever } from '@vueuse/core'
import Message from 'primevue/message'
import { computed, ref } from 'vue'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { useSystemStatsStore } from '@/stores/systemStatsStore'
import { compareVersions } from '@/utils/formatUtil'

View File

@@ -49,6 +49,7 @@
</template>
<script setup lang="ts">
import type { LGraphNode } from '@comfyorg/litegraph'
import { useEventListener, whenever } from '@vueuse/core'
import { computed, onMounted, ref, watch, watchEffect } from 'vue'
@@ -77,7 +78,6 @@ import { useWorkflowAutoSave } from '@/composables/useWorkflowAutoSave'
import { useWorkflowPersistence } from '@/composables/useWorkflowPersistence'
import { CORE_SETTINGS } from '@/constants/coreSettings'
import { i18n, t } from '@/i18n'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { UnauthorizedError, api } from '@/scripts/api'
import { app as comfyApp } from '@/scripts/app'
import { ChangeTracker } from '@/scripts/changeTracker'

View File

@@ -70,13 +70,13 @@
</template>
<script setup lang="ts">
import { LiteGraph } from '@comfyorg/litegraph'
import Button from 'primevue/button'
import ButtonGroup from 'primevue/buttongroup'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useCanvasInteractions } from '@/composables/graph/useCanvasInteractions'
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import { useCommandStore } from '@/stores/commandStore'
import { useCanvasStore } from '@/stores/graphStore'
import { useSettingStore } from '@/stores/settingStore'

View File

@@ -10,15 +10,15 @@
</template>
<script setup lang="ts">
import { useEventListener } from '@vueuse/core'
import { nextTick, ref } from 'vue'
import { st } from '@/i18n'
import {
LiteGraph,
isOverNodeInput,
isOverNodeOutput
} from '@/lib/litegraph/src/litegraph'
} from '@comfyorg/litegraph'
import { useEventListener } from '@vueuse/core'
import { nextTick, ref } from 'vue'
import { st } from '@/i18n'
import { app as comfyApp } from '@/scripts/app'
import { isDOMWidget } from '@/scripts/domWidget'
import { useNodeDefStore } from '@/stores/nodeDefStore'

View File

@@ -13,32 +13,30 @@
</template>
<script setup lang="ts">
import { createBounds } from '@comfyorg/litegraph'
import { whenever } from '@vueuse/core'
import { ref, watch } from 'vue'
import { useSelectedLiteGraphItems } from '@/composables/canvas/useSelectedLiteGraphItems'
import { useAbsolutePosition } from '@/composables/element/useAbsolutePosition'
import { createBounds } from '@/lib/litegraph/src/litegraph'
import { useCanvasStore } from '@/stores/graphStore'
const canvasStore = useCanvasStore()
const { style, updatePosition } = useAbsolutePosition()
const { getSelectableItems } = useSelectedLiteGraphItems()
const visible = ref(false)
const showBorder = ref(false)
const positionSelectionOverlay = () => {
const selectableItems = getSelectableItems()
showBorder.value = selectableItems.size > 1
const { selectedItems } = canvasStore.getCanvas()
showBorder.value = selectedItems.size > 1
if (!selectableItems.size) {
if (!selectedItems.size) {
visible.value = false
return
}
visible.value = true
const bounds = createBounds(selectableItems)
const bounds = createBounds(selectedItems)
if (bounds) {
updatePosition({
pos: [bounds[0], bounds[1]],
@@ -47,6 +45,7 @@ const positionSelectionOverlay = () => {
}
}
// Register listener on canvas creation.
whenever(
() => canvasStore.getCanvas().state.selectionChanged,
() => {

View File

@@ -15,7 +15,7 @@
<MaskEditorButton />
<ConvertToSubgraphButton />
<DeleteButton />
<RefreshSelectionButton />
<RefreshButton />
<ExtensionCommandButton
v-for="command in extensionToolboxCommands"
:key="command.id"
@@ -39,7 +39,7 @@ import ExtensionCommandButton from '@/components/graph/selectionToolbox/Extensio
import HelpButton from '@/components/graph/selectionToolbox/HelpButton.vue'
import MaskEditorButton from '@/components/graph/selectionToolbox/MaskEditorButton.vue'
import PinButton from '@/components/graph/selectionToolbox/PinButton.vue'
import RefreshSelectionButton from '@/components/graph/selectionToolbox/RefreshSelectionButton.vue'
import RefreshButton from '@/components/graph/selectionToolbox/RefreshButton.vue'
import { useCanvasInteractions } from '@/composables/graph/useCanvasInteractions'
import { useExtensionService } from '@/services/extensionService'
import { type ComfyCommandImpl, useCommandStore } from '@/stores/commandStore'

View File

@@ -13,17 +13,13 @@
</template>
<script setup lang="ts">
import { LGraphGroup, LGraphNode, LiteGraph } from '@comfyorg/litegraph'
import type { LiteGraphCanvasEvent } from '@comfyorg/litegraph'
import { useEventListener } from '@vueuse/core'
import { type CSSProperties, computed, ref, watch } from 'vue'
import EditableText from '@/components/common/EditableText.vue'
import { useAbsolutePosition } from '@/composables/element/useAbsolutePosition'
import {
LGraphGroup,
LGraphNode,
LiteGraph
} from '@/lib/litegraph/src/litegraph'
import type { LiteGraphCanvasEvent } from '@/lib/litegraph/src/litegraph'
import { app } from '@/scripts/app'
import { useCanvasStore, useTitleEditorStore } from '@/stores/graphStore'
import { useSettingStore } from '@/stores/settingStore'

View File

@@ -11,8 +11,8 @@ import { useCanvasStore } from '@/stores/graphStore'
import { useWorkflowStore } from '@/stores/workflowStore'
// Mock the litegraph module
vi.mock('@/lib/litegraph/src/litegraph', async () => {
const actual = await vi.importActual('@/lib/litegraph/src/litegraph')
vi.mock('@comfyorg/litegraph', async () => {
const actual = await vi.importActual('@comfyorg/litegraph')
return {
...actual,
LGraphCanvas: {

View File

@@ -44,17 +44,13 @@
</template>
<script setup lang="ts">
import type { ColorOption as CanvasColorOption } from '@comfyorg/litegraph'
import { LGraphCanvas, LiteGraph, isColorable } from '@comfyorg/litegraph'
import Button from 'primevue/button'
import SelectButton from 'primevue/selectbutton'
import { computed, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import type { ColorOption as CanvasColorOption } from '@/lib/litegraph/src/litegraph'
import {
LGraphCanvas,
LiteGraph,
isColorable
} from '@/lib/litegraph/src/litegraph'
import { useCanvasStore } from '@/stores/graphStore'
import { useWorkflowStore } from '@/stores/workflowStore'
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'

View File

@@ -19,11 +19,11 @@
</template>
<script setup lang="ts">
import type { LGraphNode } from '@comfyorg/litegraph'
import Button from 'primevue/button'
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { useCommandStore } from '@/stores/commandStore'
import { useCanvasStore } from '@/stores/graphStore'
import { isLGraphNode } from '@/utils/litegraphUtil'

View File

@@ -12,10 +12,10 @@
</template>
<script setup lang="ts">
import { NodeId } from '@comfyorg/litegraph'
import Skeleton from 'primevue/skeleton'
import { computed, onMounted, ref, watch } from 'vue'
import { NodeId } from '@/lib/litegraph/src/litegraph'
import { useExecutionStore } from '@/stores/executionStore'
import { linkifyHtml, nl2br } from '@/utils/formatUtil'

View File

@@ -5,6 +5,7 @@
</template>
<script setup lang="ts">
import { LGraphNode } from '@comfyorg/litegraph'
import { onMounted, onUnmounted, ref, toRaw, watch } from 'vue'
import LoadingOverlay from '@/components/load3d/LoadingOverlay.vue'
@@ -16,7 +17,6 @@ import {
UpDirection
} from '@/extensions/core/load3d/interfaces'
import { t } from '@/i18n'
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
import type { CustomInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
import { useLoad3dService } from '@/services/load3dService'

View File

@@ -75,11 +75,11 @@
</template>
<script setup lang="ts">
import { LGraphNode } from '@comfyorg/litegraph'
import { Tooltip } from 'primevue'
import Button from 'primevue/button'
import { t } from '@/i18n'
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { useLoad3dService } from '@/services/load3dService'
const vTooltip = Tooltip

View File

@@ -1,12 +1,11 @@
import { mount } from '@vue/test-utils'
import { createPinia } from 'pinia'
import PrimeVue from 'primevue/config'
import { beforeAll, describe, expect, it, vi } from 'vitest'
import { beforeAll, describe, expect, it } from 'vitest'
import { createApp } from 'vue'
import { createI18n } from 'vue-i18n'
import type { ComfyNodeDef as ComfyNodeDefV2 } from '@/schemas/nodeDef/nodeDefSchemaV2'
import * as markdownRendererUtil from '@/utils/markdownRendererUtil'
import NodePreview from './NodePreview.vue'
@@ -130,164 +129,4 @@ describe('NodePreview', () => {
expect(headdot.classes()).toContain('pr-3')
})
describe('Description Rendering', () => {
it('renders plain text description as HTML', () => {
const plainTextNodeDef: ComfyNodeDefV2 = {
...mockNodeDef,
description: 'This is a plain text description'
}
const wrapper = mountComponent(plainTextNodeDef)
const description = wrapper.find('._sb_description')
expect(description.exists()).toBe(true)
expect(description.html()).toContain('This is a plain text description')
})
it('renders markdown description with formatting', () => {
const markdownNodeDef: ComfyNodeDefV2 = {
...mockNodeDef,
description: '**Bold text** and *italic text* with `code`'
}
const wrapper = mountComponent(markdownNodeDef)
const description = wrapper.find('._sb_description')
expect(description.exists()).toBe(true)
expect(description.html()).toContain('<strong>Bold text</strong>')
expect(description.html()).toContain('<em>italic text</em>')
expect(description.html()).toContain('<code>code</code>')
})
it('does not render description element when description is empty', () => {
const noDescriptionNodeDef: ComfyNodeDefV2 = {
...mockNodeDef,
description: ''
}
const wrapper = mountComponent(noDescriptionNodeDef)
const description = wrapper.find('._sb_description')
expect(description.exists()).toBe(false)
})
it('does not render description element when description is undefined', () => {
const { description, ...nodeDefWithoutDescription } = mockNodeDef
const wrapper = mountComponent(
nodeDefWithoutDescription as ComfyNodeDefV2
)
const descriptionElement = wrapper.find('._sb_description')
expect(descriptionElement.exists()).toBe(false)
})
it('calls renderMarkdownToHtml utility function', () => {
const spy = vi.spyOn(markdownRendererUtil, 'renderMarkdownToHtml')
const testDescription = 'Test **markdown** description'
const nodeDefWithDescription: ComfyNodeDefV2 = {
...mockNodeDef,
description: testDescription
}
mountComponent(nodeDefWithDescription)
expect(spy).toHaveBeenCalledWith(testDescription)
spy.mockRestore()
})
it('handles potentially unsafe markdown content safely', () => {
const unsafeNodeDef: ComfyNodeDefV2 = {
...mockNodeDef,
description:
'Safe **markdown** content <script>alert("xss")</script> with `code` blocks'
}
const wrapper = mountComponent(unsafeNodeDef)
const description = wrapper.find('._sb_description')
// The description should still exist because there's safe content
if (description.exists()) {
// Should not contain script tags (sanitized by DOMPurify)
expect(description.html()).not.toContain('<script>')
expect(description.html()).not.toContain('alert("xss")')
// Should contain the safe markdown content rendered as HTML
expect(description.html()).toContain('<strong>markdown</strong>')
expect(description.html()).toContain('<code>code</code>')
} else {
// If DOMPurify removes everything, that's also acceptable for security
expect(description.exists()).toBe(false)
}
})
it('handles markdown with line breaks', () => {
const multilineNodeDef: ComfyNodeDefV2 = {
...mockNodeDef,
description: 'Line 1\n\nLine 3 after empty line'
}
const wrapper = mountComponent(multilineNodeDef)
const description = wrapper.find('._sb_description')
expect(description.exists()).toBe(true)
// Should contain paragraph tags for proper line break handling
expect(description.html()).toContain('<p>')
})
it('handles markdown lists', () => {
const listNodeDef: ComfyNodeDefV2 = {
...mockNodeDef,
description: '- Item 1\n- Item 2\n- Item 3'
}
const wrapper = mountComponent(listNodeDef)
const description = wrapper.find('._sb_description')
expect(description.exists()).toBe(true)
expect(description.html()).toContain('<ul>')
expect(description.html()).toContain('<li>')
})
it('applies correct styling classes to description', () => {
const wrapper = mountComponent()
const description = wrapper.find('._sb_description')
expect(description.classes()).toContain('_sb_description')
})
it('uses v-html directive for rendered content', () => {
const htmlNodeDef: ComfyNodeDefV2 = {
...mockNodeDef,
description: 'Content with **bold** text'
}
const wrapper = mountComponent(htmlNodeDef)
const description = wrapper.find('._sb_description')
// The component should render the HTML, not escape it
expect(description.html()).toContain('<strong>bold</strong>')
expect(description.html()).not.toContain('&lt;strong&gt;')
})
it('prevents XSS attacks by sanitizing dangerous HTML elements', () => {
const maliciousNodeDef: ComfyNodeDefV2 = {
...mockNodeDef,
description:
'Normal text <img src="x" onerror="alert(\'XSS\')" /> and **bold** text'
}
const wrapper = mountComponent(maliciousNodeDef)
const description = wrapper.find('._sb_description')
if (description.exists()) {
// Should not contain dangerous event handlers
expect(description.html()).not.toContain('onerror')
expect(description.html()).not.toContain('alert(')
// Should still contain safe markdown content
expect(description.html()).toContain('<strong>bold</strong>')
// May or may not contain img tag depending on DOMPurify config
}
})
})
})

View File

@@ -70,14 +70,15 @@ https://github.com/Nuked88/ComfyUI-N-Sidebar/blob/7ae7da4a9761009fb6629bc04c6830
</div>
</div>
<div
v-if="renderedDescription"
v-if="nodeDef.description"
class="_sb_description"
:style="{
color: litegraphColors.WIDGET_SECONDARY_TEXT_COLOR,
backgroundColor: litegraphColors.WIDGET_BGCOLOR
}"
v-html="renderedDescription"
/>
>
{{ nodeDef.description }}
</div>
</div>
</template>
@@ -88,9 +89,8 @@ import { computed } from 'vue'
import type { ComfyNodeDef as ComfyNodeDefV2 } from '@/schemas/nodeDef/nodeDefSchemaV2'
import { useWidgetStore } from '@/stores/widgetStore'
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
import { renderMarkdownToHtml } from '@/utils/markdownRendererUtil'
const { nodeDef } = defineProps<{
const props = defineProps<{
nodeDef: ComfyNodeDefV2
}>()
@@ -101,12 +101,7 @@ const litegraphColors = computed(
const widgetStore = useWidgetStore()
const { description } = nodeDef
const renderedDescription = computed(() => {
if (!description) return ''
return renderMarkdownToHtml(description)
})
const nodeDef = props.nodeDef
const allInputDefs = Object.values(nodeDef.inputs)
const allOutputDefs = nodeDef.outputs
const slotInputDefs = allInputDefs.filter(

View File

@@ -33,18 +33,18 @@
</template>
<script setup lang="ts">
import {
LGraphNode,
LiteGraph,
LiteGraphCanvasEvent
} from '@comfyorg/litegraph'
import { Point } from '@comfyorg/litegraph/dist/interfaces'
import type { CanvasPointerEvent } from '@comfyorg/litegraph/dist/types/events'
import { useEventListener } from '@vueuse/core'
import { storeToRefs } from 'pinia'
import Dialog from 'primevue/dialog'
import { computed, ref, toRaw, watch, watchEffect } from 'vue'
import { Point } from '@/lib/litegraph/src/interfaces'
import {
LGraphNode,
LiteGraph,
LiteGraphCanvasEvent
} from '@/lib/litegraph/src/litegraph'
import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events'
import { useLitegraphService } from '@/services/litegraphService'
import { useCanvasStore } from '@/stores/graphStore'
import { ComfyNodeDefImpl, useNodeDefStore } from '@/stores/nodeDefStore'

View File

@@ -1,6 +1,6 @@
import { LGraphCanvas } from '@comfyorg/litegraph'
import { onUnmounted, ref } from 'vue'
import { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
import { useCanvasStore } from '@/stores/graphStore'
interface CanvasTransformSyncOptions {

View File

@@ -1,161 +0,0 @@
import {
LGraphEventMode,
LGraphNode,
Positionable,
Reroute
} from '@/lib/litegraph/src/litegraph'
import { app } from '@/scripts/app'
import { useCanvasStore } from '@/stores/graphStore'
import {
collectFromNodes,
traverseNodesDepthFirst
} from '@/utils/graphTraversalUtil'
/**
* Composable for handling selected LiteGraph items filtering and operations.
* This provides utilities for working with selected items on the canvas,
* including filtering out items that should not be included in selection operations.
*/
export function useSelectedLiteGraphItems() {
const canvasStore = useCanvasStore()
/**
* Items that should not show in the selection overlay are ignored.
* @param item - The item to check.
* @returns True if the item should be ignored, false otherwise.
*/
const isIgnoredItem = (item: Positionable): boolean => {
return item instanceof Reroute
}
/**
* Filter out items that should not show in the selection overlay.
* @param items - The Set of items to filter.
* @returns The filtered Set of items.
*/
const filterSelectableItems = (
items: Set<Positionable>
): Set<Positionable> => {
const result = new Set<Positionable>()
for (const item of items) {
if (!isIgnoredItem(item)) {
result.add(item)
}
}
return result
}
/**
* Get the filtered selected items from the canvas.
* @returns The filtered Set of selected items.
*/
const getSelectableItems = (): Set<Positionable> => {
const { selectedItems } = canvasStore.getCanvas()
return filterSelectableItems(selectedItems)
}
/**
* Check if there are any selectable items.
* @returns True if there are selectable items, false otherwise.
*/
const hasSelectableItems = (): boolean => {
return getSelectableItems().size > 0
}
/**
* Check if there are multiple selectable items.
* @returns True if there are multiple selectable items, false otherwise.
*/
const hasMultipleSelectableItems = (): boolean => {
return getSelectableItems().size > 1
}
/**
* Get only the selected nodes (LGraphNode instances) from the canvas.
* This filters out other types of selected items like groups or reroutes.
* If a selected node is a subgraph, this also includes all nodes within it.
* @returns Array of selected LGraphNode instances and their descendants.
*/
const getSelectedNodes = (): LGraphNode[] => {
const selectedNodes = app.canvas.selected_nodes
if (!selectedNodes) return []
// Convert selected_nodes object to array, preserving order
const nodeArray: LGraphNode[] = []
for (const i in selectedNodes) {
nodeArray.push(selectedNodes[i])
}
// Check if any selected nodes are subgraphs
const hasSubgraphs = nodeArray.some(
(node) => node.isSubgraphNode?.() && node.subgraph
)
// If no subgraphs, just return the array directly to preserve order
if (!hasSubgraphs) {
return nodeArray
}
// Use collectFromNodes to get all nodes including those in subgraphs
return collectFromNodes(nodeArray)
}
/**
* Toggle the execution mode of all selected nodes with unified subgraph behavior.
*
* Top-level behavior (selected nodes): Standard toggle logic
* - If the selected node is already in the specified mode → set to ALWAYS
* - Otherwise → set to the specified mode
*
* Subgraph behavior (children of selected subgraph nodes): Unified state application
* - All children inherit the same mode that their parent subgraph node was set to
* - This creates predictable behavior: if you toggle a subgraph to "mute",
* ALL nodes inside become muted, regardless of their previous individual states
*
* @param mode - The LGraphEventMode to toggle to (e.g., NEVER for mute, BYPASS for bypass)
*/
const toggleSelectedNodesMode = (mode: LGraphEventMode): void => {
const selectedNodes = app.canvas.selected_nodes
if (!selectedNodes) return
// Convert selected_nodes object to array
const selectedNodeArray: LGraphNode[] = []
for (const i in selectedNodes) {
selectedNodeArray.push(selectedNodes[i])
}
// Process each selected node independently to determine its target state and apply to children
selectedNodeArray.forEach((selectedNode) => {
// Apply standard toggle logic to the selected node itself
const newModeForSelectedNode =
selectedNode.mode === mode ? LGraphEventMode.ALWAYS : mode
selectedNode.mode = newModeForSelectedNode
// If this selected node is a subgraph, apply the same mode uniformly to all its children
// This ensures predictable behavior: all children get the same state as their parent
if (selectedNode.isSubgraphNode?.() && selectedNode.subgraph) {
traverseNodesDepthFirst([selectedNode], {
visitor: (node) => {
// Skip the parent node since we already handled it above
if (node === selectedNode) return undefined
// Apply the parent's new mode to all children uniformly
node.mode = newModeForSelectedNode
return undefined
}
})
}
})
}
return {
isIgnoredItem,
filterSelectableItems,
getSelectableItems,
hasSelectableItems,
hasMultipleSelectableItems,
getSelectedNodes,
toggleSelectedNodesMode
}
}

View File

@@ -1,7 +1,7 @@
import type { Size, Vector2 } from '@comfyorg/litegraph'
import { CSSProperties, ref, watch } from 'vue'
import { useCanvasPositionConversion } from '@/composables/element/useCanvasPositionConversion'
import type { Size, Vector2 } from '@/lib/litegraph/src/litegraph'
import { useCanvasStore } from '@/stores/graphStore'
import { useSettingStore } from '@/stores/settingStore'

View File

@@ -1,7 +1,6 @@
import type { LGraphCanvas, Vector2 } from '@comfyorg/litegraph'
import { useElementBounding } from '@vueuse/core'
import type { LGraphCanvas, Vector2 } from '@/lib/litegraph/src/litegraph'
/**
* Convert between canvas and client positions
* @param canvasElement - The canvas element

View File

@@ -1,5 +1,6 @@
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import type { IWidget } from '@/lib/litegraph/src/types/widgets'
import type { LGraphNode } from '@comfyorg/litegraph'
import type { IWidget } from '@comfyorg/litegraph/dist/types/widgets'
import { ANIM_PREVIEW_WIDGET } from '@/scripts/app'
import { createImageHost } from '@/scripts/ui/imagePreview'
import { fitDimensionsToNodeWidth } from '@/utils/imageUtil'

View File

@@ -1,13 +1,13 @@
import {
BadgePosition,
LGraphBadge,
type LGraphNode
} from '@comfyorg/litegraph'
import _ from 'lodash'
import { computed, onMounted, watch } from 'vue'
import { useNodePricing } from '@/composables/node/useNodePricing'
import { useComputedWithWidgetWatch } from '@/composables/node/useWatchWidget'
import {
BadgePosition,
LGraphBadge,
type LGraphNode
} from '@/lib/litegraph/src/litegraph'
import { app } from '@/scripts/app'
import { useExtensionStore } from '@/stores/extensionStore'
import { ComfyNodeDefImpl, useNodeDefStore } from '@/stores/nodeDefStore'

View File

@@ -1,5 +1,6 @@
import type { LGraphNode } from '@comfyorg/litegraph'
import { useImagePreviewWidget } from '@/composables/widgets/useImagePreviewWidget'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
const CANVAS_IMAGE_PREVIEW_WIDGET = '$$canvas-image-preview'

View File

@@ -1,6 +1,7 @@
import { LGraphNode } from '@comfyorg/litegraph'
import type ChatHistoryWidget from '@/components/graph/widgets/ChatHistoryWidget.vue'
import { useChatHistoryWidget } from '@/composables/widgets/useChatHistoryWidget'
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
const CHAT_HISTORY_WIDGET_NAME = '$$node-chat-history'

View File

@@ -1,4 +1,4 @@
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import type { LGraphNode } from '@comfyorg/litegraph'
type DragHandler = (e: DragEvent) => boolean
type DropHandler<T> = (files: File[]) => Promise<T[]>

View File

@@ -1,5 +1,6 @@
import type { LGraphNode } from '@comfyorg/litegraph'
import { useChainCallback } from '@/composables/functional/useChainCallback'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
interface FileInputOptions {
accept?: string

View File

@@ -1,4 +1,5 @@
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import type { LGraphNode } from '@comfyorg/litegraph'
import { useNodeOutputStore } from '@/stores/imagePreviewStore'
import { fitDimensionsToNodeWidth } from '@/utils/imageUtil'

View File

@@ -1,7 +1,8 @@
import type { LGraphNode } from '@comfyorg/litegraph'
import { useNodeDragAndDrop } from '@/composables/node/useNodeDragAndDrop'
import { useNodeFileInput } from '@/composables/node/useNodeFileInput'
import { useNodePaste } from '@/composables/node/useNodePaste'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import type { ResultItemType } from '@/schemas/apiSchema'
import { api } from '@/scripts/api'
import { useToastStore } from '@/stores/toastStore'

View File

@@ -1,4 +1,4 @@
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import type { LGraphNode } from '@comfyorg/litegraph'
type PasteHandler<T> = (files: File[]) => Promise<T>

View File

@@ -1,5 +1,5 @@
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import type { IComboWidget } from '@/lib/litegraph/src/types/widgets'
import type { LGraphNode } from '@comfyorg/litegraph'
import type { IComboWidget } from '@comfyorg/litegraph/dist/types/widgets'
/**
* Function that calculates dynamic pricing based on node widget values
@@ -919,33 +919,6 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
return `$${price.toFixed(2)}/Run`
}
},
Veo3VideoGenerationNode: {
displayPrice: (node: LGraphNode): string => {
const modelWidget = node.widgets?.find(
(w) => w.name === 'model'
) as IComboWidget
const generateAudioWidget = node.widgets?.find(
(w) => w.name === 'generate_audio'
) as IComboWidget
if (!modelWidget || !generateAudioWidget) {
return '$2.00-6.00/Run (varies with model & audio generation)'
}
const model = String(modelWidget.value)
const generateAudio =
String(generateAudioWidget.value).toLowerCase() === 'true'
if (model.includes('veo-3.0-fast-generate-001')) {
return generateAudio ? '$3.20/Run' : '$2.00/Run'
} else if (model.includes('veo-3.0-generate-001')) {
return generateAudio ? '$6.00/Run' : '$4.00/Run'
}
// Default fallback
return '$2.00-6.00/Run'
}
},
LumaImageNode: {
displayPrice: (node: LGraphNode): string => {
const modelWidget = node.widgets?.find(
@@ -1367,7 +1340,6 @@ export const useNodePricing = () => {
FluxProKontextProNode: [],
FluxProKontextMaxNode: [],
VeoVideoGenerationNode: ['duration_seconds'],
Veo3VideoGenerationNode: ['model', 'generate_audio'],
LumaVideoNode: ['model', 'resolution', 'duration'],
LumaImageToVideoNode: ['model', 'resolution', 'duration'],
LumaImageNode: ['model', 'aspect_ratio'],

View File

@@ -1,5 +1,6 @@
import { LGraphNode } from '@comfyorg/litegraph'
import { useTextPreviewWidget } from '@/composables/widgets/useProgressTextWidget'
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
const TEXT_PREVIEW_WIDGET_NAME = '$$node-text-preview'

View File

@@ -1,8 +1,8 @@
import type { LGraphNode } from '@comfyorg/litegraph'
import { computedWithControl } from '@vueuse/core'
import { type ComputedRef, ref } from 'vue'
import { useChainCallback } from '@/composables/functional/useChainCallback'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
export interface UseComputedWithWidgetWatchOptions {
/**

View File

@@ -1,9 +1,9 @@
import { LGraphNode } from '@comfyorg/litegraph'
import { NodeProperty } from '@comfyorg/litegraph/dist/LGraphNode'
import { groupBy } from 'lodash'
import { computed, onMounted } from 'vue'
import { useWorkflowPacks } from '@/composables/nodePack/useWorkflowPacks'
import { NodeProperty } from '@/lib/litegraph/src/LGraphNode'
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { app } from '@/scripts/app'
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
import { useNodeDefStore } from '@/stores/nodeDefStore'

View File

@@ -1,7 +1,7 @@
import { LGraphNode } from '@comfyorg/litegraph'
import { computed, onUnmounted, ref } from 'vue'
import { useNodePacks } from '@/composables/nodePack/useNodePacks'
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { ComfyWorkflowJSON } from '@/schemas/comfyWorkflowSchema'
import { app } from '@/scripts/app'
import { useComfyRegistryStore } from '@/stores/comfyRegistryStore'

View File

@@ -1,8 +1,8 @@
import { LGraphNode } from '@comfyorg/litegraph'
import { LiteGraph } from '@comfyorg/litegraph'
import { Ref } from 'vue'
import { usePragmaticDroppable } from '@/composables/usePragmaticDragAndDrop'
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import { app as comfyApp } from '@/scripts/app'
import { useLitegraphService } from '@/services/litegraphService'
import { useWorkflowService } from '@/services/workflowService'

View File

@@ -1,11 +1,12 @@
import { st, te } from '@/i18n'
import type {
IContextMenuOptions,
IContextMenuValue,
INodeInputSlot,
IWidget
} from '@/lib/litegraph/src/litegraph'
import { LGraphCanvas, LiteGraph } from '@/lib/litegraph/src/litegraph'
} from '@comfyorg/litegraph'
import { LGraphCanvas, LiteGraph } from '@comfyorg/litegraph'
import { st, te } from '@/i18n'
import { normalizeI18nKey } from '@/utils/formatUtil'
/**

View File

@@ -1,17 +1,17 @@
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
import { useSelectedLiteGraphItems } from '@/composables/canvas/useSelectedLiteGraphItems'
import {
DEFAULT_DARK_COLOR_PALETTE,
DEFAULT_LIGHT_COLOR_PALETTE
} from '@/constants/coreColorPalettes'
import { t } from '@/i18n'
import {
LGraphEventMode,
LGraphGroup,
LGraphNode,
LiteGraph
} from '@/lib/litegraph/src/litegraph'
import { Point } from '@/lib/litegraph/src/litegraph'
} from '@comfyorg/litegraph'
import { Point } from '@comfyorg/litegraph'
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
import {
DEFAULT_DARK_COLOR_PALETTE,
DEFAULT_LIGHT_COLOR_PALETTE
} from '@/constants/coreColorPalettes'
import { t } from '@/i18n'
import { api } from '@/scripts/api'
import { app } from '@/scripts/app'
import { addFluxKontextGroupNode } from '@/scripts/fluxKontextEditNode'
@@ -29,11 +29,7 @@ import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
import { useSearchBoxStore } from '@/stores/workspace/searchBoxStore'
import { useWorkspaceStore } from '@/stores/workspaceStore'
import {
getAllNonIoNodesInSubgraph,
getExecutionIdsForSelectedNodes
} from '@/utils/graphTraversalUtil'
import { filterOutputNodes } from '@/utils/nodeFilterUtil'
import { getAllNonIoNodesInSubgraph } from '@/utils/graphTraversalUtil'
const moveSelectedNodesVersionAdded = '1.22.2'
@@ -46,10 +42,30 @@ export function useCoreCommands(): ComfyCommand[] {
const toastStore = useToastStore()
const canvasStore = useCanvasStore()
const executionStore = useExecutionStore()
const { getSelectedNodes, toggleSelectedNodesMode } =
useSelectedLiteGraphItems()
const getTracker = () => workflowStore.activeWorkflow?.changeTracker
const getSelectedNodes = (): LGraphNode[] => {
const selectedNodes = app.canvas.selected_nodes
const result: LGraphNode[] = []
if (selectedNodes) {
for (const i in selectedNodes) {
const node = selectedNodes[i]
result.push(node)
}
}
return result
}
const toggleSelectedNodesMode = (mode: LGraphEventMode) => {
getSelectedNodes().forEach((node) => {
if (node.mode === mode) {
node.mode = LGraphEventMode.ALWAYS
} else {
node.mode = mode
}
})
}
const moveSelectedNodes = (
positionUpdater: (pos: Point, gridSize: number) => Point
) => {
@@ -347,10 +363,10 @@ export function useCoreCommands(): ComfyCommand[] {
versionAdded: '1.19.6',
function: async () => {
const batchCount = useQueueSettingsStore().batchCount
const selectedNodes = getSelectedNodes()
const selectedOutputNodes = filterOutputNodes(selectedNodes)
if (selectedOutputNodes.length === 0) {
const queueNodeIds = getSelectedNodes()
.filter((node) => node.constructor.nodeData?.output_node)
.map((node) => node.id)
if (queueNodeIds.length === 0) {
toastStore.add({
severity: 'error',
summary: t('toastMessages.nothingToQueue'),
@@ -359,11 +375,7 @@ export function useCoreCommands(): ComfyCommand[] {
})
return
}
// Get execution IDs for all selected output nodes and their descendants
const executionIds =
getExecutionIdsForSelectedNodes(selectedOutputNodes)
await app.queuePrompt(0, batchCount, executionIds)
await app.queuePrompt(0, batchCount, queueNodeIds)
}
},
{

View File

@@ -8,7 +8,7 @@ import {
LGraphNode,
LLink,
LiteGraph
} from '@/lib/litegraph/src/litegraph'
} from '@comfyorg/litegraph'
/**
* Assign all properties of LiteGraph to window to make it backward compatible.

View File

@@ -1,10 +1,6 @@
import { CanvasPointer, LGraphNode, LiteGraph } from '@comfyorg/litegraph'
import { watchEffect } from 'vue'
import {
CanvasPointer,
LGraphNode,
LiteGraph
} from '@/lib/litegraph/src/litegraph'
import { useCanvasStore } from '@/stores/graphStore'
import { useSettingStore } from '@/stores/settingStore'

View File

@@ -1,13 +1,14 @@
import type { LGraphNode } from '@comfyorg/litegraph'
import { useRafFn, useThrottleFn } from '@vueuse/core'
import { computed, nextTick, ref, watch } from 'vue'
import { useCanvasTransformSync } from '@/composables/canvas/useCanvasTransformSync'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import type { NodeId } from '@/schemas/comfyWorkflowSchema'
import { api } from '@/scripts/api'
import { app } from '@/scripts/app'
import { useCanvasStore } from '@/stores/graphStore'
import { useSettingStore } from '@/stores/settingStore'
import { useWorkflowStore } from '@/stores/workflowStore'
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
interface GraphCallbacks {
@@ -20,6 +21,7 @@ export function useMinimap() {
const settingStore = useSettingStore()
const canvasStore = useCanvasStore()
const colorPaletteStore = useColorPaletteStore()
const workflowStore = useWorkflowStore()
const containerRef = ref<HTMLDivElement>()
const canvasRef = ref<HTMLCanvasElement>()
@@ -108,6 +110,14 @@ export function useMinimap() {
const canvas = computed(() => canvasStore.canvas)
const graph = ref(app.canvas?.graph)
// Update graph ref when subgraph context changes
watch(
() => workflowStore.activeSubgraph,
() => {
graph.value = app.canvas?.graph
}
)
const containerStyles = computed(() => ({
width: `${width}px`,
height: `${height}px`,
@@ -352,8 +362,6 @@ export function useMinimap() {
needsBoundsUpdate.value = false
updateFlags.value.bounds = false
needsFullRedraw.value = true
// When bounds change, we need to update the viewport position
updateFlags.value.viewport = true
}
if (
@@ -363,11 +371,6 @@ export function useMinimap() {
) {
renderMinimap()
}
// Update viewport if needed (e.g., after bounds change)
if (updateFlags.value.viewport) {
updateViewport()
}
}
const checkForChanges = useThrottleFn(() => {
@@ -642,7 +645,7 @@ export function useMinimap() {
await init()
}
},
{ immediate: true, flush: 'post' }
{ immediate: true }
)
watch(visible, async (isVisible) => {
@@ -660,8 +663,6 @@ export function useMinimap() {
await nextTick()
await nextTick()
updateMinimap()
updateViewport()
resumeChangeDetection()

View File

@@ -1,7 +1,7 @@
import { LiteGraph } from '@comfyorg/litegraph'
import type { LGraphNode } from '@comfyorg/litegraph'
import { useEventListener } from '@vueuse/core'
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { ComfyWorkflowJSON } from '@/schemas/comfyWorkflowSchema'
import { app } from '@/scripts/app'
import { useCanvasStore } from '@/stores/graphStore'

View File

@@ -1,6 +1,6 @@
import type { LGraphNode } from '@comfyorg/litegraph'
import { computed, ref, watchEffect } from 'vue'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { useCanvasStore } from '@/stores/graphStore'
import { isLGraphNode } from '@/utils/litegraphUtil'

View File

@@ -1,4 +1,5 @@
import type { ISerialisedGraph } from '@/lib/litegraph/src/types/serialisation'
import type { ISerialisedGraph } from '@comfyorg/litegraph/dist/types/serialisation'
import type { ComfyWorkflowJSON } from '@/schemas/comfyWorkflowSchema'
import { validateComfyWorkflow } from '@/schemas/comfyWorkflowSchema'
import { useToastStore } from '@/stores/toastStore'

View File

@@ -1,4 +1,5 @@
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import type { LGraphNode } from '@comfyorg/litegraph'
import {
type InputSpec,
isBooleanInputSpec

View File

@@ -1,7 +1,7 @@
import type { LGraphNode } from '@comfyorg/litegraph'
import { ref } from 'vue'
import ChatHistoryWidget from '@/components/graph/widgets/ChatHistoryWidget.vue'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
import {
ComponentWidgetImpl,

View File

@@ -1,8 +1,8 @@
import type { LGraphNode } from '@comfyorg/litegraph'
import type { IComboWidget } from '@comfyorg/litegraph/dist/types/widgets'
import { ref } from 'vue'
import MultiSelectWidget from '@/components/graph/widgets/MultiSelectWidget.vue'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import type { IComboWidget } from '@/lib/litegraph/src/types/widgets'
import { transformInputSpecV2ToV1 } from '@/schemas/nodeDef/migration'
import {
ComboInputSpec,

View File

@@ -1,7 +1,7 @@
import type { LGraphNode } from '@comfyorg/litegraph'
import type { INumericWidget } from '@comfyorg/litegraph/dist/types/widgets'
import _ from 'lodash'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import type { INumericWidget } from '@/lib/litegraph/src/types/widgets'
import {
type InputSpec,
isFloatInputSpec

View File

@@ -3,11 +3,12 @@ import {
type CanvasPointer,
type LGraphNode,
LiteGraph
} from '@/lib/litegraph/src/litegraph'
} from '@comfyorg/litegraph'
import type {
IBaseWidget,
IWidgetOptions
} from '@/lib/litegraph/src/types/widgets'
} from '@comfyorg/litegraph/dist/types/widgets'
import type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
import { app } from '@/scripts/app'
import { calculateImageGrid } from '@/scripts/ui/imagePreview'

View File

@@ -1,9 +1,10 @@
import type { LGraphNode } from '@comfyorg/litegraph'
import { IComboWidget } from '@comfyorg/litegraph/dist/types/widgets'
import { useNodeImage, useNodeVideo } from '@/composables/node/useNodeImage'
import { useNodeImageUpload } from '@/composables/node/useNodeImageUpload'
import { useValueTransform } from '@/composables/useValueTransform'
import { t } from '@/i18n'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { IComboWidget } from '@/lib/litegraph/src/types/widgets'
import type { ResultItem, ResultItemType } from '@/schemas/apiSchema'
import type { InputSpec } from '@/schemas/nodeDefSchema'
import type { ComfyWidgetConstructor } from '@/scripts/widgets'

View File

@@ -1,5 +1,6 @@
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import type { INumericWidget } from '@/lib/litegraph/src/types/widgets'
import type { LGraphNode } from '@comfyorg/litegraph'
import type { INumericWidget } from '@comfyorg/litegraph/dist/types/widgets'
import { transformInputSpecV2ToV1 } from '@/schemas/nodeDef/migration'
import {
type InputSpec,

View File

@@ -1,3 +1,4 @@
import type { LGraphNode } from '@comfyorg/litegraph'
import { Editor as TiptapEditor } from '@tiptap/core'
import TiptapLink from '@tiptap/extension-link'
import TiptapTable from '@tiptap/extension-table'
@@ -7,7 +8,6 @@ import TiptapTableRow from '@tiptap/extension-table-row'
import TiptapStarterKit from '@tiptap/starter-kit'
import { Markdown as TiptapMarkdown } from 'tiptap-markdown'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { type InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
import { app } from '@/scripts/app'
import { type ComfyWidgetConstructorV2 } from '@/scripts/widgets'

View File

@@ -1,7 +1,7 @@
import type { LGraphNode } from '@comfyorg/litegraph'
import { ref } from 'vue'
import TextPreviewWidget from '@/components/graph/widgets/TextPreviewWidget.vue'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
import {
ComponentWidgetImpl,

View File

@@ -1,8 +1,8 @@
import { LGraphNode } from '@comfyorg/litegraph'
import { IWidget } from '@comfyorg/litegraph'
import axios from 'axios'
import { useChainCallback } from '@/composables/functional/useChainCallback'
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { IWidget } from '@/lib/litegraph/src/litegraph'
import type { RemoteWidgetConfig } from '@/schemas/nodeDefSchema'
import { api } from '@/scripts/api'

View File

@@ -1,4 +1,5 @@
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import type { LGraphNode } from '@comfyorg/litegraph'
import {
type InputSpec,
isStringInputSpec

View File

@@ -1,4 +1,5 @@
import { LinkMarkerShape, LiteGraph } from '@/lib/litegraph/src/litegraph'
import { LinkMarkerShape, LiteGraph } from '@comfyorg/litegraph'
import type { ColorPalettes } from '@/schemas/colorPaletteSchema'
import type { Keybinding } from '@/schemas/keyBindingSchema'
import { NodeBadgeMode } from '@/types/nodeSource'

View File

@@ -1,8 +1,4 @@
import {
LGraphCanvas,
LiteGraph,
isComboWidget
} from '@/lib/litegraph/src/litegraph'
import { LGraphCanvas, LiteGraph, isComboWidget } from '@comfyorg/litegraph'
import { app } from '../../scripts/app'

View File

@@ -1,12 +1,13 @@
import { t } from '@/i18n'
import { type NodeId } from '@/lib/litegraph/src/LGraphNode'
import {
type ExecutableLGraphNode,
type ExecutionId,
LGraphNode,
LiteGraph,
SubgraphNode
} from '@/lib/litegraph/src/litegraph'
} from '@comfyorg/litegraph'
import { type NodeId } from '@comfyorg/litegraph/dist/LGraphNode'
import { t } from '@/i18n'
import {
ComfyLink,
ComfyNode,

View File

@@ -2,7 +2,8 @@ import {
type LGraphNode,
type LGraphNodeConstructor,
LiteGraph
} from '@/lib/litegraph/src/litegraph'
} from '@comfyorg/litegraph'
import { useToastStore } from '@/stores/toastStore'
import { type ComfyApp, app } from '../../scripts/app'

View File

@@ -1,7 +1,8 @@
import type { Positionable } from '@/lib/litegraph/src/interfaces'
import { LGraphGroup } from '@/lib/litegraph/src/litegraph'
import { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { LGraphGroup } from '@comfyorg/litegraph'
import { LGraphCanvas } from '@comfyorg/litegraph'
import type { LGraphNode } from '@comfyorg/litegraph'
import type { Positionable } from '@comfyorg/litegraph/dist/interfaces'
import { useSettingStore } from '@/stores/settingStore'
import { app } from '../../scripts/app'

View File

@@ -1,3 +1,4 @@
import type { IStringWidget } from '@comfyorg/litegraph/dist/types/widgets'
import { nextTick } from 'vue'
import Load3D from '@/components/load3d/Load3D.vue'
@@ -6,7 +7,6 @@ import Load3DConfiguration from '@/extensions/core/load3d/Load3DConfiguration'
import Load3dAnimation from '@/extensions/core/load3d/Load3dAnimation'
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
import { t } from '@/i18n'
import type { IStringWidget } from '@/lib/litegraph/src/types/widgets'
import { CustomInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
import { api } from '@/scripts/api'
import { ComponentWidgetImpl, addWidget } from '@/scripts/domWidget'

View File

@@ -1,6 +1,7 @@
import type { IBaseWidget } from '@comfyorg/litegraph/dist/types/widgets'
import Load3d from '@/extensions/core/load3d/Load3d'
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
import { api } from '@/scripts/api'
import { useSettingStore } from '@/stores/settingStore'

View File

@@ -1,6 +1,6 @@
import { LGraphNode } from '@comfyorg/litegraph'
import * as THREE from 'three'
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { CustomInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
import { CameraManager } from './CameraManager'

View File

@@ -1,7 +1,6 @@
import { LGraphNode } from '@comfyorg/litegraph'
import * as THREE from 'three'
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { AnimationManager } from './AnimationManager'
import Load3d from './Load3d'
import { Load3DOptions } from './interfaces'

View File

@@ -1,4 +1,4 @@
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { LGraphNode } from '@comfyorg/litegraph'
import { NodeStorageInterface } from './interfaces'

View File

@@ -1,3 +1,4 @@
import { LGraphNode } from '@comfyorg/litegraph'
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { ViewHelper } from 'three/examples/jsm/helpers/ViewHelper'
@@ -7,7 +8,6 @@ import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader'
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader'
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { CustomInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
export type Load3DNodeType = 'Load3D' | 'Preview3D'

View File

@@ -1,5 +1,6 @@
import { LGraphCanvas } from '@comfyorg/litegraph'
import { t } from '@/i18n'
import { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
import { useDialogService } from '@/services/dialogService'
import { useToastStore } from '@/stores/toastStore'
import { deserialiseAndCreate } from '@/utils/vintageClipboard'

View File

@@ -1,5 +1,5 @@
import { LGraphCanvas, LiteGraph } from '@/lib/litegraph/src/litegraph'
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { LGraphCanvas, LiteGraph } from '@comfyorg/litegraph'
import { LGraphNode } from '@comfyorg/litegraph'
import { app } from '../../scripts/app'
import { ComfyWidgets } from '../../scripts/widgets'

View File

@@ -1,9 +1,5 @@
import type { IContextMenuValue } from '@/lib/litegraph/src/litegraph'
import {
LGraphCanvas,
LGraphNode,
LiteGraph
} from '@/lib/litegraph/src/litegraph'
import type { IContextMenuValue } from '@comfyorg/litegraph'
import { LGraphCanvas, LGraphNode, LiteGraph } from '@comfyorg/litegraph'
import { app } from '../../scripts/app'
import { getWidgetConfig, mergeIfValid, setWidgetConfig } from './widgetInputs'

View File

@@ -1,4 +1,4 @@
import { LGraphCanvas, LiteGraph } from '@/lib/litegraph/src/litegraph'
import { LGraphCanvas, LiteGraph } from '@comfyorg/litegraph'
import { app } from '../../scripts/app'

View File

@@ -1,4 +1,4 @@
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import { LiteGraph } from '@comfyorg/litegraph'
import { app } from '../../scripts/app'
import { ComfyWidgets } from '../../scripts/widgets'

View File

@@ -1,3 +1,8 @@
import type { LGraphNode } from '@comfyorg/litegraph'
import type {
IBaseWidget,
IStringWidget
} from '@comfyorg/litegraph/dist/types/widgets'
import { MediaRecorder as ExtendableMediaRecorder } from 'extendable-media-recorder'
import { useChainCallback } from '@/composables/functional/useChainCallback'
@@ -5,11 +10,6 @@ import { useNodeDragAndDrop } from '@/composables/node/useNodeDragAndDrop'
import { useNodeFileInput } from '@/composables/node/useNodeFileInput'
import { useNodePaste } from '@/composables/node/useNodePaste'
import { t } from '@/i18n'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import type {
IBaseWidget,
IStringWidget
} from '@/lib/litegraph/src/types/widgets'
import type { ResultItemType } from '@/schemas/apiSchema'
import type { ComfyNodeDef } from '@/schemas/nodeDefSchema'
import type { DOMWidget } from '@/scripts/domWidget'

View File

@@ -1,17 +1,18 @@
import {
type CallbackParams,
useChainCallback
} from '@/composables/functional/useChainCallback'
import { LGraphNode, LiteGraph } from '@/lib/litegraph/src/litegraph'
import { LGraphNode, LiteGraph } from '@comfyorg/litegraph'
import type {
INodeInputSlot,
INodeOutputSlot,
ISlotType,
LLink,
Vector2
} from '@/lib/litegraph/src/litegraph'
import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events'
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
} from '@comfyorg/litegraph'
import type { CanvasPointerEvent } from '@comfyorg/litegraph/dist/types/events'
import type { IBaseWidget } from '@comfyorg/litegraph/dist/types/widgets'
import {
type CallbackParams,
useChainCallback
} from '@/composables/functional/useChainCallback'
import type { InputSpec } from '@/schemas/nodeDefSchema'
import { app } from '@/scripts/app'
import { ComfyWidgets, addValueControlWidgets } from '@/scripts/widgets'

View File

@@ -1,203 +0,0 @@
# API
This document is intended to provide a brief introduction to new Litegraph APIs.
<detail open>
<summary>
# CanvasPointer API
</summary>
CanvasPointer replaces much of the original pointer handling code. It provides a standard click, double-click, and drag UX for users.
<detail open>
<summary>
## Default behaviour changes
</summary>
- Dragging multiple items no longer requires that the shift key be held down
- Clicking an item when multiple nodes / etc are selected will still deselect everything else
- Clicking a connected link on an input no longer disconnects and reconnects it
- Double-clicking requires that both clicks occur nearby
- Provides much room for extension, configuration, and changes
#### Bug fixes
- Intermittent issue where clicking a node slightly displaces it
- Alt-clicking to add a reroute creates two undo steps
### Selecting multiple items
- `Ctrl + drag` - Begin multi-select
- `Ctrl + Shift + drag` - Add to selection
- `Ctrl + drag`, `Shift` - Alternate add to selection
- `Ctrl + drag`, `Alt` - Remove from selection
### Click "drift"
A small amount of buffering is performed between down/up events to prevent accidental micro-drag events. If either of the two controls are exceeded, the event will be considered a drag event, not a click.
- `buffterTime` is the maximum time that tiny movements can be ignored (Default: 150ms)
- `maxClickDrift` controls how far a click can drift from its down event before it is considered a drag (Default: 6)
### Double-click
When double clicking, the double click callback is executed shortly after one normal click callback (if present). At present, dragging from the second click simply invalidates the event - nothing will happen.
- `doubleClickTime` is the maximum time between two `down` events for them to be considered a double click (Default: 300ms)
- Distance between the two events must be less than `3 * maxClickDrift`
### Configuration
All above configuration is via class static.
```ts
CanvasPointer.bufferTime = 150
CanvasPointer.maxClickDrift = 6
CanvasPointer.doubleClickTime = 300
```
</detail>
<detail open>
<summary>
## Implementing
</summary>
Clicking, double-clicking, and dragging can now all be configured during the initial `pointerdown` event, and the correct callback(s) will be executed.
A click event can be as simple as:
```ts
if (node.isClickedInSpot(e.canvasX, e.canvasY))
this.pointer.onClick = () => node.gotClickInSpot()
```
Full usage can be seen in the old `processMouseDown` handler, which is still in place (several monkey patches in the wild).
### Registering a click or drag event
Example usage:
```typescript
const { pointer } = this
// Click / double click - executed on pointerup
pointer.onClick = e => node.executeClick(e)
pointer.onDoubleClick = node.gotDoubleClick
// Drag events - executed on pointermove
pointer.onDragStart = e => {
node.isBeingDragged = true
canvas.startedDragging(e)
}
pointer.onDrag = () => {}
// finally() is preferred where possible, as it is guaranteed to run
pointer.onDragEnd = () => {}
// Always run, regardless of outcome
pointer.finally = () => (node.isBeingDragged = false)
```
## Widgets
Adds `onPointerDown` callback to node widgets. A few benefits of the new API:
- Simplified usage
- Exposes callbacks like "double click", removing the need to time / measure multiple pointer events
- Unified UX - same API as used in the rest of Litegraph
- Honours the user's click speed and pointer accuracy settings
#### Usage
```ts
// Callbacks for each pointer action can be configured ahead of time
widget.onPointerDown = function (pointer, node, canvas) {
const e = pointer.eDown
const offsetFromNode = [e.canvasX - node.pos[0], e.canvasY - node.pos[1]]
// Click events - no overlap with drag events
pointer.onClick = upEvent => {
// Provides access to the whole lifecycle of events in every callback
console.log(pointer.eDown)
console.log(pointer.eMove ?? "Pointer didn't move")
console.log(pointer.eUp)
}
pointer.onDoubleClick = upEvent => this.customFunction(upEvent)
// Runs once before the first onDrag event
pointer.onDragStart = () => {}
// Receives every movement event
pointer.onDrag = moveEvent => {}
// The pointerup event of a drag
pointer.onDragEnd = upEvent => {}
// Semantics of a "finally" block (try/catch). Once set, the block always executes.
pointer.finally = () => {}
// Return true to cancel regular Litegraph handling of this click / drag
return true
}
```
</detail>
### TypeScript & JSDoc
In-IDE typing is available for use in at least mainstream editors. TypeScript definitions are available in the litegraph library.
```ts
/** @import { IWidget } from './path/to/litegraph/litegraph.d.ts' */
/** @type IWidget */
const widget = node.widgets[0]
widget.onPointerDown = function (pointer, node, canvas) {}
```
#### VS Code
![image](https://github.com/user-attachments/assets/e14afd02-247f-44dc-acbf-6333027cd488)
## Hovering over
Adds API for downstream consumers to handle custom cursors. A bitwise enum of items,
```typescript
type LGraphCanvasState = {
/** If `true`, pointer move events will set the canvas cursor style. */
shouldSetCursor: boolean,
/** Bit flags indicating what is currently below the pointer. */
hoveringOver: CanvasItem,
...
}
// Disable litegraph cursors
canvas.state.shouldSetCursor = false
// Checking state - bit operators
if (canvas.state.hoveringOver & CanvasItem.ResizeSe) element.style.cursor = 'se-resize'
```
</detail>
# Removed public interfaces
All are unused and incomplete. Have bugs beyond just typescript typing, and are (currently) not worth maintaining. If any of these features are desired down the track, they can be reimplemented.
- Live mode
- Subgraph
- `dragged_node`
## LiteGraph
These features have not been maintained, and would require refactoring / rewrites. As code search revealed them to be unused, they are being removed.
- addNodeMethod
- compareObjects
- auto_sort_node_types (option)

View File

@@ -1,62 +0,0 @@
- This codebase has extensive eslint autofix rules and IDEs are configured to use eslint as the format on save tool. Run ESLint instead of manually figuring out whitespace fixes or other trivial style concerns. Review the results and correct any remaining eslint errors.
- Take advantage of `TypedArray` `subarray` when appropriate.
- The `size` and `pos` properties of `Rectangle` share the same array buffer (`subarray`); they may be used to set the rectangles size and position.
- Prefer single line `if` syntax over adding curly braces, when the statement has a very concise expression and concise, single line statement.
- Do not replace `&&=` or `||=` with `=` when there is no reason to do so. If you do find a reason to remove either `&&=` or `||=`, leave a comment explaining why the removal occurred.
- You are allowed to research code on https://developer.mozilla.org/ and https://stackoverflow.com without asking.
- When adding features, always write vitest unit tests using cursor rules in @.cursor
- When writing methods, prefer returning idiomatic JavaScript `undefined` over `null`.
# Bash commands
- `npm run typecheck` Run the typechecker
- `npm run build` Build the project
- `npm run lint:fix` Run ESLint
# Code style
- Always prefer best practices when writing code.
- Write using concise, legible, and easily maintainable code.
- Avoid repetition where possible, but not at the expense of code legibility.
- Type assertions are an absolute last resort. In almost all cases, they are a crutch that leads to brittle code.
# Workflow
- Be sure to typecheck when youre done making a series of code changes
- Prefer running single tests, and not the whole test suite, for performance
# Testing Guidelines
## Avoiding Circular Dependencies in Tests
**CRITICAL**: When writing tests for subgraph-related code, always import from the barrel export to avoid circular dependency issues:
```typescript
// ✅ CORRECT - Use barrel import
import { LGraph, Subgraph, SubgraphNode } from "@/litegraph"
// ❌ WRONG - Direct imports cause circular dependency
import { LGraph } from "@/LGraph"
import { Subgraph } from "@/subgraph/Subgraph"
import { SubgraphNode } from "@/subgraph/SubgraphNode"
```
**Root cause**: `LGraph` and `Subgraph` have a circular dependency:
- `LGraph.ts` imports `Subgraph` (creates instances with `new Subgraph()`)
- `Subgraph.ts` extends `LGraph`
The barrel export (`@/litegraph`) handles this properly, but direct imports cause module loading failures.
## Test Setup for Subgraphs
Use the provided test helpers for consistent setup:
```typescript
import { createTestSubgraph, createTestSubgraphNode } from "./fixtures/subgraphHelpers"
function createTestSetup() {
const subgraph = createTestSubgraph()
const subgraphNode = createTestSubgraphNode(subgraph)
return { subgraph, subgraphNode }
}
```

View File

@@ -1,9 +0,0 @@
# Contribution Rules
There are some simple rules that everyone should follow:
### Do not commit files from build folder
> I usually have horrible merge conflicts when I upload the build version that take me too much time to solve, but I want to keep the build version in the repo, so I guess it would be better if only one of us does the built, which would be me.
> https://github.com/jagenjo/litegraph.js/pull/155#issuecomment-656602861
Those files will be updated by owner.

View File

@@ -1,19 +0,0 @@
Copyright (C) 2013 by Javi Agenjo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

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