mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-28 02:02:08 +00:00
Compare commits
46 Commits
sno-lint-i
...
manager/lo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
525a3b801f | ||
|
|
4842f08aa1 | ||
|
|
574a1db0c2 | ||
|
|
08f25dd8d2 | ||
|
|
424f6de901 | ||
|
|
2cf4dab010 | ||
|
|
2a8c474425 | ||
|
|
2827e78e54 | ||
|
|
84d6352406 | ||
|
|
a29019fd51 | ||
|
|
62a33a661f | ||
|
|
450c3b00a5 | ||
|
|
40bdc9fe79 | ||
|
|
becf55dbf2 | ||
|
|
f5744d224b | ||
|
|
3f598e46cc | ||
|
|
b4e4f4560e | ||
|
|
5733156fc8 | ||
|
|
0f282cc7fb | ||
|
|
799f13aa8b | ||
|
|
aeb948ae01 | ||
|
|
2fc6ae0a65 | ||
|
|
37a5add8cc | ||
|
|
b59ad87c98 | ||
|
|
69c660b3b7 | ||
|
|
88579c2a40 | ||
|
|
7ab247aa1d | ||
|
|
c78d03dd2c | ||
|
|
65785af348 | ||
|
|
ec4ad5ea92 | ||
|
|
e9ddf29507 | ||
|
|
fdd8564c07 | ||
|
|
d18081a54e | ||
|
|
45cc6ca2b4 | ||
|
|
c303a3f037 | ||
|
|
c90fd18ade | ||
|
|
2ed1704749 | ||
|
|
7d5a4d423e | ||
|
|
7aaa0f022e | ||
|
|
a132dad216 | ||
|
|
9dbdc6a72b | ||
|
|
7b228d693d | ||
|
|
547af0e043 | ||
|
|
4ca6220adf | ||
|
|
1e41c6dc45 | ||
|
|
5224c63bce |
@@ -111,50 +111,7 @@ echo "Last stable release: $LAST_STABLE"
|
||||
```
|
||||
7. **HUMAN ANALYSIS**: Review change summary and verify scope
|
||||
|
||||
### Step 3: Version Preview
|
||||
|
||||
**Version Preview:**
|
||||
- Current: `${CURRENT_VERSION}`
|
||||
- Proposed: Show exact version number
|
||||
- **CONFIRMATION REQUIRED**: Proceed with version `X.Y.Z`?
|
||||
|
||||
### Step 4: Security and Dependency Audit
|
||||
|
||||
1. Run security audit:
|
||||
```bash
|
||||
npm audit --audit-level moderate
|
||||
```
|
||||
2. Check for known vulnerabilities in dependencies
|
||||
3. Scan for hardcoded secrets or credentials:
|
||||
```bash
|
||||
git log -p ${BASE_TAG}..HEAD | grep -iE "(password|key|secret|token)" || echo "No sensitive data found"
|
||||
```
|
||||
4. Verify no sensitive data in recent commits
|
||||
5. **SECURITY REVIEW**: Address any critical findings before proceeding?
|
||||
|
||||
### Step 5: Pre-Release Testing
|
||||
|
||||
1. Run complete test suite:
|
||||
```bash
|
||||
npm run test:unit
|
||||
npm run test:component
|
||||
```
|
||||
2. Run type checking:
|
||||
```bash
|
||||
npm run typecheck
|
||||
```
|
||||
3. Run linting (may have issues with missing packages):
|
||||
```bash
|
||||
npm run lint || echo "Lint issues - verify if critical"
|
||||
```
|
||||
4. Test build process:
|
||||
```bash
|
||||
npm run build
|
||||
npm run build:types
|
||||
```
|
||||
5. **QUALITY GATE**: All tests and builds passing?
|
||||
|
||||
### Step 6: Breaking Change Analysis
|
||||
### Step 3: Breaking Change Analysis
|
||||
|
||||
1. Analyze API changes in:
|
||||
- Public TypeScript interfaces
|
||||
@@ -169,7 +126,7 @@ echo "Last stable release: $LAST_STABLE"
|
||||
3. Generate breaking change summary
|
||||
4. **COMPATIBILITY REVIEW**: Breaking changes documented and justified?
|
||||
|
||||
### Step 7: Analyze Dependency Updates
|
||||
### Step 4: Analyze Dependency Updates
|
||||
|
||||
1. **Check significant dependency updates:**
|
||||
```bash
|
||||
@@ -195,7 +152,117 @@ echo "Last stable release: $LAST_STABLE"
|
||||
done
|
||||
```
|
||||
|
||||
### Step 8: Generate Comprehensive Release Notes
|
||||
### Step 5: Generate GTM Feature Summary
|
||||
|
||||
1. **Collect PR data for analysis:**
|
||||
```bash
|
||||
# Get list of PR numbers from commits
|
||||
PR_NUMBERS=$(git log ${BASE_TAG}..HEAD --oneline --no-merges --first-parent | \
|
||||
grep -oE "#[0-9]+" | tr -d '#' | sort -u)
|
||||
|
||||
# Save PR data for each PR
|
||||
echo "[" > prs-${NEW_VERSION}.json
|
||||
first=true
|
||||
for PR in $PR_NUMBERS; do
|
||||
[[ "$first" == true ]] && first=false || echo "," >> prs-${NEW_VERSION}.json
|
||||
gh pr view $PR --json number,title,author,body,labels 2>/dev/null >> prs-${NEW_VERSION}.json || echo "{}" >> prs-${NEW_VERSION}.json
|
||||
done
|
||||
echo "]" >> prs-${NEW_VERSION}.json
|
||||
```
|
||||
|
||||
2. **Analyze for GTM-worthy features:**
|
||||
```
|
||||
<task>
|
||||
Review these PRs to identify features worthy of marketing attention.
|
||||
|
||||
A feature is GTM-worthy if it meets ALL of these criteria:
|
||||
- Introduces a NEW capability users didn't have before (not just improvements)
|
||||
- Would be a compelling reason for users to upgrade to this version
|
||||
- Can be demonstrated visually or has clear before/after comparison
|
||||
- Affects a significant portion of the user base
|
||||
|
||||
NOT GTM-worthy:
|
||||
- Bug fixes (even important ones)
|
||||
- Minor UI tweaks or color changes
|
||||
- Performance improvements without user-visible impact
|
||||
- Internal refactoring
|
||||
- Small convenience features
|
||||
- Features that only improve existing functionality marginally
|
||||
|
||||
For each GTM-worthy feature, note:
|
||||
- PR number, title, and author
|
||||
- Media links from the PR description
|
||||
- One compelling sentence on why users should care
|
||||
|
||||
If there are no GTM-worthy features, just say "No marketing-worthy features in this release."
|
||||
</task>
|
||||
|
||||
PR data: [contents of prs-${NEW_VERSION}.json]
|
||||
```
|
||||
|
||||
3. **Generate GTM notification:**
|
||||
```bash
|
||||
# Save to gtm-summary-${NEW_VERSION}.md based on analysis
|
||||
# If GTM-worthy features exist, include them with testing instructions
|
||||
# If not, note that this is a maintenance/bug fix release
|
||||
|
||||
# Check if notification is needed
|
||||
if grep -q "No marketing-worthy features" gtm-summary-${NEW_VERSION}.md; then
|
||||
echo "✅ No GTM notification needed for this release"
|
||||
echo "📄 Summary saved to: gtm-summary-${NEW_VERSION}.md"
|
||||
else
|
||||
echo "📋 GTM summary saved to: gtm-summary-${NEW_VERSION}.md"
|
||||
echo "📤 Share this file in #gtm channel to notify the team"
|
||||
fi
|
||||
```
|
||||
|
||||
### Step 6: Version Preview
|
||||
|
||||
**Version Preview:**
|
||||
- Current: `${CURRENT_VERSION}`
|
||||
- Proposed: Show exact version number based on analysis:
|
||||
- Major version if breaking changes detected
|
||||
- Minor version if new features added
|
||||
- Patch version if only bug fixes
|
||||
- **CONFIRMATION REQUIRED**: Proceed with version `X.Y.Z`?
|
||||
|
||||
### Step 7: Security and Dependency Audit
|
||||
|
||||
1. Run security audit:
|
||||
```bash
|
||||
npm audit --audit-level moderate
|
||||
```
|
||||
2. Check for known vulnerabilities in dependencies
|
||||
3. Scan for hardcoded secrets or credentials:
|
||||
```bash
|
||||
git log -p ${BASE_TAG}..HEAD | grep -iE "(password|key|secret|token)" || echo "No sensitive data found"
|
||||
```
|
||||
4. Verify no sensitive data in recent commits
|
||||
5. **SECURITY REVIEW**: Address any critical findings before proceeding?
|
||||
|
||||
### Step 8: Pre-Release Testing
|
||||
|
||||
1. Run complete test suite:
|
||||
```bash
|
||||
npm run test:unit
|
||||
npm run test:component
|
||||
```
|
||||
2. Run type checking:
|
||||
```bash
|
||||
npm run typecheck
|
||||
```
|
||||
3. Run linting (may have issues with missing packages):
|
||||
```bash
|
||||
npm run lint || echo "Lint issues - verify if critical"
|
||||
```
|
||||
4. Test build process:
|
||||
```bash
|
||||
npm run build
|
||||
npm run build:types
|
||||
```
|
||||
5. **QUALITY GATE**: All tests and builds passing?
|
||||
|
||||
### Step 9: Generate Comprehensive Release Notes
|
||||
|
||||
1. Extract commit messages since base release:
|
||||
```bash
|
||||
@@ -257,7 +324,7 @@ echo "Last stable release: $LAST_STABLE"
|
||||
- Ensure consistent bullet format: `- Description (#PR_NUMBER)`
|
||||
5. **CONTENT REVIEW**: Release notes follow standard format?
|
||||
|
||||
### Step 9: Create Version Bump PR
|
||||
### Step 10: Create Version Bump PR
|
||||
|
||||
**For standard version bumps (patch/minor/major):**
|
||||
```bash
|
||||
@@ -303,7 +370,7 @@ echo "Workflow triggered. Waiting for PR creation..."
|
||||
```
|
||||
4. **PR REVIEW**: Version bump PR created with standardized release notes?
|
||||
|
||||
### Step 10: Critical Release PR Verification
|
||||
### Step 11: Critical Release PR Verification
|
||||
|
||||
1. **CRITICAL**: Verify PR has "Release" label:
|
||||
```bash
|
||||
@@ -325,7 +392,7 @@ echo "Workflow triggered. Waiting for PR creation..."
|
||||
```
|
||||
7. **FINAL CODE REVIEW**: Release label present and no [skip ci]?
|
||||
|
||||
### Step 11: Pre-Merge Validation
|
||||
### Step 12: Pre-Merge Validation
|
||||
|
||||
1. **Review Requirements**: Release PRs require approval
|
||||
2. Monitor CI checks - watch for update-locales
|
||||
@@ -333,7 +400,7 @@ echo "Workflow triggered. Waiting for PR creation..."
|
||||
4. Check no new commits to main since PR creation
|
||||
5. **DEPLOYMENT READINESS**: Ready to merge?
|
||||
|
||||
### Step 12: Execute Release
|
||||
### Step 13: Execute Release
|
||||
|
||||
1. **FINAL CONFIRMATION**: Merge PR to trigger release?
|
||||
2. Merge the Release PR:
|
||||
@@ -366,7 +433,7 @@ echo "Workflow triggered. Waiting for PR creation..."
|
||||
gh run watch ${WORKFLOW_RUN_ID}
|
||||
```
|
||||
|
||||
### Step 13: Enhance GitHub Release
|
||||
### Step 14: Enhance GitHub Release
|
||||
|
||||
1. Wait for automatic release creation:
|
||||
```bash
|
||||
@@ -394,7 +461,7 @@ echo "Workflow triggered. Waiting for PR creation..."
|
||||
gh release view v${NEW_VERSION}
|
||||
```
|
||||
|
||||
### Step 14: Verify Multi-Channel Distribution
|
||||
### Step 15: Verify Multi-Channel Distribution
|
||||
|
||||
1. **GitHub Release:**
|
||||
```bash
|
||||
@@ -432,7 +499,7 @@ echo "Workflow triggered. Waiting for PR creation..."
|
||||
|
||||
4. **DISTRIBUTION VERIFICATION**: All channels published successfully?
|
||||
|
||||
### Step 15: Post-Release Monitoring Setup
|
||||
### Step 16: Post-Release Monitoring Setup
|
||||
|
||||
1. **Monitor immediate release health:**
|
||||
```bash
|
||||
@@ -502,11 +569,49 @@ echo "Workflow triggered. Waiting for PR creation..."
|
||||
## Files Generated
|
||||
- \`release-notes-${NEW_VERSION}.md\` - Comprehensive release notes
|
||||
- \`post-release-checklist.md\` - Follow-up tasks
|
||||
- \`gtm-summary-${NEW_VERSION}.md\` - Marketing team notification
|
||||
EOF
|
||||
```
|
||||
|
||||
4. **RELEASE COMPLETION**: All post-release setup completed?
|
||||
|
||||
### Step 17: Create Release Summary
|
||||
|
||||
1. **Create comprehensive release summary:**
|
||||
```bash
|
||||
cat > release-summary-${NEW_VERSION}.md << EOF
|
||||
# Release Summary: ComfyUI Frontend v${NEW_VERSION}
|
||||
|
||||
**Released:** $(date)
|
||||
**Type:** ${VERSION_TYPE}
|
||||
**Duration:** ~${RELEASE_DURATION} minutes
|
||||
**Release Commit:** ${RELEASE_COMMIT}
|
||||
|
||||
## Metrics
|
||||
- **Commits Included:** ${COMMITS_COUNT}
|
||||
- **Contributors:** ${CONTRIBUTORS_COUNT}
|
||||
- **Files Changed:** ${FILES_CHANGED}
|
||||
- **Lines Added/Removed:** +${LINES_ADDED}/-${LINES_REMOVED}
|
||||
|
||||
## Distribution Status
|
||||
- ✅ GitHub Release: Published
|
||||
- ✅ PyPI Package: Available
|
||||
- ✅ npm Types: Available
|
||||
|
||||
## Next Steps
|
||||
- Monitor for 24-48 hours
|
||||
- Address any critical issues immediately
|
||||
- Plan next release cycle
|
||||
|
||||
## Files Generated
|
||||
- \`release-notes-${NEW_VERSION}.md\` - Comprehensive release notes
|
||||
- \`post-release-checklist.md\` - Follow-up tasks
|
||||
- \`gtm-summary-${NEW_VERSION}.md\` - Marketing team notification
|
||||
EOF
|
||||
```
|
||||
|
||||
2. **RELEASE COMPLETION**: All steps completed successfully?
|
||||
|
||||
## Advanced Safety Features
|
||||
|
||||
### Rollback Procedures
|
||||
|
||||
@@ -13,6 +13,10 @@ module.exports = defineConfig({
|
||||
reference: `Special names to keep untranslated: flux, photomaker, clip, vae, cfg, stable audio, stable cascade, stable zero, controlnet, lora, HiDream.
|
||||
'latent' is the short form of 'latent space'.
|
||||
'mask' is in the context of image processing.
|
||||
Note: For Traditional Chinese (Taiwan), use Taiwan-specific terminology and traditional characters.
|
||||
|
||||
IMPORTANT Chinese Translation Guidelines:
|
||||
- For 'zh' locale: Use ONLY Simplified Chinese characters (简体中文). Common examples: 节点 (not 節點), 画布 (not 畫布), 图像 (not 圖像), 选择 (not 選擇), 减小 (not 減小).
|
||||
- For 'zh-TW' locale: Use ONLY Traditional Chinese characters (繁體中文) with Taiwan-specific terminology.
|
||||
- NEVER mix Simplified and Traditional Chinese characters within the same locale.
|
||||
`
|
||||
});
|
||||
|
||||
@@ -50,7 +50,7 @@ export class Topbar {
|
||||
workflowName: string,
|
||||
command: 'Save' | 'Save As' | 'Export'
|
||||
) {
|
||||
await this.triggerTopbarCommand(['Workflow', command])
|
||||
await this.triggerTopbarCommand(['File', command])
|
||||
await this.getSaveDialog().fill(workflowName)
|
||||
await this.page.keyboard.press('Enter')
|
||||
|
||||
@@ -72,8 +72,8 @@ export class Topbar {
|
||||
}
|
||||
|
||||
async triggerTopbarCommand(path: string[]) {
|
||||
if (path.length < 2) {
|
||||
throw new Error('Path is too short')
|
||||
if (path.length < 1) {
|
||||
throw new Error('Path cannot be empty')
|
||||
}
|
||||
|
||||
const menu = await this.openTopbarMenu()
|
||||
@@ -85,6 +85,13 @@ export class Topbar {
|
||||
.locator('.p-tieredmenu-item')
|
||||
.filter({ has: topLevelMenuItem })
|
||||
await topLevelMenu.waitFor({ state: 'visible' })
|
||||
|
||||
// Handle top-level commands (like "New")
|
||||
if (path.length === 1) {
|
||||
await topLevelMenuItem.click()
|
||||
return
|
||||
}
|
||||
|
||||
await topLevelMenu.hover()
|
||||
|
||||
let currentMenu = topLevelMenu
|
||||
|
||||
@@ -268,10 +268,7 @@ test.describe('Group Node', () => {
|
||||
await comfyPage.setSetting('Comfy.ConfirmClear', false)
|
||||
|
||||
// Clear workflow
|
||||
await comfyPage.menu.topbar.triggerTopbarCommand([
|
||||
'Edit',
|
||||
'Clear Workflow'
|
||||
])
|
||||
await comfyPage.executeCommand('Comfy.ClearWorkflow')
|
||||
|
||||
await comfyPage.ctrlV()
|
||||
await verifyNodeLoaded(comfyPage, 1)
|
||||
@@ -280,7 +277,7 @@ test.describe('Group Node', () => {
|
||||
test('Copies and pastes group node into a newly created blank workflow', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New'])
|
||||
await comfyPage.menu.topbar.triggerTopbarCommand(['New'])
|
||||
await comfyPage.ctrlV()
|
||||
await verifyNodeLoaded(comfyPage, 1)
|
||||
})
|
||||
@@ -296,7 +293,7 @@ test.describe('Group Node', () => {
|
||||
test('Serializes group node after copy and paste across workflows', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New'])
|
||||
await comfyPage.menu.topbar.triggerTopbarCommand(['New'])
|
||||
await comfyPage.ctrlV()
|
||||
const currentGraphState = await comfyPage.page.evaluate(() =>
|
||||
window['app'].graph.serialize()
|
||||
|
||||
@@ -684,7 +684,7 @@ test.describe('Load workflow', () => {
|
||||
workflowA = generateUniqueFilename()
|
||||
await comfyPage.menu.topbar.saveWorkflow(workflowA)
|
||||
workflowB = generateUniqueFilename()
|
||||
await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New'])
|
||||
await comfyPage.menu.topbar.triggerTopbarCommand(['New'])
|
||||
await comfyPage.menu.topbar.saveWorkflow(workflowB)
|
||||
|
||||
// Wait for localStorage to persist the workflow paths before reloading
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 70 KiB |
@@ -73,9 +73,80 @@ test.describe('Menu', () => {
|
||||
expect(isTextCutoff).toBe(false)
|
||||
})
|
||||
|
||||
test('Clicking on active state items does not close menu', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Open the menu
|
||||
await comfyPage.menu.topbar.openTopbarMenu()
|
||||
const menu = comfyPage.page.locator('.comfy-command-menu')
|
||||
|
||||
// Navigate to View menu
|
||||
const viewMenuItem = comfyPage.page.locator(
|
||||
'.p-menubar-item-label:text-is("View")'
|
||||
)
|
||||
await viewMenuItem.hover()
|
||||
|
||||
// Wait for submenu to appear
|
||||
const viewSubmenu = comfyPage.page
|
||||
.locator('.p-tieredmenu-submenu:visible')
|
||||
.first()
|
||||
await viewSubmenu.waitFor({ state: 'visible' })
|
||||
|
||||
// Find Bottom Panel menu item
|
||||
const bottomPanelMenuItem = viewSubmenu
|
||||
.locator('.p-tieredmenu-item:has-text("Bottom Panel")')
|
||||
.first()
|
||||
const bottomPanelItem = bottomPanelMenuItem.locator(
|
||||
'.p-menubar-item-label:text-is("Bottom Panel")'
|
||||
)
|
||||
await bottomPanelItem.waitFor({ state: 'visible' })
|
||||
|
||||
// Get checkmark icon element
|
||||
const checkmark = bottomPanelMenuItem.locator('.pi-check')
|
||||
|
||||
// Check initial state of bottom panel (it's initially hidden)
|
||||
const bottomPanel = comfyPage.page.locator('.bottom-panel')
|
||||
await expect(bottomPanel).not.toBeVisible()
|
||||
|
||||
// Checkmark should be invisible initially (panel is hidden)
|
||||
await expect(checkmark).toHaveClass(/invisible/)
|
||||
|
||||
// Click Bottom Panel to toggle it on
|
||||
await bottomPanelItem.click()
|
||||
|
||||
// Verify menu is still visible after clicking
|
||||
await expect(menu).toBeVisible()
|
||||
await expect(viewSubmenu).toBeVisible()
|
||||
|
||||
// Verify bottom panel is now visible
|
||||
await expect(bottomPanel).toBeVisible()
|
||||
|
||||
// Checkmark should now be visible (panel is shown)
|
||||
await expect(checkmark).not.toHaveClass(/invisible/)
|
||||
|
||||
// Click Bottom Panel again to toggle it off
|
||||
await bottomPanelItem.click()
|
||||
|
||||
// Verify menu is still visible after second click
|
||||
await expect(menu).toBeVisible()
|
||||
await expect(viewSubmenu).toBeVisible()
|
||||
|
||||
// Verify bottom panel is hidden again
|
||||
await expect(bottomPanel).not.toBeVisible()
|
||||
|
||||
// Checkmark should be invisible again (panel is hidden)
|
||||
await expect(checkmark).toHaveClass(/invisible/)
|
||||
|
||||
// Click outside to close menu
|
||||
await comfyPage.page.locator('body').click({ position: { x: 10, y: 10 } })
|
||||
|
||||
// Verify menu is now closed
|
||||
await expect(menu).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('Displays keybinding next to item', async ({ comfyPage }) => {
|
||||
await comfyPage.menu.topbar.openTopbarMenu()
|
||||
const workflowMenuItem = comfyPage.menu.topbar.getMenuItem('Workflow')
|
||||
const workflowMenuItem = comfyPage.menu.topbar.getMenuItem('File')
|
||||
await workflowMenuItem.hover()
|
||||
const exportTag = comfyPage.page.locator('.keybinding-tag', {
|
||||
hasText: 'Ctrl + s'
|
||||
|
||||
@@ -18,7 +18,7 @@ test.describe('Reroute Node', () => {
|
||||
[workflowName]: workflowName
|
||||
})
|
||||
await comfyPage.setup()
|
||||
await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New'])
|
||||
await comfyPage.menu.topbar.triggerTopbarCommand(['New'])
|
||||
|
||||
// Insert the workflow
|
||||
const workflowsTab = comfyPage.menu.workflowsTab
|
||||
|
||||
@@ -63,7 +63,7 @@ test.describe('Workflow Tab Thumbnails', () => {
|
||||
test('Should show thumbnail when hovering over a non-active tab', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New'])
|
||||
await comfyPage.menu.topbar.triggerTopbarCommand(['New'])
|
||||
const thumbnailImg = await getTabThumbnailImage(
|
||||
comfyPage,
|
||||
0,
|
||||
@@ -73,7 +73,7 @@ test.describe('Workflow Tab Thumbnails', () => {
|
||||
})
|
||||
|
||||
test('Should not show thumbnail for active tab', async ({ comfyPage }) => {
|
||||
await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New'])
|
||||
await comfyPage.menu.topbar.triggerTopbarCommand(['New'])
|
||||
const thumbnailImg = await getTabThumbnailImage(
|
||||
comfyPage,
|
||||
1,
|
||||
@@ -105,7 +105,7 @@ test.describe('Workflow Tab Thumbnails', () => {
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
// Create a new workflow (tab 1) which will be empty
|
||||
await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New'])
|
||||
await comfyPage.menu.topbar.triggerTopbarCommand(['New'])
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
// Now we have two tabs: tab 0 (default workflow with nodes) and tab 1 (empty)
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"version": "1.26.2",
|
||||
"version": "1.26.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"version": "1.26.2",
|
||||
"version": "1.26.4",
|
||||
"license": "GPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@alloc/quick-lru": "^5.2.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"private": true,
|
||||
"version": "1.26.2",
|
||||
"version": "1.26.4",
|
||||
"type": "module",
|
||||
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
|
||||
"homepage": "https://comfy.org",
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
>
|
||||
<div class="shortcut-info flex-grow pr-4">
|
||||
<div class="shortcut-name text-sm font-medium">
|
||||
{{ command.label || command.id }}
|
||||
{{ t(`commands.${normalizeI18nKey(command.id)}.label`) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -50,6 +50,7 @@ import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import type { ComfyCommandImpl } from '@/stores/commandStore'
|
||||
import { normalizeI18nKey } from '@/utils/formatUtil'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
|
||||
131
src/components/common/DotSpinner.vue
Normal file
131
src/components/common/DotSpinner.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<div
|
||||
class="inline-flex items-center justify-center"
|
||||
:style="{ width: size + 'px', height: size + 'px' }"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
:width="size"
|
||||
:height="size"
|
||||
viewBox="0 0 14 14"
|
||||
fill="none"
|
||||
class="animate-spin"
|
||||
:style="{ animationDuration: duration }"
|
||||
>
|
||||
<g clip-path="url(#clip0_776_9582)">
|
||||
<!-- Top dot -->
|
||||
<path
|
||||
class="dot-animation"
|
||||
style="animation-delay: 0s"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M7 2.21053C7.61042 2.21053 8.10526 1.71568 8.10526 1.10526C8.10526 0.494843 7.61042 0 7 0C6.38958 0 5.89474 0.494843 5.89474 1.10526C5.89474 1.71568 6.38958 2.21053 7 2.21053Z"
|
||||
:fill="color"
|
||||
/>
|
||||
<!-- Left dot -->
|
||||
<path
|
||||
class="dot-animation"
|
||||
style="animation-delay: 0.25s"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M2.21053 7C2.21053 7.61042 1.71568 8.10526 1.10526 8.10526C0.494843 8.10526 0 7.61042 0 7C0 6.38958 0.494843 5.89474 1.10526 5.89474C1.71568 5.89474 2.21053 6.38958 2.21053 7Z"
|
||||
:fill="color"
|
||||
/>
|
||||
<!-- Right dot -->
|
||||
<path
|
||||
class="dot-animation"
|
||||
style="animation-delay: 0.5s"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M14 7C14 7.61042 13.5052 8.10526 12.8947 8.10526C12.2843 8.10526 11.7895 7.61042 11.7895 7C11.7895 6.38958 12.2843 5.89474 12.8947 5.89474C13.5052 5.89474 14 6.38958 14 7Z"
|
||||
:fill="color"
|
||||
/>
|
||||
<!-- Bottom dot -->
|
||||
<path
|
||||
class="dot-animation"
|
||||
style="animation-delay: 0.75s"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M8.10526 12.8947C8.10526 13.5052 7.61041 14 6.99999 14C6.38957 14 5.89473 13.5052 5.89473 12.8947C5.89473 12.2843 6.38957 11.7895 6.99999 11.7895C7.61041 11.7895 8.10526 12.2843 8.10526 12.8947Z"
|
||||
:fill="color"
|
||||
/>
|
||||
<!-- Top-left dot -->
|
||||
<path
|
||||
class="dot-animation"
|
||||
style="animation-delay: 0.125s"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M2.05039 3.61349C2.48203 4.04513 3.18184 4.04513 3.61347 3.61349C4.0451 3.18186 4.0451 2.48205 3.61347 2.05042C3.18184 1.61878 2.48203 1.61878 2.05039 2.05042C1.61876 2.48205 1.61876 3.18186 2.05039 3.61349Z"
|
||||
:fill="color"
|
||||
/>
|
||||
<!-- Bottom-right dot -->
|
||||
<path
|
||||
class="dot-animation"
|
||||
style="animation-delay: 0.625s"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M11.9496 11.9496C11.518 12.3812 10.8182 12.3812 10.3865 11.9496C9.9549 11.5179 9.9549 10.8181 10.3865 10.3865C10.8182 9.95485 11.518 9.95485 11.9496 10.3865C12.3812 10.8181 12.3812 11.5179 11.9496 11.9496Z"
|
||||
:fill="color"
|
||||
/>
|
||||
<!-- Bottom-left dot -->
|
||||
<path
|
||||
class="dot-animation"
|
||||
style="animation-delay: 0.875s"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M2.05039 11.9496C2.48203 12.3812 3.18184 12.3812 3.61347 11.9496C4.0451 11.5179 4.0451 10.8181 3.61347 10.3865C3.18184 9.95485 2.48203 9.95485 2.05039 10.3865C1.61876 10.8181 1.61876 11.5179 2.05039 11.9496Z"
|
||||
:fill="color"
|
||||
/>
|
||||
<!-- Top-right dot -->
|
||||
<path
|
||||
class="dot-animation"
|
||||
style="animation-delay: 0.375s"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M11.9496 3.61349C11.518 4.04513 10.8182 4.04513 10.3865 3.61349C9.9549 3.18186 9.9549 2.48205 10.3865 2.05042C10.8182 1.61878 11.518 1.61878 11.9496 2.05042C12.3812 2.48205 12.3812 3.18186 11.9496 3.61349Z"
|
||||
:fill="color"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_776_9582">
|
||||
<rect width="14" height="14" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
|
||||
|
||||
const { size = 24, duration = '2s' } = defineProps<{
|
||||
size?: number
|
||||
duration?: string
|
||||
}>()
|
||||
|
||||
const colorPaletteStore = useColorPaletteStore()
|
||||
|
||||
const color = computed(() =>
|
||||
colorPaletteStore.completedActivePalette.light_theme ? '#2C2B30' : '#D4D4D4'
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dot-animation {
|
||||
animation: dot-pulse 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes dot-pulse {
|
||||
0%,
|
||||
80%,
|
||||
100% {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
40% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -31,7 +31,7 @@
|
||||
</div>
|
||||
</template>
|
||||
</ListBox>
|
||||
<div v-if="isManagerInstalled" class="flex justify-end py-3">
|
||||
<div v-if="!isLegacyManager" class="flex justify-end py-3">
|
||||
<PackInstallButton
|
||||
:disabled="isLoading || !!error || missingNodePacks.length === 0"
|
||||
:node-packs="missingNodePacks"
|
||||
@@ -45,14 +45,12 @@
|
||||
<script setup lang="ts">
|
||||
import Button from 'primevue/button'
|
||||
import ListBox from 'primevue/listbox'
|
||||
import { computed } from 'vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
|
||||
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
|
||||
import MissingCoreNodesMessage from '@/components/dialog/content/MissingCoreNodesMessage.vue'
|
||||
import PackInstallButton from '@/components/dialog/content/manager/button/PackInstallButton.vue'
|
||||
import { useMissingNodes } from '@/composables/nodePack/useMissingNodes'
|
||||
import { useComfyManagerService } from '@/services/comfyManagerService'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { useAboutPanelStore } from '@/stores/aboutPanelStore'
|
||||
import type { MissingNodeType } from '@/types/comfy'
|
||||
import { ManagerTab } from '@/types/comfyManagerTypes'
|
||||
|
||||
@@ -60,22 +58,11 @@ const props = defineProps<{
|
||||
missingNodeTypes: MissingNodeType[]
|
||||
}>()
|
||||
|
||||
const aboutPanelStore = useAboutPanelStore()
|
||||
|
||||
// Get missing node packs from workflow with loading and error states
|
||||
const { missingNodePacks, isLoading, error, missingCoreNodes } =
|
||||
useMissingNodes()
|
||||
|
||||
// Determines if ComfyUI-Manager is installed by checking for its badge in the about panel
|
||||
// This allows us to conditionally show the Manager button only when the extension is available
|
||||
// TODO: Remove this check when Manager functionality is fully migrated into core
|
||||
const isManagerInstalled = computed(() => {
|
||||
return aboutPanelStore.badges.some(
|
||||
(badge) =>
|
||||
badge.label.includes('ComfyUI-Manager') ||
|
||||
badge.url.includes('ComfyUI-Manager')
|
||||
)
|
||||
})
|
||||
const isLegacyManager = ref(false)
|
||||
|
||||
const uniqueNodes = computed(() => {
|
||||
const seenTypes = new Set()
|
||||
@@ -103,6 +90,13 @@ const openManager = () => {
|
||||
initialTab: ManagerTab.Missing
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const isLegacyResponse = await useComfyManagerService().isLegacyManagerUI()
|
||||
if (isLegacyResponse?.is_legacy_manager_ui) {
|
||||
isLegacyManager.value = true
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
82
src/components/dialog/content/manager/ManagerHeader.test.ts
Normal file
82
src/components/dialog/content/manager/ManagerHeader.test.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createPinia } from 'pinia'
|
||||
import PrimeVue from 'primevue/config'
|
||||
import Tag from 'primevue/tag'
|
||||
import Tooltip from 'primevue/tooltip'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import enMessages from '@/locales/en/main.json'
|
||||
|
||||
import ManagerHeader from './ManagerHeader.vue'
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages: {
|
||||
en: enMessages
|
||||
}
|
||||
})
|
||||
|
||||
describe('ManagerHeader', () => {
|
||||
const createWrapper = () => {
|
||||
return mount(ManagerHeader, {
|
||||
global: {
|
||||
plugins: [createPinia(), PrimeVue, i18n],
|
||||
directives: {
|
||||
tooltip: Tooltip
|
||||
},
|
||||
components: {
|
||||
Tag
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
it('renders the component title', () => {
|
||||
const wrapper = createWrapper()
|
||||
|
||||
expect(wrapper.find('h2').text()).toBe(
|
||||
enMessages.manager.discoverCommunityContent
|
||||
)
|
||||
})
|
||||
|
||||
it('displays the legacy manager UI tag', () => {
|
||||
const wrapper = createWrapper()
|
||||
|
||||
const tag = wrapper.find('[data-pc-name="tag"]')
|
||||
expect(tag.exists()).toBe(true)
|
||||
expect(tag.text()).toContain(enMessages.manager.legacyManagerUI)
|
||||
})
|
||||
|
||||
it('applies info severity to the tag', () => {
|
||||
const wrapper = createWrapper()
|
||||
|
||||
const tag = wrapper.find('[data-pc-name="tag"]')
|
||||
expect(tag.classes()).toContain('p-tag-info')
|
||||
})
|
||||
|
||||
it('displays info icon in the tag', () => {
|
||||
const wrapper = createWrapper()
|
||||
|
||||
const icon = wrapper.find('.pi-info-circle')
|
||||
expect(icon.exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('has cursor-help class on the tag', () => {
|
||||
const wrapper = createWrapper()
|
||||
|
||||
const tag = wrapper.find('[data-pc-name="tag"]')
|
||||
expect(tag.classes()).toContain('cursor-help')
|
||||
})
|
||||
|
||||
it('has proper structure with flex container', () => {
|
||||
const wrapper = createWrapper()
|
||||
|
||||
const flexContainer = wrapper.find('.flex.justify-end.ml-auto.pr-4')
|
||||
expect(flexContainer.exists()).toBe(true)
|
||||
|
||||
const tag = flexContainer.find('[data-pc-name="tag"]')
|
||||
expect(tag.exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -4,6 +4,22 @@
|
||||
<h2 class="text-lg font-normal text-left">
|
||||
{{ $t('manager.discoverCommunityContent') }}
|
||||
</h2>
|
||||
<div class="flex justify-end ml-auto pr-4">
|
||||
<Tag
|
||||
v-tooltip.left="$t('manager.legacyManagerUIDescription')"
|
||||
severity="info"
|
||||
icon="pi pi-info-circle"
|
||||
:value="$t('manager.legacyManagerUI')"
|
||||
class="cursor-help"
|
||||
:pt="{
|
||||
root: { class: 'text-xs' }
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Tag from 'primevue/tag'
|
||||
</script>
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
:loading="isInstalling"
|
||||
:loading-message="$t('g.installing')"
|
||||
@action="installAllPacks"
|
||||
@click="onClick"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -37,10 +36,6 @@ const { nodePacks, variant, label } = defineProps<{
|
||||
|
||||
const isInstalling = inject(IsInstallingKey, ref(false))
|
||||
|
||||
const onClick = (): void => {
|
||||
isInstalling.value = true
|
||||
}
|
||||
|
||||
const managerStore = useComfyManagerStore()
|
||||
|
||||
const createPayload = (installItem: NodePack) => {
|
||||
@@ -65,8 +60,6 @@ const installPack = (item: NodePack) =>
|
||||
const installAllPacks = async () => {
|
||||
if (!nodePacks?.length) return
|
||||
|
||||
isInstalling.value = true
|
||||
|
||||
const uninstalledPacks = nodePacks.filter(
|
||||
(pack) => !managerStore.isPackInstalled(pack.id)
|
||||
)
|
||||
|
||||
@@ -84,10 +84,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { whenever } from '@vueuse/core'
|
||||
import Card from 'primevue/card'
|
||||
import ProgressSpinner from 'primevue/progressspinner'
|
||||
import { computed, provide, ref } from 'vue'
|
||||
import { computed, provide } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import PackVersionBadge from '@/components/dialog/content/manager/PackVersionBadge.vue'
|
||||
@@ -114,18 +113,17 @@ const isLightTheme = computed(
|
||||
() => colorPaletteStore.completedActivePalette.light_theme
|
||||
)
|
||||
|
||||
const isInstalling = ref(false)
|
||||
provide(IsInstallingKey, isInstalling)
|
||||
const { isPackInstalled, isPackEnabled, isPackInstalling } =
|
||||
useComfyManagerStore()
|
||||
|
||||
const { isPackInstalled, isPackEnabled } = useComfyManagerStore()
|
||||
const isInstalling = computed(() => isPackInstalling(nodePack?.id))
|
||||
provide(IsInstallingKey, isInstalling)
|
||||
|
||||
const isInstalled = computed(() => isPackInstalled(nodePack?.id))
|
||||
const isDisabled = computed(
|
||||
() => isInstalled.value && !isPackEnabled(nodePack?.id)
|
||||
)
|
||||
|
||||
whenever(isInstalled, () => (isInstalling.value = false))
|
||||
|
||||
const nodesCount = computed(() =>
|
||||
isMergedNodePack(nodePack) ? nodePack.comfy_nodes?.length : undefined
|
||||
)
|
||||
|
||||
@@ -1,40 +1,47 @@
|
||||
<template>
|
||||
<div
|
||||
class="w-full px-6 py-4 shadow-lg flex items-center justify-between"
|
||||
class="w-full px-6 py-2 shadow-lg flex items-center justify-between"
|
||||
:class="{
|
||||
'rounded-t-none': progressDialogContent.isExpanded,
|
||||
'rounded-lg': !progressDialogContent.isExpanded
|
||||
}"
|
||||
>
|
||||
<div class="justify-center text-sm font-bold leading-none">
|
||||
<div class="flex items-center text-base leading-none">
|
||||
<div class="flex items-center">
|
||||
<template v-if="isInProgress">
|
||||
<i class="pi pi-spin pi-spinner mr-2 text-3xl" />
|
||||
<!-- 1. Queue running (install/enable/disable etc.) -->
|
||||
<template v-if="isQueueRunning">
|
||||
<DotSpinner duration="1s" class="mr-2" />
|
||||
<span>{{ currentTaskName }}</span>
|
||||
</template>
|
||||
<!-- 3. Restarting -->
|
||||
<template v-else-if="isRestarting">
|
||||
<DotSpinner duration="1s" class="mr-2" />
|
||||
<span>{{ $t('manager.restartingBackend') }}</span>
|
||||
</template>
|
||||
<!-- 4. Restart completed -->
|
||||
<template v-else-if="isRestartCompleted">
|
||||
<span class="mr-2">🎉</span>
|
||||
<span>{{ $t('manager.extensionsSuccessfullyInstalled') }}</span>
|
||||
</template>
|
||||
<!-- 2. Tasks completed (waiting for restart) -->
|
||||
<template v-else>
|
||||
<i class="pi pi-check-circle mr-2 text-green-500" />
|
||||
<span class="leading-none">{{
|
||||
$t('manager.restartToApplyChanges')
|
||||
}}</span>
|
||||
<span class="mr-2">✅</span>
|
||||
<span>
|
||||
{{ $t('manager.clickToFinishSetup') }}
|
||||
'{{ $t('manager.applyChanges') }}'
|
||||
{{ $t('manager.toFinishSetup') }}
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<span v-if="isInProgress" class="text-xs font-bold text-neutral-600">
|
||||
{{ comfyManagerStore.uncompletedCount }} {{ $t('g.progressCountOf') }}
|
||||
{{ comfyManagerStore.taskLogs.length }}
|
||||
</span>
|
||||
<div class="flex items-center">
|
||||
<Button
|
||||
v-if="!isInProgress"
|
||||
rounded
|
||||
outlined
|
||||
class="px-4 py-2 rounded-md mr-4"
|
||||
@click="handleRestart"
|
||||
>
|
||||
{{ $t('g.restart') }}
|
||||
</Button>
|
||||
<!-- 1. Queue running -->
|
||||
<template v-if="isQueueRunning">
|
||||
<span class="text-sm text-neutral-700 dark-theme:text-neutral-400">
|
||||
{{ completedTasksCount }} {{ $t('g.progressCountOf') }}
|
||||
{{ taskLogs }}
|
||||
</span>
|
||||
<Button
|
||||
:icon="
|
||||
progressDialogContent.isExpanded
|
||||
@@ -44,20 +51,46 @@
|
||||
text
|
||||
rounded
|
||||
size="small"
|
||||
class="font-bold"
|
||||
severity="secondary"
|
||||
:aria-label="progressDialogContent.isExpanded ? 'Collapse' : 'Expand'"
|
||||
@click.stop="progressDialogContent.toggle"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 2. Tasks completed (waiting for restart) -->
|
||||
<template v-else-if="!isRestarting && !isRestartCompleted">
|
||||
<Button
|
||||
icon="pi pi-times"
|
||||
text
|
||||
rounded
|
||||
size="small"
|
||||
severity="secondary"
|
||||
aria-label="Close"
|
||||
@click.stop="closeDialog"
|
||||
/>
|
||||
</div>
|
||||
outlined
|
||||
class="rounded-md border-2 px-3 text-neutral-600 border-neutral-900 hover:bg-neutral-100 dark-theme:bg-none dark-theme:text-white dark-theme:border-white dark-theme:hover:bg-neutral-700"
|
||||
@click="handleRestart"
|
||||
>
|
||||
{{ $t('manager.applyChanges') }}
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<!-- 3. Restarting -->
|
||||
<template v-else-if="isRestarting">
|
||||
<!-- No buttons during restart -->
|
||||
</template>
|
||||
|
||||
<!-- 4. Restart completed -->
|
||||
<template v-else-if="isRestartCompleted">
|
||||
<!-- No buttons after restart completed (auto-close after 3 seconds) -->
|
||||
</template>
|
||||
|
||||
<!-- Common: Close button -->
|
||||
<Button
|
||||
icon="pi pi-times"
|
||||
text
|
||||
rounded
|
||||
size="small"
|
||||
class="font-bold"
|
||||
severity="secondary"
|
||||
aria-label="Close"
|
||||
@click.stop="closeDialog"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -65,9 +98,10 @@
|
||||
<script setup lang="ts">
|
||||
import { useEventListener } from '@vueuse/core'
|
||||
import Button from 'primevue/button'
|
||||
import { computed } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import DotSpinner from '@/components/common/DotSpinner.vue'
|
||||
import { api } from '@/scripts/api'
|
||||
import { useComfyManagerService } from '@/services/comfyManagerService'
|
||||
import { useWorkflowService } from '@/services/workflowService'
|
||||
@@ -77,19 +111,34 @@ import {
|
||||
} from '@/stores/comfyManagerStore'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
|
||||
const { t } = useI18n()
|
||||
const dialogStore = useDialogStore()
|
||||
const progressDialogContent = useManagerProgressDialogStore()
|
||||
const comfyManagerStore = useComfyManagerStore()
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
const isInProgress = computed(() => comfyManagerStore.uncompletedCount > 0)
|
||||
// State management for restart process
|
||||
const isRestarting = ref<boolean>(false)
|
||||
const isRestartCompleted = ref<boolean>(false)
|
||||
|
||||
// Computed states
|
||||
const isQueueRunning = computed(() => comfyManagerStore.uncompletedCount > 0)
|
||||
const taskLogs = computed(() => comfyManagerStore.taskLogs.length)
|
||||
|
||||
const completedTasksCount = computed(() => {
|
||||
if (isQueueRunning.value && taskLogs.value > 0) {
|
||||
return taskLogs.value - 1
|
||||
}
|
||||
return taskLogs.value
|
||||
})
|
||||
|
||||
const closeDialog = () => {
|
||||
dialogStore.closeDialog({ key: 'global-manager-progress-dialog' })
|
||||
}
|
||||
|
||||
const fallbackTaskName = t('g.installing')
|
||||
const fallbackTaskName = t('manager.installingDependencies')
|
||||
const currentTaskName = computed(() => {
|
||||
if (!comfyManagerStore.taskLogs.length) return fallbackTaskName
|
||||
const task = comfyManagerStore.taskLogs.at(-1)
|
||||
@@ -97,21 +146,52 @@ const currentTaskName = computed(() => {
|
||||
})
|
||||
|
||||
const handleRestart = async () => {
|
||||
const onReconnect = async () => {
|
||||
// Refresh manager state
|
||||
// Store original toast setting value
|
||||
const originalToastSetting = settingStore.get(
|
||||
'Comfy.Toast.DisableReconnectingToast'
|
||||
)
|
||||
|
||||
comfyManagerStore.clearLogs()
|
||||
comfyManagerStore.setStale()
|
||||
try {
|
||||
await settingStore.set('Comfy.Toast.DisableReconnectingToast', true)
|
||||
|
||||
// Refresh node definitions
|
||||
await useCommandStore().execute('Comfy.RefreshNodeDefinitions')
|
||||
isRestarting.value = true
|
||||
|
||||
// Reload workflow
|
||||
await useWorkflowService().reloadCurrentWorkflow()
|
||||
const onReconnect = async () => {
|
||||
try {
|
||||
comfyManagerStore.setStale()
|
||||
|
||||
await useCommandStore().execute('Comfy.RefreshNodeDefinitions')
|
||||
|
||||
await useWorkflowService().reloadCurrentWorkflow()
|
||||
} finally {
|
||||
await settingStore.set(
|
||||
'Comfy.Toast.DisableReconnectingToast',
|
||||
originalToastSetting
|
||||
)
|
||||
|
||||
isRestarting.value = false
|
||||
isRestartCompleted.value = true
|
||||
|
||||
setTimeout(() => {
|
||||
closeDialog()
|
||||
comfyManagerStore.clearLogs()
|
||||
}, 3000)
|
||||
}
|
||||
}
|
||||
|
||||
useEventListener(api, 'reconnected', onReconnect, { once: true })
|
||||
|
||||
await useComfyManagerService().rebootComfyUI()
|
||||
} catch (error) {
|
||||
// If restart fails, restore settings and reset state
|
||||
await settingStore.set(
|
||||
'Comfy.Toast.DisableReconnectingToast',
|
||||
originalToastSetting
|
||||
)
|
||||
isRestarting.value = false
|
||||
isRestartCompleted.value = false
|
||||
closeDialog() // Close dialog on error
|
||||
throw error
|
||||
}
|
||||
useEventListener(api, 'reconnected', onReconnect, { once: true })
|
||||
|
||||
await useComfyManagerService().rebootComfyUI()
|
||||
closeDialog()
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -90,18 +90,16 @@ const closeDialog = () => {
|
||||
const canvasStore = useCanvasStore()
|
||||
|
||||
const addNode = (nodeDef: ComfyNodeDefImpl) => {
|
||||
if (!triggerEvent) {
|
||||
console.warn('The trigger event was undefined when addNode was called.')
|
||||
return
|
||||
}
|
||||
|
||||
const node = litegraphService.addNodeOnGraph(nodeDef, {
|
||||
pos: getNewNodeLocation()
|
||||
})
|
||||
|
||||
if (disconnectOnReset) {
|
||||
if (disconnectOnReset && triggerEvent) {
|
||||
canvasStore.getCanvas().linkConnector.connectToNode(node, triggerEvent)
|
||||
} else if (!triggerEvent) {
|
||||
console.warn('The trigger event was undefined when addNode was called.')
|
||||
}
|
||||
|
||||
disconnectOnReset = false
|
||||
|
||||
// Notify changeTracker - new step should be added
|
||||
|
||||
@@ -58,11 +58,12 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { computed, onMounted } from 'vue'
|
||||
|
||||
import HelpCenterMenuContent from '@/components/helpcenter/HelpCenterMenuContent.vue'
|
||||
import ReleaseNotificationToast from '@/components/helpcenter/ReleaseNotificationToast.vue'
|
||||
import WhatsNewPopup from '@/components/helpcenter/WhatsNewPopup.vue'
|
||||
import { useHelpCenterStore } from '@/stores/helpCenterStore'
|
||||
import { useReleaseStore } from '@/stores/releaseStore'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
|
||||
@@ -70,8 +71,9 @@ import SidebarIcon from './SidebarIcon.vue'
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
const releaseStore = useReleaseStore()
|
||||
const helpCenterStore = useHelpCenterStore()
|
||||
const { shouldShowRedDot } = storeToRefs(releaseStore)
|
||||
const isHelpCenterVisible = ref(false)
|
||||
const { isVisible: isHelpCenterVisible } = storeToRefs(helpCenterStore)
|
||||
|
||||
const sidebarLocation = computed(() =>
|
||||
settingStore.get('Comfy.Sidebar.Location')
|
||||
@@ -80,11 +82,11 @@ const sidebarLocation = computed(() =>
|
||||
const sidebarSize = computed(() => settingStore.get('Comfy.Sidebar.Size'))
|
||||
|
||||
const toggleHelpCenter = () => {
|
||||
isHelpCenterVisible.value = !isHelpCenterVisible.value
|
||||
helpCenterStore.toggle()
|
||||
}
|
||||
|
||||
const closeHelpCenter = () => {
|
||||
isHelpCenterVisible.value = false
|
||||
helpCenterStore.hide()
|
||||
}
|
||||
|
||||
// Initialize release store on mount
|
||||
@@ -130,6 +132,7 @@ onMounted(async () => {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
|
||||
@@ -30,11 +30,18 @@
|
||||
/>
|
||||
<Button
|
||||
v-tooltip.bottom="$t('sideToolbar.nodeLibraryTab.resetView')"
|
||||
icon="pi pi-refresh"
|
||||
icon="pi pi-filter-slash"
|
||||
text
|
||||
severity="secondary"
|
||||
@click="resetOrganization"
|
||||
/>
|
||||
<Button
|
||||
v-tooltip.bottom="$t('menu.refresh')"
|
||||
icon="pi pi-refresh"
|
||||
text
|
||||
severity="secondary"
|
||||
@click="() => commandStore.execute('Comfy.RefreshNodeDefinitions')"
|
||||
/>
|
||||
<Popover ref="groupingPopover">
|
||||
<div class="flex flex-col gap-1 p-2">
|
||||
<Button
|
||||
@@ -139,6 +146,7 @@ import {
|
||||
DEFAULT_SORTING_ID,
|
||||
nodeOrganizationService
|
||||
} from '@/services/nodeOrganizationService'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useNodeBookmarkStore } from '@/stores/nodeBookmarkStore'
|
||||
import { ComfyNodeDefImpl, useNodeDefStore } from '@/stores/nodeDefStore'
|
||||
import { useNodeHelpStore } from '@/stores/workspace/nodeHelpStore'
|
||||
@@ -155,6 +163,7 @@ import NodeBookmarkTreeExplorer from './nodeLibrary/NodeBookmarkTreeExplorer.vue
|
||||
const nodeDefStore = useNodeDefStore()
|
||||
const nodeBookmarkStore = useNodeBookmarkStore()
|
||||
const nodeHelpStore = useNodeHelpStore()
|
||||
const commandStore = useCommandStore()
|
||||
const expandedKeys = ref<Record<string, boolean>>({})
|
||||
const { expandNode, toggleNodeOnEvent } = useTreeExpansion(expandedKeys)
|
||||
|
||||
|
||||
@@ -55,9 +55,30 @@
|
||||
v-bind="props.action"
|
||||
:href="item.url"
|
||||
target="_blank"
|
||||
:class="typeof item.class === 'function' ? item.class() : item.class"
|
||||
@mousedown="
|
||||
isZoomCommand(item) ? handleZoomMouseDown(item, $event) : undefined
|
||||
"
|
||||
@click="handleItemClick(item, $event)"
|
||||
>
|
||||
<span v-if="item.icon" class="p-menubar-item-icon" :class="item.icon" />
|
||||
<i
|
||||
v-if="hasActiveStateSiblings(item)"
|
||||
class="p-menubar-item-icon pi pi-check text-sm"
|
||||
:class="{ invisible: !item.comfyCommand?.active?.() }"
|
||||
/>
|
||||
<span
|
||||
v-else-if="
|
||||
item.icon && item.comfyCommand?.id !== 'Comfy.NewBlankWorkflow'
|
||||
"
|
||||
class="p-menubar-item-icon"
|
||||
:class="item.icon"
|
||||
/>
|
||||
<span class="p-menubar-item-label text-nowrap">{{ item.label }}</span>
|
||||
<i
|
||||
v-if="item.comfyCommand?.id === 'Comfy.NewBlankWorkflow'"
|
||||
class="ml-auto"
|
||||
:class="item.icon"
|
||||
/>
|
||||
<span
|
||||
v-if="item?.comfyCommand?.keybinding"
|
||||
class="ml-auto border border-surface rounded text-muted text-xs text-nowrap p-1 keybinding-tag"
|
||||
@@ -94,6 +115,7 @@ import { useSettingStore } from '@/stores/settingStore'
|
||||
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
|
||||
import { showNativeSystemMenu } from '@/utils/envUtil'
|
||||
import { normalizeI18nKey } from '@/utils/formatUtil'
|
||||
import { whileMouseDown } from '@/utils/mouseDownUtil'
|
||||
|
||||
const colorPaletteStore = useColorPaletteStore()
|
||||
const menuItemsStore = useMenuItemStore()
|
||||
@@ -163,16 +185,22 @@ const extraMenuItems: MenuItem[] = [
|
||||
},
|
||||
{ separator: true },
|
||||
{
|
||||
key: 'manage-extensions',
|
||||
label: t('menu.manageExtensions'),
|
||||
icon: 'mdi mdi-puzzle-outline',
|
||||
command: showManageExtensions
|
||||
key: 'browse-templates',
|
||||
label: t('menuLabels.Browse Templates'),
|
||||
icon: 'pi pi-folder-open',
|
||||
command: () => commandStore.execute('Comfy.BrowseTemplates')
|
||||
},
|
||||
{
|
||||
key: 'settings',
|
||||
label: t('g.settings'),
|
||||
icon: 'mdi mdi-cog-outline',
|
||||
command: () => showSettings()
|
||||
},
|
||||
{
|
||||
key: 'manage-extensions',
|
||||
label: t('menu.manageExtensions'),
|
||||
icon: 'mdi mdi-puzzle-outline',
|
||||
command: showManageExtensions
|
||||
}
|
||||
]
|
||||
|
||||
@@ -237,6 +265,44 @@ const onMenuShow = () => {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const isZoomCommand = (item: MenuItem) => {
|
||||
return (
|
||||
item.comfyCommand?.id === 'Comfy.Canvas.ZoomIn' ||
|
||||
item.comfyCommand?.id === 'Comfy.Canvas.ZoomOut'
|
||||
)
|
||||
}
|
||||
|
||||
const handleZoomMouseDown = (item: MenuItem, event: MouseEvent) => {
|
||||
if (item.comfyCommand) {
|
||||
whileMouseDown(
|
||||
event,
|
||||
async () => {
|
||||
await commandStore.execute(item.comfyCommand!.id)
|
||||
},
|
||||
50
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const handleItemClick = (item: MenuItem, event: MouseEvent) => {
|
||||
// Prevent the menu from closing for zoom commands or commands that have active state
|
||||
if (isZoomCommand(item) || item.comfyCommand?.active) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
if (item.comfyCommand?.active) {
|
||||
item.command?.({
|
||||
item,
|
||||
originalEvent: event
|
||||
})
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const hasActiveStateSiblings = (item: MenuItem): boolean => {
|
||||
return menuItemsStore.menuItemHasActiveStateChildren[item.parentPath]
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1362,9 +1362,27 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
|
||||
return '$0.0004/$0.0016 per 1K tokens'
|
||||
} else if (model.includes('gpt-4.1')) {
|
||||
return '$0.002/$0.008 per 1K tokens'
|
||||
} else if (model.includes('gpt-5-nano')) {
|
||||
return '$0.00005/$0.0004 per 1K tokens'
|
||||
} else if (model.includes('gpt-5-mini')) {
|
||||
return '$0.00025/$0.002 per 1K tokens'
|
||||
} else if (model.includes('gpt-5')) {
|
||||
return '$0.00125/$0.01 per 1K tokens'
|
||||
}
|
||||
return 'Token-based'
|
||||
}
|
||||
},
|
||||
ViduTextToVideoNode: {
|
||||
displayPrice: '$0.4/Run'
|
||||
},
|
||||
ViduImageToVideoNode: {
|
||||
displayPrice: '$0.4/Run'
|
||||
},
|
||||
ViduReferenceVideoNode: {
|
||||
displayPrice: '$0.4/Run'
|
||||
},
|
||||
ViduStartEndToVideoNode: {
|
||||
displayPrice: '$0.4/Run'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,12 +15,15 @@ import { Point } from '@/lib/litegraph/src/litegraph'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app } from '@/scripts/app'
|
||||
import { addFluxKontextGroupNode } from '@/scripts/fluxKontextEditNode'
|
||||
import { useComfyManagerService } from '@/services/comfyManagerService'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { useLitegraphService } from '@/services/litegraphService'
|
||||
import { useWorkflowService } from '@/services/workflowService'
|
||||
import type { ComfyCommand } from '@/stores/commandStore'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useExecutionStore } from '@/stores/executionStore'
|
||||
import { useCanvasStore, useTitleEditorStore } from '@/stores/graphStore'
|
||||
import { useHelpCenterStore } from '@/stores/helpCenterStore'
|
||||
import { useNodeOutputStore } from '@/stores/imagePreviewStore'
|
||||
import { useQueueSettingsStore, useQueueStore } from '@/stores/queueStore'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
@@ -31,6 +34,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 { ManagerTab } from '@/types/comfyManagerTypes'
|
||||
import {
|
||||
getAllNonIoNodesInSubgraph,
|
||||
getExecutionIdsForSelectedNodes
|
||||
@@ -278,6 +282,7 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
id: 'Comfy.Canvas.FitView',
|
||||
icon: 'pi pi-expand',
|
||||
label: 'Fit view to selected nodes',
|
||||
menubarLabel: 'Zoom to fit',
|
||||
category: 'view-controls' as const,
|
||||
function: () => {
|
||||
if (app.canvas.empty) {
|
||||
@@ -303,6 +308,7 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
id: 'Comfy.Canvas.ToggleLinkVisibility',
|
||||
icon: 'pi pi-eye',
|
||||
label: 'Canvas Toggle Link Visibility',
|
||||
menubarLabel: 'Node Links',
|
||||
versionAdded: '1.3.6',
|
||||
|
||||
function: (() => {
|
||||
@@ -324,12 +330,15 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
)
|
||||
}
|
||||
}
|
||||
})()
|
||||
})(),
|
||||
active: () =>
|
||||
useSettingStore().get('Comfy.LinkRenderMode') !== LiteGraph.HIDDEN_LINK
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Canvas.ToggleMinimap',
|
||||
icon: 'pi pi-map',
|
||||
label: 'Canvas Toggle Minimap',
|
||||
menubarLabel: 'Minimap',
|
||||
versionAdded: '1.24.1',
|
||||
function: async () => {
|
||||
const settingStore = useSettingStore()
|
||||
@@ -337,7 +346,8 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
'Comfy.Minimap.Visible',
|
||||
!settingStore.get('Comfy.Minimap.Visible')
|
||||
)
|
||||
}
|
||||
},
|
||||
active: () => useSettingStore().get('Comfy.Minimap.Visible')
|
||||
},
|
||||
{
|
||||
id: 'Comfy.QueuePrompt',
|
||||
@@ -541,21 +551,25 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
id: 'Workspace.ToggleBottomPanel',
|
||||
icon: 'pi pi-list',
|
||||
label: 'Toggle Bottom Panel',
|
||||
menubarLabel: 'Bottom Panel',
|
||||
versionAdded: '1.3.22',
|
||||
category: 'view-controls' as const,
|
||||
function: () => {
|
||||
bottomPanelStore.toggleBottomPanel()
|
||||
}
|
||||
},
|
||||
active: () => bottomPanelStore.bottomPanelVisible
|
||||
},
|
||||
{
|
||||
id: 'Workspace.ToggleFocusMode',
|
||||
icon: 'pi pi-eye',
|
||||
label: 'Toggle Focus Mode',
|
||||
menubarLabel: 'Focus Mode',
|
||||
versionAdded: '1.3.27',
|
||||
category: 'view-controls' as const,
|
||||
function: () => {
|
||||
useWorkspaceStore().toggleFocusMode()
|
||||
}
|
||||
},
|
||||
active: () => useWorkspaceStore().focusMode
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Graph.FitGroupToContents',
|
||||
@@ -699,12 +713,54 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Manager.CustomNodesManager',
|
||||
icon: 'pi pi-puzzle',
|
||||
label: 'Toggle the Custom Nodes Manager',
|
||||
id: 'Comfy.Manager.CustomNodesManager.ShowCustomNodesMenu',
|
||||
icon: 'pi pi-objects-column',
|
||||
label: 'Custom Nodes Manager',
|
||||
versionAdded: '1.12.10',
|
||||
function: async () => {
|
||||
const { is_legacy_manager_ui } =
|
||||
(await useComfyManagerService().isLegacyManagerUI()) ?? {}
|
||||
|
||||
if (is_legacy_manager_ui === true) {
|
||||
try {
|
||||
await useCommandStore().execute(
|
||||
'Comfy.Manager.Menu.ToggleVisibility' // This command is registered by legacy manager FE extension
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('error', error)
|
||||
useToastStore().add({
|
||||
severity: 'error',
|
||||
summary: t('g.error'),
|
||||
detail: t('manager.legacyMenuNotAvailable'),
|
||||
life: 3000
|
||||
})
|
||||
dialogService.showManagerDialog()
|
||||
}
|
||||
} else {
|
||||
dialogService.showManagerDialog()
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Manager.ShowUpdateAvailablePacks',
|
||||
icon: 'pi pi-sync',
|
||||
label: 'Check for Custom Node Updates',
|
||||
versionAdded: '1.17.0',
|
||||
function: () => {
|
||||
dialogService.toggleManagerDialog()
|
||||
dialogService.showManagerDialog({
|
||||
initialTab: ManagerTab.UpdateAvailable
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Manager.ShowMissingPacks',
|
||||
icon: 'pi pi-exclamation-circle',
|
||||
label: 'Install Missing Custom Nodes',
|
||||
versionAdded: '1.17.0',
|
||||
function: () => {
|
||||
dialogService.showManagerDialog({
|
||||
initialTab: ManagerTab.Missing
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -815,6 +871,34 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
graph.unpackSubgraph(subgraphNode)
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'Comfy.OpenManagerDialog',
|
||||
icon: 'mdi mdi-puzzle-outline',
|
||||
label: 'Manager',
|
||||
function: () => {
|
||||
dialogService.showManagerDialog()
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'Comfy.ToggleHelpCenter',
|
||||
icon: 'pi pi-question-circle',
|
||||
label: 'Help Center',
|
||||
function: () => {
|
||||
useHelpCenterStore().toggle()
|
||||
},
|
||||
active: () => useHelpCenterStore().isVisible
|
||||
},
|
||||
{
|
||||
id: 'Comfy.ToggleCanvasInfo',
|
||||
icon: 'pi pi-info-circle',
|
||||
label: 'Canvas Performance',
|
||||
function: async () => {
|
||||
const settingStore = useSettingStore()
|
||||
const currentValue = settingStore.get('Comfy.Graph.CanvasInfo')
|
||||
await settingStore.set('Comfy.Graph.CanvasInfo', !currentValue)
|
||||
},
|
||||
active: () => useSettingStore().get('Comfy.Graph.CanvasInfo')
|
||||
},
|
||||
{
|
||||
id: 'Workspace.ToggleBottomPanel.Shortcuts',
|
||||
icon: 'pi pi-key',
|
||||
@@ -839,6 +923,84 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
navigationStore.navigationStack.at(-2) ?? canvas.graph.rootGraph
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Manager.CustomNodesManager.ShowLegacyCustomNodesMenu',
|
||||
icon: 'pi pi-bars',
|
||||
label: 'Custom Nodes (Legacy)',
|
||||
versionAdded: '1.16.4',
|
||||
function: async () => {
|
||||
try {
|
||||
await useCommandStore().execute(
|
||||
'Comfy.Manager.CustomNodesManager.ToggleVisibility'
|
||||
)
|
||||
} catch (error) {
|
||||
useToastStore().add({
|
||||
severity: 'error',
|
||||
summary: t('g.error'),
|
||||
detail: t('manager.legacyMenuNotAvailable'),
|
||||
life: 3000
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Manager.ShowLegacyManagerMenu',
|
||||
icon: 'mdi mdi-puzzle',
|
||||
label: 'Manager Menu (Legacy)',
|
||||
versionAdded: '1.16.4',
|
||||
function: async () => {
|
||||
try {
|
||||
await useCommandStore().execute('Comfy.Manager.Menu.ToggleVisibility')
|
||||
} catch (error) {
|
||||
useToastStore().add({
|
||||
severity: 'error',
|
||||
summary: t('g.error'),
|
||||
detail: t('manager.legacyMenuNotAvailable'),
|
||||
life: 3000
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Memory.UnloadModels',
|
||||
icon: 'mdi mdi-vacuum-outline',
|
||||
label: 'Unload Models',
|
||||
versionAdded: '1.16.4',
|
||||
function: async () => {
|
||||
if (!useSettingStore().get('Comfy.Memory.AllowManualUnload')) {
|
||||
useToastStore().add({
|
||||
severity: 'error',
|
||||
summary: t('g.error'),
|
||||
detail: t('g.commandProhibited', {
|
||||
command: 'Comfy.Memory.UnloadModels'
|
||||
}),
|
||||
life: 3000
|
||||
})
|
||||
return
|
||||
}
|
||||
await api.freeMemory({ freeExecutionCache: false })
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Memory.UnloadModelsAndExecutionCache',
|
||||
icon: 'mdi mdi-vacuum-outline',
|
||||
label: 'Unload Models and Execution Cache',
|
||||
versionAdded: '1.16.4',
|
||||
function: async () => {
|
||||
if (!useSettingStore().get('Comfy.Memory.AllowManualUnload')) {
|
||||
useToastStore().add({
|
||||
severity: 'error',
|
||||
summary: t('g.error'),
|
||||
detail: t('g.commandProhibited', {
|
||||
command: 'Comfy.Memory.UnloadModelsAndExecutionCache'
|
||||
}),
|
||||
life: 3000
|
||||
})
|
||||
return
|
||||
}
|
||||
await api.freeMemory({ freeExecutionCache: true })
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
36
src/composables/useFeatureFlags.ts
Normal file
36
src/composables/useFeatureFlags.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { computed, reactive, readonly } from 'vue'
|
||||
|
||||
import { api } from '@/scripts/api'
|
||||
|
||||
/**
|
||||
* Known server feature flags (top-level, not extensions)
|
||||
*/
|
||||
export enum ServerFeatureFlag {
|
||||
SUPPORTS_PREVIEW_METADATA = 'supports_preview_metadata',
|
||||
MAX_UPLOAD_SIZE = 'max_upload_size'
|
||||
}
|
||||
|
||||
/**
|
||||
* Composable for reactive access to feature flags
|
||||
*/
|
||||
export function useFeatureFlags() {
|
||||
// Create reactive state that tracks server feature flags
|
||||
const flags = reactive({
|
||||
get supportsPreviewMetadata() {
|
||||
return api.getServerFeature(ServerFeatureFlag.SUPPORTS_PREVIEW_METADATA)
|
||||
},
|
||||
get maxUploadSize() {
|
||||
return api.getServerFeature(ServerFeatureFlag.MAX_UPLOAD_SIZE)
|
||||
}
|
||||
})
|
||||
|
||||
// Create a reactive computed for any feature flag
|
||||
const featureFlag = <T = unknown>(featurePath: string, defaultValue?: T) => {
|
||||
return computed(() => api.getServerFeature(featurePath, defaultValue))
|
||||
}
|
||||
|
||||
return {
|
||||
flags: readonly(flags),
|
||||
featureFlag
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ export const useLitegraphSettings = () => {
|
||||
const canvasInfoEnabled = settingStore.get('Comfy.Graph.CanvasInfo')
|
||||
if (canvasStore.canvas) {
|
||||
canvasStore.canvas.show_info = canvasInfoEnabled
|
||||
canvasStore.canvas.draw(false, true)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ import { useCanvasTransformSync } from '@/composables/canvas/useCanvasTransformS
|
||||
import { LGraphEventMode, 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'
|
||||
import { adjustColor } from '@/utils/colorUtil'
|
||||
|
||||
@@ -27,6 +27,7 @@ export type MinimapOptionKey =
|
||||
export function useMinimap() {
|
||||
const settingStore = useSettingStore()
|
||||
const canvasStore = useCanvasStore()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const colorPaletteStore = useColorPaletteStore()
|
||||
|
||||
const containerRef = ref<HTMLDivElement>()
|
||||
@@ -147,7 +148,11 @@ export function useMinimap() {
|
||||
}
|
||||
|
||||
const canvas = computed(() => canvasStore.canvas)
|
||||
const graph = ref(app.canvas?.graph)
|
||||
const graph = computed(() => {
|
||||
// If we're in a subgraph, use that; otherwise use the canvas graph
|
||||
const activeSubgraph = workflowStore.activeSubgraph
|
||||
return activeSubgraph || canvas.value?.graph
|
||||
})
|
||||
|
||||
const containerStyles = computed(() => ({
|
||||
width: `${width}px`,
|
||||
@@ -627,7 +632,8 @@ export function useMinimap() {
|
||||
c.setDirty(true, true)
|
||||
}
|
||||
|
||||
let originalCallbacks: GraphCallbacks = {}
|
||||
// Map to store original callbacks per graph ID
|
||||
const originalCallbacksMap = new Map<string, GraphCallbacks>()
|
||||
|
||||
const handleGraphChanged = useThrottleFn(() => {
|
||||
needsFullRedraw.value = true
|
||||
@@ -641,11 +647,18 @@ export function useMinimap() {
|
||||
const g = graph.value
|
||||
if (!g) return
|
||||
|
||||
originalCallbacks = {
|
||||
// Check if we've already wrapped this graph's callbacks
|
||||
if (originalCallbacksMap.has(g.id)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Store the original callbacks for this graph
|
||||
const originalCallbacks: GraphCallbacks = {
|
||||
onNodeAdded: g.onNodeAdded,
|
||||
onNodeRemoved: g.onNodeRemoved,
|
||||
onConnectionChange: g.onConnectionChange
|
||||
}
|
||||
originalCallbacksMap.set(g.id, originalCallbacks)
|
||||
|
||||
g.onNodeAdded = function (node) {
|
||||
originalCallbacks.onNodeAdded?.call(this, node)
|
||||
@@ -670,15 +683,19 @@ export function useMinimap() {
|
||||
const g = graph.value
|
||||
if (!g) return
|
||||
|
||||
if (originalCallbacks.onNodeAdded !== undefined) {
|
||||
g.onNodeAdded = originalCallbacks.onNodeAdded
|
||||
}
|
||||
if (originalCallbacks.onNodeRemoved !== undefined) {
|
||||
g.onNodeRemoved = originalCallbacks.onNodeRemoved
|
||||
}
|
||||
if (originalCallbacks.onConnectionChange !== undefined) {
|
||||
g.onConnectionChange = originalCallbacks.onConnectionChange
|
||||
const originalCallbacks = originalCallbacksMap.get(g.id)
|
||||
if (!originalCallbacks) {
|
||||
console.error(
|
||||
'Attempted to cleanup event listeners for graph that was never set up'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
g.onNodeAdded = originalCallbacks.onNodeAdded
|
||||
g.onNodeRemoved = originalCallbacks.onNodeRemoved
|
||||
g.onConnectionChange = originalCallbacks.onConnectionChange
|
||||
|
||||
originalCallbacksMap.delete(g.id)
|
||||
}
|
||||
|
||||
const init = async () => {
|
||||
@@ -751,6 +768,19 @@ export function useMinimap() {
|
||||
{ immediate: true, flush: 'post' }
|
||||
)
|
||||
|
||||
// Watch for graph changes (e.g., when navigating to/from subgraphs)
|
||||
watch(graph, (newGraph, oldGraph) => {
|
||||
if (newGraph && newGraph !== oldGraph) {
|
||||
cleanupEventListeners()
|
||||
setupEventListeners()
|
||||
needsFullRedraw.value = true
|
||||
updateFlags.value.bounds = true
|
||||
updateFlags.value.nodes = true
|
||||
updateFlags.value.connections = true
|
||||
updateMinimap()
|
||||
}
|
||||
})
|
||||
|
||||
watch(visible, async (isVisible) => {
|
||||
if (isVisible) {
|
||||
if (containerRef.value) {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
export const CORE_MENU_COMMANDS = [
|
||||
[['Workflow'], ['Comfy.NewBlankWorkflow']],
|
||||
[['Workflow'], ['Comfy.OpenWorkflow', 'Comfy.BrowseTemplates']],
|
||||
[[], ['Comfy.NewBlankWorkflow']],
|
||||
[[], []], // Separator after New
|
||||
[['File'], ['Comfy.OpenWorkflow']],
|
||||
[
|
||||
['Workflow'],
|
||||
['File'],
|
||||
[
|
||||
'Comfy.SaveWorkflow',
|
||||
'Comfy.SaveWorkflowAs',
|
||||
@@ -11,9 +12,15 @@ export const CORE_MENU_COMMANDS = [
|
||||
]
|
||||
],
|
||||
[['Edit'], ['Comfy.Undo', 'Comfy.Redo']],
|
||||
[['Edit'], ['Comfy.RefreshNodeDefinitions']],
|
||||
[['Edit'], ['Comfy.ClearWorkflow']],
|
||||
[['Edit'], ['Comfy.OpenClipspace']],
|
||||
[
|
||||
['Manager'],
|
||||
[
|
||||
'Comfy.Manager.CustomNodesManager.ShowCustomNodesMenu',
|
||||
'Comfy.Manager.ShowMissingPacks',
|
||||
'Comfy.Manager.ShowUpdateAvailablePacks'
|
||||
]
|
||||
],
|
||||
[
|
||||
['Help'],
|
||||
[
|
||||
|
||||
@@ -13,6 +13,13 @@ import type { SettingParams } from '@/types/settingTypes'
|
||||
* when they are no longer needed.
|
||||
*/
|
||||
export const CORE_SETTINGS: SettingParams[] = [
|
||||
{
|
||||
id: 'Comfy.Memory.AllowManualUnload',
|
||||
name: 'Allow manual unload of models and execution cache via user command',
|
||||
type: 'hidden',
|
||||
defaultValue: true,
|
||||
versionAdded: '1.18.0'
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Validation.Workflows',
|
||||
name: 'Validate workflows',
|
||||
@@ -772,7 +779,8 @@ export const CORE_SETTINGS: SettingParams[] = [
|
||||
{
|
||||
id: 'LiteGraph.Canvas.LowQualityRenderingZoomThreshold',
|
||||
name: 'Low quality rendering zoom threshold',
|
||||
tooltip: 'Render low quality shapes when zoomed out',
|
||||
tooltip:
|
||||
'Zoom level threshold for performance mode. Lower values (0.1) = quality at all zoom levels. Higher values (1.0) = performance mode even when zoomed in. Performance mode simplifies rendering by hiding text labels, shadows, and details.',
|
||||
type: 'slider',
|
||||
attrs: {
|
||||
min: 0.1,
|
||||
|
||||
@@ -3608,6 +3608,7 @@ export class LGraphCanvas
|
||||
subgraphs: []
|
||||
}
|
||||
|
||||
// NOTE: logic for traversing nested subgraphs depends on this being a set.
|
||||
const subgraphs = new Set<Subgraph>()
|
||||
|
||||
// Create serialisable objects
|
||||
@@ -3646,8 +3647,13 @@ export class LGraphCanvas
|
||||
}
|
||||
|
||||
// Add unique subgraph entries
|
||||
// TODO: Must find all nested subgraphs
|
||||
// NOTE: subgraphs is appended to mid iteration.
|
||||
for (const subgraph of subgraphs) {
|
||||
for (const node of subgraph.nodes) {
|
||||
if (node instanceof SubgraphNode) {
|
||||
subgraphs.add(node.subgraph)
|
||||
}
|
||||
}
|
||||
const cloned = subgraph.clone(true).asSerialisable()
|
||||
serialisable.subgraphs.push(cloned)
|
||||
}
|
||||
@@ -3764,12 +3770,19 @@ export class LGraphCanvas
|
||||
created.push(group)
|
||||
}
|
||||
|
||||
// Update subgraph ids with nesting
|
||||
function updateSubgraphIds(nodes: { type: string }[]) {
|
||||
for (const info of nodes) {
|
||||
const subgraph = results.subgraphs.get(info.type)
|
||||
if (!subgraph) continue
|
||||
info.type = subgraph.id
|
||||
updateSubgraphIds(subgraph.nodes)
|
||||
}
|
||||
}
|
||||
updateSubgraphIds(parsed.nodes)
|
||||
|
||||
// Nodes
|
||||
for (const info of parsed.nodes) {
|
||||
// If the subgraph was cloned, update references to use the new subgraph ID.
|
||||
const subgraph = results.subgraphs.get(info.type)
|
||||
if (subgraph) info.type = subgraph.id
|
||||
|
||||
const node = info.type == null ? null : LiteGraph.createNode(info.type)
|
||||
if (!node) {
|
||||
// failedNodes.push(info)
|
||||
|
||||
@@ -414,6 +414,18 @@ export class LLink implements LinkSegment, Serialisable<SerialisableLLink> {
|
||||
* If `input` or `output`, reroutes will not be automatically removed, and retain a connection to the input or output, respectively.
|
||||
*/
|
||||
disconnect(network: LinkNetwork, keepReroutes?: 'input' | 'output'): void {
|
||||
// Clean up the target node's input slot
|
||||
if (this.target_id !== -1) {
|
||||
const targetNode = network.getNodeById(this.target_id)
|
||||
if (targetNode) {
|
||||
const targetInput = targetNode.inputs?.[this.target_slot]
|
||||
if (targetInput && targetInput.link === this.id) {
|
||||
targetInput.link = null
|
||||
targetNode.setDirtyCanvas?.(true, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const reroutes = LLink.getReroutes(network, this)
|
||||
|
||||
const lastReroute = reroutes.at(-1)
|
||||
|
||||
@@ -135,6 +135,10 @@ export class FloatingRenderLink implements RenderLink {
|
||||
return true
|
||||
}
|
||||
|
||||
canConnectToSubgraphInput(input: SubgraphInput): boolean {
|
||||
return this.toType === 'output' && input.isValidTarget(this.fromSlot)
|
||||
}
|
||||
|
||||
connectToInput(
|
||||
node: LGraphNode,
|
||||
input: INodeInputSlot,
|
||||
|
||||
@@ -681,6 +681,20 @@ export class LinkConnector {
|
||||
let targetSlot = input
|
||||
|
||||
for (const link of renderLinks) {
|
||||
// Validate the connection type before proceeding
|
||||
if (
|
||||
'canConnectToSubgraphInput' in link &&
|
||||
!link.canConnectToSubgraphInput(targetSlot)
|
||||
) {
|
||||
console.warn(
|
||||
'Invalid connection type',
|
||||
link.fromSlot.type,
|
||||
'->',
|
||||
targetSlot.type
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
link.connectToSubgraphInput(targetSlot, this.events)
|
||||
|
||||
// If we just connected to an EmptySubgraphInput, check if we should reuse the slot
|
||||
@@ -941,6 +955,14 @@ export class LinkConnector {
|
||||
)
|
||||
}
|
||||
|
||||
isSubgraphInputValidDrop(input: SubgraphInput): boolean {
|
||||
return this.renderLinks.some(
|
||||
(link) =>
|
||||
'canConnectToSubgraphInput' in link &&
|
||||
link.canConnectToSubgraphInput(input)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a reroute is a valid drop target for any of the links being connected.
|
||||
* @param reroute The reroute that would be dropped on.
|
||||
|
||||
@@ -55,6 +55,10 @@ export class MovingOutputLink extends MovingLinkBase {
|
||||
return reroute.origin_id !== this.outputNode.id
|
||||
}
|
||||
|
||||
canConnectToSubgraphInput(input: SubgraphInput): boolean {
|
||||
return input.isValidTarget(this.fromSlot)
|
||||
}
|
||||
|
||||
connectToInput(): never {
|
||||
throw new Error('MovingOutputLink cannot connect to an input.')
|
||||
}
|
||||
|
||||
@@ -58,6 +58,10 @@ export class ToOutputRenderLink implements RenderLink {
|
||||
return true
|
||||
}
|
||||
|
||||
canConnectToSubgraphInput(input: SubgraphInput): boolean {
|
||||
return input.isValidTarget(this.fromSlot)
|
||||
}
|
||||
|
||||
connectToOutput(
|
||||
node: LGraphNode,
|
||||
output: INodeOutputSlot,
|
||||
|
||||
@@ -16,7 +16,10 @@ import type {
|
||||
GraphOrSubgraph,
|
||||
Subgraph
|
||||
} from '@/lib/litegraph/src/subgraph/Subgraph'
|
||||
import type { ExportedSubgraphInstance } from '@/lib/litegraph/src/types/serialisation'
|
||||
import type {
|
||||
ExportedSubgraphInstance,
|
||||
ISerialisedNode
|
||||
} from '@/lib/litegraph/src/types/serialisation'
|
||||
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
||||
import type { UUID } from '@/lib/litegraph/src/utils/uuid'
|
||||
import { toConcreteWidget } from '@/lib/litegraph/src/widgets/widgetMap'
|
||||
@@ -266,10 +269,14 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph {
|
||||
const subgraphInput = this.subgraph.inputNode.slots.find(
|
||||
(slot) => slot.name === input.name
|
||||
)
|
||||
if (!subgraphInput)
|
||||
throw new Error(
|
||||
`[SubgraphNode.configure] No subgraph input found for input ${input.name}`
|
||||
if (!subgraphInput) {
|
||||
// Skip inputs that don't exist in the subgraph definition
|
||||
// This can happen when loading workflows with dynamically added inputs
|
||||
console.warn(
|
||||
`[SubgraphNode.configure] No subgraph input found for input ${input.name}, skipping`
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
this.#addSubgraphInputListeners(subgraphInput, input)
|
||||
|
||||
@@ -536,4 +543,36 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizes widget values from this SubgraphNode instance to the
|
||||
* corresponding widgets in the subgraph definition before serialization.
|
||||
* This ensures nested subgraph widget values are preserved when saving.
|
||||
*/
|
||||
override serialize(): ISerialisedNode {
|
||||
// Sync widget values to subgraph definition before serialization
|
||||
for (let i = 0; i < this.widgets.length; i++) {
|
||||
const widget = this.widgets[i]
|
||||
const input = this.inputs.find((inp) => inp.name === widget.name)
|
||||
|
||||
if (input) {
|
||||
const subgraphInput = this.subgraph.inputNode.slots.find(
|
||||
(slot) => slot.name === input.name
|
||||
)
|
||||
|
||||
if (subgraphInput) {
|
||||
// Find all widgets connected to this subgraph input
|
||||
const connectedWidgets = subgraphInput.getConnectedWidgets()
|
||||
|
||||
// Update the value of all connected widgets
|
||||
for (const connectedWidget of connectedWidgets) {
|
||||
connectedWidget.value = widget.value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call parent serialize method
|
||||
return super.serialize()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect } from 'vitest'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { LLink } from '@/lib/litegraph/src/litegraph'
|
||||
import { LGraph, LGraphNode, LLink } from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
import { test } from './testExtensions'
|
||||
|
||||
@@ -14,4 +14,84 @@ describe('LLink', () => {
|
||||
const link = new LLink(1, 'float', 4, 2, 5, 3)
|
||||
expect(link.serialize()).toMatchSnapshot('Basic')
|
||||
})
|
||||
|
||||
describe('disconnect', () => {
|
||||
it('should clear the target input link reference when disconnecting', () => {
|
||||
// Create a graph and nodes
|
||||
const graph = new LGraph()
|
||||
const sourceNode = new LGraphNode('Source')
|
||||
const targetNode = new LGraphNode('Target')
|
||||
|
||||
// Add nodes to graph
|
||||
graph.add(sourceNode)
|
||||
graph.add(targetNode)
|
||||
|
||||
// Add slots
|
||||
sourceNode.addOutput('out', 'number')
|
||||
targetNode.addInput('in', 'number')
|
||||
|
||||
// Connect the nodes
|
||||
const link = sourceNode.connect(0, targetNode, 0)
|
||||
expect(link).toBeDefined()
|
||||
expect(targetNode.inputs[0].link).toBe(link?.id)
|
||||
|
||||
// Mock setDirtyCanvas
|
||||
const setDirtyCanvasSpy = vi.spyOn(targetNode, 'setDirtyCanvas')
|
||||
|
||||
// Disconnect the link
|
||||
link?.disconnect(graph)
|
||||
|
||||
// Verify the target input's link reference is cleared
|
||||
expect(targetNode.inputs[0].link).toBeNull()
|
||||
|
||||
// Verify setDirtyCanvas was called
|
||||
expect(setDirtyCanvasSpy).toHaveBeenCalledWith(true, false)
|
||||
})
|
||||
|
||||
it('should handle disconnecting when target node is not found', () => {
|
||||
// Create a link with invalid target
|
||||
const graph = new LGraph()
|
||||
const link = new LLink(1, 'number', 1, 0, 999, 0) // Invalid target id
|
||||
|
||||
// Should not throw when disconnecting
|
||||
expect(() => link.disconnect(graph)).not.toThrow()
|
||||
})
|
||||
|
||||
it('should only clear link reference if it matches the current link id', () => {
|
||||
// Create a graph and nodes
|
||||
const graph = new LGraph()
|
||||
const sourceNode1 = new LGraphNode('Source1')
|
||||
const sourceNode2 = new LGraphNode('Source2')
|
||||
const targetNode = new LGraphNode('Target')
|
||||
|
||||
// Add nodes to graph
|
||||
graph.add(sourceNode1)
|
||||
graph.add(sourceNode2)
|
||||
graph.add(targetNode)
|
||||
|
||||
// Add slots
|
||||
sourceNode1.addOutput('out', 'number')
|
||||
sourceNode2.addOutput('out', 'number')
|
||||
targetNode.addInput('in', 'number')
|
||||
|
||||
// Create first connection
|
||||
const link1 = sourceNode1.connect(0, targetNode, 0)
|
||||
expect(link1).toBeDefined()
|
||||
|
||||
// Disconnect first connection
|
||||
targetNode.disconnectInput(0)
|
||||
|
||||
// Create second connection
|
||||
const link2 = sourceNode2.connect(0, targetNode, 0)
|
||||
expect(link2).toBeDefined()
|
||||
expect(targetNode.inputs[0].link).toBe(link2?.id)
|
||||
|
||||
// Try to disconnect the first link (which is already disconnected)
|
||||
// It should not affect the current connection
|
||||
link1?.disconnect(graph)
|
||||
|
||||
// The input should still have the second link
|
||||
expect(targetNode.inputs[0].link).toBe(link2?.id)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,310 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { LinkConnector } from '@/lib/litegraph/src/canvas/LinkConnector'
|
||||
import { MovingOutputLink } from '@/lib/litegraph/src/canvas/MovingOutputLink'
|
||||
import { ToOutputRenderLink } from '@/lib/litegraph/src/canvas/ToOutputRenderLink'
|
||||
import { LGraphNode, LLink } from '@/lib/litegraph/src/litegraph'
|
||||
import { NodeInputSlot } from '@/lib/litegraph/src/node/NodeInputSlot'
|
||||
|
||||
import { createTestSubgraph } from '../subgraph/fixtures/subgraphHelpers'
|
||||
|
||||
describe('LinkConnector SubgraphInput connection validation', () => {
|
||||
let connector: LinkConnector
|
||||
const mockSetConnectingLinks = vi.fn()
|
||||
|
||||
beforeEach(() => {
|
||||
connector = new LinkConnector(mockSetConnectingLinks)
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('MovingOutputLink validation', () => {
|
||||
it('should implement canConnectToSubgraphInput method', () => {
|
||||
const subgraph = createTestSubgraph({
|
||||
inputs: [{ name: 'number_input', type: 'number' }]
|
||||
})
|
||||
|
||||
const sourceNode = new LGraphNode('SourceNode')
|
||||
sourceNode.addOutput('number_out', 'number')
|
||||
subgraph.add(sourceNode)
|
||||
|
||||
const targetNode = new LGraphNode('TargetNode')
|
||||
targetNode.addInput('number_in', 'number')
|
||||
subgraph.add(targetNode)
|
||||
|
||||
const link = new LLink(1, 'number', sourceNode.id, 0, targetNode.id, 0)
|
||||
subgraph._links.set(link.id, link)
|
||||
|
||||
const movingLink = new MovingOutputLink(subgraph, link)
|
||||
|
||||
// Verify the method exists
|
||||
expect(typeof movingLink.canConnectToSubgraphInput).toBe('function')
|
||||
})
|
||||
|
||||
it('should validate type compatibility correctly', () => {
|
||||
const subgraph = createTestSubgraph({
|
||||
inputs: [{ name: 'number_input', type: 'number' }]
|
||||
})
|
||||
|
||||
const sourceNode = new LGraphNode('SourceNode')
|
||||
sourceNode.addOutput('number_out', 'number')
|
||||
sourceNode.addOutput('string_out', 'string')
|
||||
subgraph.add(sourceNode)
|
||||
|
||||
const targetNode = new LGraphNode('TargetNode')
|
||||
targetNode.addInput('number_in', 'number')
|
||||
targetNode.addInput('string_in', 'string')
|
||||
subgraph.add(targetNode)
|
||||
|
||||
// Create valid link (number -> number)
|
||||
const validLink = new LLink(
|
||||
1,
|
||||
'number',
|
||||
sourceNode.id,
|
||||
0,
|
||||
targetNode.id,
|
||||
0
|
||||
)
|
||||
subgraph._links.set(validLink.id, validLink)
|
||||
const validMovingLink = new MovingOutputLink(subgraph, validLink)
|
||||
|
||||
// Create invalid link (string -> number)
|
||||
const invalidLink = new LLink(
|
||||
2,
|
||||
'string',
|
||||
sourceNode.id,
|
||||
1,
|
||||
targetNode.id,
|
||||
1
|
||||
)
|
||||
subgraph._links.set(invalidLink.id, invalidLink)
|
||||
const invalidMovingLink = new MovingOutputLink(subgraph, invalidLink)
|
||||
|
||||
const numberInput = subgraph.inputs[0]
|
||||
|
||||
// Test validation
|
||||
expect(validMovingLink.canConnectToSubgraphInput(numberInput)).toBe(true)
|
||||
expect(invalidMovingLink.canConnectToSubgraphInput(numberInput)).toBe(
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
it('should handle wildcard types', () => {
|
||||
const subgraph = createTestSubgraph({
|
||||
inputs: [{ name: 'wildcard_input', type: '*' }]
|
||||
})
|
||||
|
||||
const sourceNode = new LGraphNode('SourceNode')
|
||||
sourceNode.addOutput('number_out', 'number')
|
||||
subgraph.add(sourceNode)
|
||||
|
||||
const targetNode = new LGraphNode('TargetNode')
|
||||
targetNode.addInput('number_in', 'number')
|
||||
subgraph.add(targetNode)
|
||||
|
||||
const link = new LLink(1, 'number', sourceNode.id, 0, targetNode.id, 0)
|
||||
subgraph._links.set(link.id, link)
|
||||
const movingLink = new MovingOutputLink(subgraph, link)
|
||||
|
||||
const wildcardInput = subgraph.inputs[0]
|
||||
|
||||
// Wildcard should accept any type
|
||||
expect(movingLink.canConnectToSubgraphInput(wildcardInput)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ToOutputRenderLink validation', () => {
|
||||
it('should implement canConnectToSubgraphInput method', () => {
|
||||
// Create a minimal valid setup
|
||||
const subgraph = createTestSubgraph()
|
||||
const node = new LGraphNode('TestNode')
|
||||
node.id = 1
|
||||
node.addInput('test_in', 'number')
|
||||
subgraph.add(node)
|
||||
|
||||
const slot = node.inputs[0] as NodeInputSlot
|
||||
const renderLink = new ToOutputRenderLink(subgraph, node, slot)
|
||||
|
||||
// Verify the method exists
|
||||
expect(typeof renderLink.canConnectToSubgraphInput).toBe('function')
|
||||
})
|
||||
})
|
||||
|
||||
describe('dropOnIoNode validation', () => {
|
||||
it('should prevent invalid connections when dropping on SubgraphInputNode', () => {
|
||||
const subgraph = createTestSubgraph({
|
||||
inputs: [{ name: 'number_input', type: 'number' }]
|
||||
})
|
||||
|
||||
const sourceNode = new LGraphNode('SourceNode')
|
||||
sourceNode.addOutput('string_out', 'string')
|
||||
subgraph.add(sourceNode)
|
||||
|
||||
const targetNode = new LGraphNode('TargetNode')
|
||||
targetNode.addInput('string_in', 'string')
|
||||
subgraph.add(targetNode)
|
||||
|
||||
// Create an invalid link (string output -> string input, but subgraph expects number)
|
||||
const link = new LLink(1, 'string', sourceNode.id, 0, targetNode.id, 0)
|
||||
subgraph._links.set(link.id, link)
|
||||
const movingLink = new MovingOutputLink(subgraph, link)
|
||||
|
||||
// Mock console.warn to verify it's called
|
||||
const consoleWarnSpy = vi
|
||||
.spyOn(console, 'warn')
|
||||
.mockImplementation(() => {})
|
||||
|
||||
// Add the link to the connector
|
||||
connector.renderLinks.push(movingLink)
|
||||
connector.state.connectingTo = 'output'
|
||||
|
||||
// Create mock event
|
||||
const mockEvent = {
|
||||
canvasX: 100,
|
||||
canvasY: 100
|
||||
} as any
|
||||
|
||||
// Mock the getSlotInPosition to return the subgraph input
|
||||
const mockGetSlotInPosition = vi.fn().mockReturnValue(subgraph.inputs[0])
|
||||
subgraph.inputNode.getSlotInPosition = mockGetSlotInPosition
|
||||
|
||||
// Spy on connectToSubgraphInput to ensure it's NOT called
|
||||
const connectSpy = vi.spyOn(movingLink, 'connectToSubgraphInput')
|
||||
|
||||
// Drop on the SubgraphInputNode
|
||||
connector.dropOnIoNode(subgraph.inputNode, mockEvent)
|
||||
|
||||
// Verify that the invalid connection was skipped
|
||||
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
||||
'Invalid connection type',
|
||||
'string',
|
||||
'->',
|
||||
'number'
|
||||
)
|
||||
expect(connectSpy).not.toHaveBeenCalled()
|
||||
|
||||
consoleWarnSpy.mockRestore()
|
||||
})
|
||||
|
||||
it('should allow valid connections when dropping on SubgraphInputNode', () => {
|
||||
const subgraph = createTestSubgraph({
|
||||
inputs: [{ name: 'number_input', type: 'number' }]
|
||||
})
|
||||
|
||||
const sourceNode = new LGraphNode('SourceNode')
|
||||
sourceNode.addOutput('number_out', 'number')
|
||||
subgraph.add(sourceNode)
|
||||
|
||||
const targetNode = new LGraphNode('TargetNode')
|
||||
targetNode.addInput('number_in', 'number')
|
||||
subgraph.add(targetNode)
|
||||
|
||||
// Create a valid link (number -> number)
|
||||
const link = new LLink(1, 'number', sourceNode.id, 0, targetNode.id, 0)
|
||||
subgraph._links.set(link.id, link)
|
||||
const movingLink = new MovingOutputLink(subgraph, link)
|
||||
|
||||
// Add the link to the connector
|
||||
connector.renderLinks.push(movingLink)
|
||||
connector.state.connectingTo = 'output'
|
||||
|
||||
// Create mock event
|
||||
const mockEvent = {
|
||||
canvasX: 100,
|
||||
canvasY: 100
|
||||
} as any
|
||||
|
||||
// Mock the getSlotInPosition to return the subgraph input
|
||||
const mockGetSlotInPosition = vi.fn().mockReturnValue(subgraph.inputs[0])
|
||||
subgraph.inputNode.getSlotInPosition = mockGetSlotInPosition
|
||||
|
||||
// Spy on connectToSubgraphInput to ensure it IS called
|
||||
const connectSpy = vi.spyOn(movingLink, 'connectToSubgraphInput')
|
||||
|
||||
// Drop on the SubgraphInputNode
|
||||
connector.dropOnIoNode(subgraph.inputNode, mockEvent)
|
||||
|
||||
// Verify that the valid connection was made
|
||||
expect(connectSpy).toHaveBeenCalledWith(
|
||||
subgraph.inputs[0],
|
||||
connector.events
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('isSubgraphInputValidDrop', () => {
|
||||
it('should check if render links can connect to SubgraphInput', () => {
|
||||
const subgraph = createTestSubgraph({
|
||||
inputs: [{ name: 'number_input', type: 'number' }]
|
||||
})
|
||||
|
||||
const sourceNode = new LGraphNode('SourceNode')
|
||||
sourceNode.addOutput('number_out', 'number')
|
||||
sourceNode.addOutput('string_out', 'string')
|
||||
subgraph.add(sourceNode)
|
||||
|
||||
const targetNode = new LGraphNode('TargetNode')
|
||||
targetNode.addInput('number_in', 'number')
|
||||
targetNode.addInput('string_in', 'string')
|
||||
subgraph.add(targetNode)
|
||||
|
||||
// Create valid and invalid links
|
||||
const validLink = new LLink(
|
||||
1,
|
||||
'number',
|
||||
sourceNode.id,
|
||||
0,
|
||||
targetNode.id,
|
||||
0
|
||||
)
|
||||
const invalidLink = new LLink(
|
||||
2,
|
||||
'string',
|
||||
sourceNode.id,
|
||||
1,
|
||||
targetNode.id,
|
||||
1
|
||||
)
|
||||
subgraph._links.set(validLink.id, validLink)
|
||||
subgraph._links.set(invalidLink.id, invalidLink)
|
||||
|
||||
const validMovingLink = new MovingOutputLink(subgraph, validLink)
|
||||
const invalidMovingLink = new MovingOutputLink(subgraph, invalidLink)
|
||||
|
||||
const subgraphInput = subgraph.inputs[0]
|
||||
|
||||
// Test with only invalid link
|
||||
connector.renderLinks.length = 0
|
||||
connector.renderLinks.push(invalidMovingLink)
|
||||
expect(connector.isSubgraphInputValidDrop(subgraphInput)).toBe(false)
|
||||
|
||||
// Test with valid link
|
||||
connector.renderLinks.length = 0
|
||||
connector.renderLinks.push(validMovingLink)
|
||||
expect(connector.isSubgraphInputValidDrop(subgraphInput)).toBe(true)
|
||||
|
||||
// Test with mixed links
|
||||
connector.renderLinks.length = 0
|
||||
connector.renderLinks.push(invalidMovingLink, validMovingLink)
|
||||
expect(connector.isSubgraphInputValidDrop(subgraphInput)).toBe(true)
|
||||
})
|
||||
|
||||
it('should handle render links without canConnectToSubgraphInput method', () => {
|
||||
const subgraph = createTestSubgraph({
|
||||
inputs: [{ name: 'number_input', type: 'number' }]
|
||||
})
|
||||
|
||||
// Create a mock render link without the method
|
||||
const mockLink = {
|
||||
fromSlot: { type: 'number' }
|
||||
// No canConnectToSubgraphInput method
|
||||
} as any
|
||||
|
||||
connector.renderLinks.push(mockLink)
|
||||
|
||||
const subgraphInput = subgraph.inputs[0]
|
||||
|
||||
// Should return false as the link doesn't have the method
|
||||
expect(connector.isSubgraphInputValidDrop(subgraphInput)).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -185,6 +185,9 @@
|
||||
"Comfy_OpenClipspace": {
|
||||
"label": "Clipspace"
|
||||
},
|
||||
"Comfy_OpenManagerDialog": {
|
||||
"label": "مدير"
|
||||
},
|
||||
"Comfy_OpenWorkflow": {
|
||||
"label": "فتح سير عمل"
|
||||
},
|
||||
@@ -212,6 +215,12 @@
|
||||
"Comfy_ShowSettingsDialog": {
|
||||
"label": "عرض نافذة الإعدادات"
|
||||
},
|
||||
"Comfy_ToggleCanvasInfo": {
|
||||
"label": "أداء اللوحة"
|
||||
},
|
||||
"Comfy_ToggleHelpCenter": {
|
||||
"label": "مركز المساعدة"
|
||||
},
|
||||
"Comfy_ToggleTheme": {
|
||||
"label": "تبديل النمط (فاتح/داكن)"
|
||||
},
|
||||
|
||||
@@ -749,6 +749,7 @@
|
||||
"manageExtensions": "إدارة الإضافات",
|
||||
"onChange": "عند التغيير",
|
||||
"onChangeTooltip": "سيتم وضع سير العمل في قائمة الانتظار عند إجراء تغيير",
|
||||
"queue": "لوحة الانتظار",
|
||||
"refresh": "تحديث تعريفات العقد",
|
||||
"resetView": "إعادة تعيين عرض اللوحة",
|
||||
"run": "تشغيل",
|
||||
@@ -762,11 +763,11 @@
|
||||
"menuLabels": {
|
||||
"About ComfyUI": "حول ComfyUI",
|
||||
"Add Edit Model Step": "إضافة خطوة تعديل النموذج",
|
||||
"Bottom Panel": "لوحة سفلية",
|
||||
"Browse Templates": "تصفح القوالب",
|
||||
"Bypass/Unbypass Selected Nodes": "تجاوز/إلغاء تجاوز العقد المحددة",
|
||||
"Canvas Toggle Link Visibility": "تبديل ظهور الروابط على اللوحة",
|
||||
"Canvas Performance": "أداء اللوحة",
|
||||
"Canvas Toggle Lock": "تبديل قفل اللوحة",
|
||||
"Canvas Toggle Minimap": "تبديل الخريطة المصغرة على اللوحة",
|
||||
"Check for Updates": "التحقق من التحديثات",
|
||||
"Clear Pending Tasks": "مسح المهام المعلقة",
|
||||
"Clear Workflow": "مسح سير العمل",
|
||||
@@ -788,15 +789,20 @@
|
||||
"Exit Subgraph": "الخروج من الرسم الفرعي",
|
||||
"Export": "تصدير",
|
||||
"Export (API)": "تصدير (API)",
|
||||
"File": "ملف",
|
||||
"Fit Group To Contents": "ملائمة المجموعة للمحتويات",
|
||||
"Fit view to selected nodes": "تعديل العرض للعقد المحددة",
|
||||
"Focus Mode": "وضع التركيز",
|
||||
"Give Feedback": "تقديم ملاحظات",
|
||||
"Group Selected Nodes": "تجميع العقد المحددة",
|
||||
"Help": "مساعدة",
|
||||
"Help Center": "مركز المساعدة",
|
||||
"Increase Brush Size in MaskEditor": "زيادة حجم الفرشاة في محرر القناع",
|
||||
"Interrupt": "إيقاف مؤقت",
|
||||
"Load Default Workflow": "تحميل سير العمل الافتراضي",
|
||||
"Manage group nodes": "إدارة عقد المجموعة",
|
||||
"Manager": "المدير",
|
||||
"Minimap": "خريطة مصغرة",
|
||||
"Model Library": "مكتبة النماذج",
|
||||
"Move Selected Nodes Down": "تحريك العقد المحددة للأسفل",
|
||||
"Move Selected Nodes Left": "تحريك العقد المحددة لليسار",
|
||||
"Move Selected Nodes Right": "تحريك العقد المحددة لليمين",
|
||||
@@ -804,6 +810,8 @@
|
||||
"Mute/Unmute Selected Nodes": "كتم/إلغاء كتم العقد المحددة",
|
||||
"New": "جديد",
|
||||
"Next Opened Workflow": "سير العمل التالي المفتوح",
|
||||
"Node Library": "مكتبة العقد",
|
||||
"Node Links": "روابط العقد",
|
||||
"Open": "فتح",
|
||||
"Open 3D Viewer (Beta) for Selected Node": "فتح عارض ثلاثي الأبعاد (بيتا) للعقدة المحددة",
|
||||
"Open Custom Nodes Folder": "فتح مجلد العقد المخصصة",
|
||||
@@ -818,6 +826,7 @@
|
||||
"Pin/Unpin Selected Items": "تثبيت/إلغاء تثبيت العناصر المحددة",
|
||||
"Pin/Unpin Selected Nodes": "تثبيت/إلغاء تثبيت العقد المحددة",
|
||||
"Previous Opened Workflow": "سير العمل السابق المفتوح",
|
||||
"Queue Panel": "لوحة الانتظار",
|
||||
"Queue Prompt": "قائمة انتظار التعليمات",
|
||||
"Queue Prompt (Front)": "قائمة انتظار التعليمات (أمامي)",
|
||||
"Queue Selected Output Nodes": "قائمة انتظار عقد المخرجات المحددة",
|
||||
@@ -833,26 +842,21 @@
|
||||
"Show Keybindings Dialog": "عرض مربع حوار اختصارات لوحة المفاتيح",
|
||||
"Show Settings Dialog": "عرض نافذة الإعدادات",
|
||||
"Sign Out": "تسجيل خروج",
|
||||
"Toggle Bottom Panel": "تبديل اللوحة السفلية",
|
||||
"Toggle Essential Bottom Panel": "تبديل اللوحة السفلية الأساسية",
|
||||
"Toggle Focus Mode": "تبديل وضع التركيز",
|
||||
"Toggle Logs Bottom Panel": "تبديل لوحة السجلات السفلية",
|
||||
"Toggle Model Library Sidebar": "تبديل الشريط الجانبي لمكتبة النماذج",
|
||||
"Toggle Node Library Sidebar": "تبديل الشريط الجانبي لمكتبة العقد",
|
||||
"Toggle Queue Sidebar": "تبديل الشريط الجانبي لقائمة الانتظار",
|
||||
"Toggle Search Box": "تبديل مربع البحث",
|
||||
"Toggle Terminal Bottom Panel": "تبديل لوحة الطرفية السفلية",
|
||||
"Toggle Theme (Dark/Light)": "تبديل السمة (داكن/فاتح)",
|
||||
"Toggle View Controls Bottom Panel": "تبديل لوحة التحكم في العرض السفلية",
|
||||
"Toggle Workflows Sidebar": "تبديل الشريط الجانبي لسير العمل",
|
||||
"Toggle the Custom Nodes Manager": "تبديل مدير العقد المخصصة",
|
||||
"Toggle the Custom Nodes Manager Progress Bar": "تبديل شريط تقدم مدير العقد المخصصة",
|
||||
"Undo": "تراجع",
|
||||
"Ungroup selected group nodes": "فك تجميع عقد المجموعة المحددة",
|
||||
"Unpack the selected Subgraph": "فك تجميع الرسم البياني الفرعي المحدد",
|
||||
"Workflow": "سير العمل",
|
||||
"Workflows": "سير العمل",
|
||||
"Zoom In": "تكبير",
|
||||
"Zoom Out": "تصغير"
|
||||
"Zoom Out": "تصغير",
|
||||
"Zoom to fit": "تكبير لتناسب"
|
||||
},
|
||||
"minimap": {
|
||||
"nodeColors": "ألوان العقد",
|
||||
|
||||
@@ -164,8 +164,20 @@
|
||||
"Comfy_LoadDefaultWorkflow": {
|
||||
"label": "Load Default Workflow"
|
||||
},
|
||||
"Comfy_Manager_CustomNodesManager": {
|
||||
"label": "Toggle the Custom Nodes Manager"
|
||||
"Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": {
|
||||
"label": "Custom Nodes Manager"
|
||||
},
|
||||
"Comfy_Manager_CustomNodesManager_ShowLegacyCustomNodesMenu": {
|
||||
"label": "Custom Nodes (Legacy)"
|
||||
},
|
||||
"Comfy_Manager_ShowLegacyManagerMenu": {
|
||||
"label": "Manager Menu (Legacy)"
|
||||
},
|
||||
"Comfy_Manager_ShowMissingPacks": {
|
||||
"label": "Install Missing Custom Nodes"
|
||||
},
|
||||
"Comfy_Manager_ShowUpdateAvailablePacks": {
|
||||
"label": "Check for Custom Node Updates"
|
||||
},
|
||||
"Comfy_Manager_ToggleManagerProgressDialog": {
|
||||
"label": "Toggle the Custom Nodes Manager Progress Bar"
|
||||
@@ -179,12 +191,21 @@
|
||||
"Comfy_MaskEditor_OpenMaskEditor": {
|
||||
"label": "Open Mask Editor for Selected Node"
|
||||
},
|
||||
"Comfy_Memory_UnloadModels": {
|
||||
"label": "Unload Models"
|
||||
},
|
||||
"Comfy_Memory_UnloadModelsAndExecutionCache": {
|
||||
"label": "Unload Models and Execution Cache"
|
||||
},
|
||||
"Comfy_NewBlankWorkflow": {
|
||||
"label": "New Blank Workflow"
|
||||
},
|
||||
"Comfy_OpenClipspace": {
|
||||
"label": "Clipspace"
|
||||
},
|
||||
"Comfy_OpenManagerDialog": {
|
||||
"label": "Manager"
|
||||
},
|
||||
"Comfy_OpenWorkflow": {
|
||||
"label": "Open Workflow"
|
||||
},
|
||||
@@ -212,6 +233,12 @@
|
||||
"Comfy_ShowSettingsDialog": {
|
||||
"label": "Show Settings Dialog"
|
||||
},
|
||||
"Comfy_ToggleCanvasInfo": {
|
||||
"label": "Canvas Performance"
|
||||
},
|
||||
"Comfy_ToggleHelpCenter": {
|
||||
"label": "Help Center"
|
||||
},
|
||||
"Comfy_ToggleTheme": {
|
||||
"label": "Toggle Theme (Dark/Light)"
|
||||
},
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
"confirmed": "Confirmed",
|
||||
"reset": "Reset",
|
||||
"resetAll": "Reset All",
|
||||
"clearFilters": "Clear Filters",
|
||||
"resetAllKeybindingsTooltip": "Reset all keybindings to default",
|
||||
"customizeFolder": "Customize Folder",
|
||||
"icon": "Icon",
|
||||
@@ -99,12 +98,6 @@
|
||||
"nodes": "Nodes",
|
||||
"community": "Community",
|
||||
"all": "All",
|
||||
"versionMismatchWarning": "Version Compatibility Warning",
|
||||
"versionMismatchWarningMessage": "{warning}: {detail} Visit https://docs.comfy.org/installation/update_comfyui#common-update-issues for update instructions.",
|
||||
"frontendOutdated": "Frontend version {frontendVersion} is outdated. Backend requires {requiredVersion} or higher.",
|
||||
"frontendNewer": "Frontend version {frontendVersion} may not be compatible with backend version {backendVersion}.",
|
||||
"updateFrontend": "Update Frontend",
|
||||
"dismiss": "Dismiss",
|
||||
"update": "Update",
|
||||
"updated": "Updated",
|
||||
"resultsCount": "Found {count} Results",
|
||||
@@ -141,15 +134,18 @@
|
||||
"releaseTitle": "{package} {version} Release",
|
||||
"progressCountOf": "of",
|
||||
"keybindingAlreadyExists": "Keybinding already exists on",
|
||||
"commandProhibited": "Command {command} is prohibited. Contact an administrator for more information.",
|
||||
"startRecording": "Start Recording",
|
||||
"stopRecording": "Stop Recording",
|
||||
"micPermissionDenied": "Microphone permission denied",
|
||||
"noAudioRecorded": "No audio recorded",
|
||||
"nodesRunning": "nodes running",
|
||||
"duplicate": "Duplicate"
|
||||
"nodesRunning": "nodes running"
|
||||
},
|
||||
"manager": {
|
||||
"title": "Custom Nodes Manager",
|
||||
"legacyMenuNotAvailable": "Legacy manager menu is not available, defaulting to the new manager menu.",
|
||||
"legacyManagerUI": "Use Legacy UI",
|
||||
"legacyManagerUIDescription": "To use the legacy Manager UI, start ComfyUI with --enable-manager-legacy-ui",
|
||||
"failed": "Failed ({count})",
|
||||
"noNodesFound": "No nodes found",
|
||||
"noNodesFoundDescription": "The pack's nodes either could not be parsed, or the pack is a frontend extension only and doesn't have any nodes.",
|
||||
@@ -159,6 +155,12 @@
|
||||
"inWorkflow": "In Workflow",
|
||||
"infoPanelEmpty": "Click an item to see the info",
|
||||
"restartToApplyChanges": "To apply changes, please restart ComfyUI",
|
||||
"clickToFinishSetup": "Click",
|
||||
"toFinishSetup": "to finish setup",
|
||||
"applyChanges": "Apply Changes",
|
||||
"restartingBackend": "Restarting backend to apply changes...",
|
||||
"extensionsSuccessfullyInstalled": "Extension(s) successfully installed and are ready to use!",
|
||||
"installingDependencies": "Installing dependencies...",
|
||||
"loadingVersions": "Loading versions...",
|
||||
"selectVersion": "Select Version",
|
||||
"downloads": "Downloads",
|
||||
@@ -432,19 +434,12 @@
|
||||
"restart": "Restart"
|
||||
},
|
||||
"sideToolbar": {
|
||||
"themeToggle": "Toggle Theme",
|
||||
"helpCenter": "Help Center",
|
||||
"logout": "Logout",
|
||||
"queue": "Queue",
|
||||
"nodeLibrary": "Node Library",
|
||||
"workflows": "Workflows",
|
||||
"templates": "Templates",
|
||||
"labels": {
|
||||
"queue": "Queue",
|
||||
"nodes": "Nodes",
|
||||
"models": "Models",
|
||||
"workflows": "Workflows",
|
||||
"templates": "Templates"
|
||||
},
|
||||
"browseTemplates": "Browse example templates",
|
||||
"openWorkflow": "Open workflow in local file system",
|
||||
"newBlankWorkflow": "Create a new blank workflow",
|
||||
@@ -540,13 +535,7 @@
|
||||
"clipspace": "Open Clipspace",
|
||||
"resetView": "Reset canvas view",
|
||||
"clear": "Clear workflow",
|
||||
"toggleBottomPanel": "Toggle Bottom Panel",
|
||||
"theme": "Theme",
|
||||
"dark": "Dark",
|
||||
"light": "Light",
|
||||
"manageExtensions": "Manage Extensions",
|
||||
"settings": "Settings",
|
||||
"help": "Help"
|
||||
"toggleBottomPanel": "Toggle Bottom Panel"
|
||||
},
|
||||
"tabMenu": {
|
||||
"duplicateTab": "Duplicate Tab",
|
||||
@@ -559,8 +548,6 @@
|
||||
},
|
||||
"templateWorkflows": {
|
||||
"title": "Get Started with a Template",
|
||||
"loadingMore": "Loading more templates...",
|
||||
"searchPlaceholder": "Search templates...",
|
||||
"category": {
|
||||
"ComfyUI Examples": "ComfyUI Examples",
|
||||
"Custom Nodes": "Custom Nodes",
|
||||
@@ -890,8 +877,7 @@
|
||||
"fitView": "Fit View",
|
||||
"selectMode": "Select Mode",
|
||||
"panMode": "Pan Mode",
|
||||
"toggleLinkVisibility": "Toggle Link Visibility",
|
||||
"toggleMinimap": "Toggle Minimap"
|
||||
"toggleLinkVisibility": "Toggle Link Visibility"
|
||||
},
|
||||
"groupNode": {
|
||||
"create": "Create group node",
|
||||
@@ -942,6 +928,7 @@
|
||||
"menuLabels": {
|
||||
"Workflow": "Workflow",
|
||||
"Edit": "Edit",
|
||||
"Manager": "Manager",
|
||||
"Help": "Help",
|
||||
"Check for Updates": "Check for Updates",
|
||||
"Open Custom Nodes Folder": "Open Custom Nodes Folder",
|
||||
@@ -955,7 +942,6 @@
|
||||
"Quit": "Quit",
|
||||
"Reinstall": "Reinstall",
|
||||
"Restart": "Restart",
|
||||
"Open 3D Viewer (Beta) for Selected Node": "Open 3D Viewer (Beta) for Selected Node",
|
||||
"Browse Templates": "Browse Templates",
|
||||
"Add Edit Model Step": "Add Edit Model Step",
|
||||
"Delete Selected Items": "Delete Selected Items",
|
||||
@@ -968,7 +954,6 @@
|
||||
"Resize Selected Nodes": "Resize Selected Nodes",
|
||||
"Canvas Toggle Link Visibility": "Canvas Toggle Link Visibility",
|
||||
"Canvas Toggle Lock": "Canvas Toggle Lock",
|
||||
"Canvas Toggle Minimap": "Canvas Toggle Minimap",
|
||||
"Pin/Unpin Selected Items": "Pin/Unpin Selected Items",
|
||||
"Bypass/Unbypass Selected Nodes": "Bypass/Unbypass Selected Nodes",
|
||||
"Collapse/Expand Selected Nodes": "Collapse/Expand Selected Nodes",
|
||||
@@ -984,10 +969,8 @@
|
||||
"Export (API)": "Export (API)",
|
||||
"Give Feedback": "Give Feedback",
|
||||
"Convert Selection to Subgraph": "Convert Selection to Subgraph",
|
||||
"Exit Subgraph": "Exit Subgraph",
|
||||
"Fit Group To Contents": "Fit Group To Contents",
|
||||
"Group Selected Nodes": "Group Selected Nodes",
|
||||
"Unpack the selected Subgraph": "Unpack the selected Subgraph",
|
||||
"Convert selected nodes to group node": "Convert selected nodes to group node",
|
||||
"Manage group nodes": "Manage group nodes",
|
||||
"Ungroup selected group nodes": "Ungroup selected group nodes",
|
||||
@@ -998,11 +981,15 @@
|
||||
"ComfyUI Issues": "ComfyUI Issues",
|
||||
"Interrupt": "Interrupt",
|
||||
"Load Default Workflow": "Load Default Workflow",
|
||||
"Toggle the Custom Nodes Manager": "Toggle the Custom Nodes Manager",
|
||||
"Custom Nodes Manager": "Custom Nodes Manager",
|
||||
"Custom Nodes (Legacy)": "Custom Nodes (Legacy)",
|
||||
"Manager Menu (Legacy)": "Manager Menu (Legacy)",
|
||||
"Install Missing Custom Nodes": "Install Missing Custom Nodes",
|
||||
"Check for Custom Node Updates": "Check for Custom Node Updates",
|
||||
"Toggle the Custom Nodes Manager Progress Bar": "Toggle the Custom Nodes Manager Progress Bar",
|
||||
"Decrease Brush Size in MaskEditor": "Decrease Brush Size in MaskEditor",
|
||||
"Increase Brush Size in MaskEditor": "Increase Brush Size in MaskEditor",
|
||||
"Open Mask Editor for Selected Node": "Open Mask Editor for Selected Node",
|
||||
"Unload Models": "Unload Models",
|
||||
"Unload Models and Execution Cache": "Unload Models and Execution Cache",
|
||||
"New": "New",
|
||||
"Clipspace": "Clipspace",
|
||||
"Open": "Open",
|
||||
@@ -1023,11 +1010,8 @@
|
||||
"Previous Opened Workflow": "Previous Opened Workflow",
|
||||
"Toggle Search Box": "Toggle Search Box",
|
||||
"Toggle Bottom Panel": "Toggle Bottom Panel",
|
||||
"Show Keybindings Dialog": "Show Keybindings Dialog",
|
||||
"Toggle Terminal Bottom Panel": "Toggle Terminal Bottom Panel",
|
||||
"Toggle Logs Bottom Panel": "Toggle Logs Bottom Panel",
|
||||
"Toggle Essential Bottom Panel": "Toggle Essential Bottom Panel",
|
||||
"Toggle View Controls Bottom Panel": "Toggle View Controls Bottom Panel",
|
||||
"Toggle Focus Mode": "Toggle Focus Mode",
|
||||
"Toggle Model Library Sidebar": "Toggle Model Library Sidebar",
|
||||
"Toggle Node Library Sidebar": "Toggle Node Library Sidebar",
|
||||
@@ -1091,8 +1075,7 @@
|
||||
"User": "User",
|
||||
"Credits": "Credits",
|
||||
"API Nodes": "API Nodes",
|
||||
"Notification Preferences": "Notification Preferences",
|
||||
"3DViewer": "3DViewer"
|
||||
"Notification Preferences": "Notification Preferences"
|
||||
},
|
||||
"serverConfigItems": {
|
||||
"listen": {
|
||||
@@ -1376,13 +1359,6 @@
|
||||
"outdatedVersionGeneric": "Some nodes require a newer version of ComfyUI. Please update to use all nodes.",
|
||||
"coreNodesFromVersion": "Requires ComfyUI {version}:"
|
||||
},
|
||||
"versionMismatchWarning": {
|
||||
"title": "Version Compatibility Warning",
|
||||
"frontendOutdated": "Frontend version {frontendVersion} is outdated. Backend requires version {requiredVersion} or higher.",
|
||||
"frontendNewer": "Frontend version {frontendVersion} may not be compatible with backend version {backendVersion}.",
|
||||
"updateFrontend": "Update Frontend",
|
||||
"dismiss": "Dismiss"
|
||||
},
|
||||
"errorDialog": {
|
||||
"defaultTitle": "An error occurred",
|
||||
"loadWorkflowTitle": "Loading aborted due to error reloading workflow data",
|
||||
@@ -1444,31 +1420,12 @@
|
||||
"depth": "Depth",
|
||||
"lineart": "Lineart"
|
||||
},
|
||||
"upDirections": {
|
||||
"original": "Original"
|
||||
},
|
||||
"startRecording": "Start Recording",
|
||||
"stopRecording": "Stop Recording",
|
||||
"exportRecording": "Export Recording",
|
||||
"clearRecording": "Clear Recording",
|
||||
"resizeNodeMatchOutput": "Resize Node to match output",
|
||||
"loadingBackgroundImage": "Loading Background Image",
|
||||
"cameraType": {
|
||||
"perspective": "Perspective",
|
||||
"orthographic": "Orthographic"
|
||||
},
|
||||
"viewer": {
|
||||
"title": "3D Viewer (Beta)",
|
||||
"apply": "Apply",
|
||||
"cancel": "Cancel",
|
||||
"cameraType": "Camera Type",
|
||||
"sceneSettings": "Scene Settings",
|
||||
"cameraSettings": "Camera Settings",
|
||||
"lightSettings": "Light Settings",
|
||||
"exportSettings": "Export Settings",
|
||||
"modelSettings": "Model Settings"
|
||||
},
|
||||
"openIn3DViewer": "Open in 3D Viewer"
|
||||
"loadingBackgroundImage": "Loading Background Image"
|
||||
},
|
||||
"toastMessages": {
|
||||
"nothingToQueue": "Nothing to queue",
|
||||
@@ -1506,8 +1463,7 @@
|
||||
"useApiKeyTip": "Tip: Can't access normal login? Use the Comfy API Key option.",
|
||||
"nothingSelected": "Nothing selected",
|
||||
"cannotCreateSubgraph": "Cannot create subgraph",
|
||||
"failedToConvertToSubgraph": "Failed to convert items to subgraph",
|
||||
"failedToInitializeLoad3dViewer": "Failed to initialize 3D Viewer"
|
||||
"failedToConvertToSubgraph": "Failed to convert items to subgraph"
|
||||
},
|
||||
"auth": {
|
||||
"apiKey": {
|
||||
@@ -1660,32 +1616,5 @@
|
||||
"whatsNewPopup": {
|
||||
"learnMore": "Learn more",
|
||||
"noReleaseNotes": "No release notes available."
|
||||
},
|
||||
"breadcrumbsMenu": {
|
||||
"duplicate": "Duplicate",
|
||||
"clearWorkflow": "Clear Workflow",
|
||||
"deleteWorkflow": "Delete Workflow",
|
||||
"enterNewName": "Enter new name"
|
||||
},
|
||||
"shortcuts": {
|
||||
"essentials": "Essential",
|
||||
"viewControls": "View Controls",
|
||||
"manageShortcuts": "Manage Shortcuts",
|
||||
"noKeybinding": "No keybinding",
|
||||
"keyboardShortcuts": "Keyboard Shortcuts",
|
||||
"subcategories": {
|
||||
"workflow": "Workflow",
|
||||
"node": "Node",
|
||||
"queue": "Queue",
|
||||
"view": "View",
|
||||
"panelControls": "Panel Controls"
|
||||
}
|
||||
},
|
||||
"minimap": {
|
||||
"nodeColors": "Node Colors",
|
||||
"showLinks": "Show Links",
|
||||
"showGroups": "Show Frames/Groups",
|
||||
"renderBypassState": "Render Bypass State",
|
||||
"renderErrorState": "Render Error State"
|
||||
}
|
||||
}
|
||||
@@ -390,7 +390,7 @@
|
||||
},
|
||||
"LiteGraph_Canvas_LowQualityRenderingZoomThreshold": {
|
||||
"name": "Low quality rendering zoom threshold",
|
||||
"tooltip": "Render low quality shapes when zoomed out"
|
||||
"tooltip": "Zoom level threshold for performance mode. Lower values (0.1) = quality at all zoom levels. Higher values (1.0) = performance mode even when zoomed in. Performance mode simplifies rendering by hiding text labels, shadows, and details."
|
||||
},
|
||||
"LiteGraph_Canvas_MaximumFps": {
|
||||
"name": "Maximum FPS",
|
||||
|
||||
@@ -164,8 +164,20 @@
|
||||
"Comfy_LoadDefaultWorkflow": {
|
||||
"label": "Cargar flujo de trabajo predeterminado"
|
||||
},
|
||||
"Comfy_Manager_CustomNodesManager": {
|
||||
"label": "Administrador de nodos personalizados"
|
||||
"Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": {
|
||||
"label": "Nodos personalizados (Beta)"
|
||||
},
|
||||
"Comfy_Manager_CustomNodesManager_ShowLegacyCustomNodesMenu": {
|
||||
"label": "Nodos personalizados (heredados)"
|
||||
},
|
||||
"Comfy_Manager_ShowLegacyManagerMenu": {
|
||||
"label": "Menú del administrador (heredado)"
|
||||
},
|
||||
"Comfy_Manager_ShowMissingPacks": {
|
||||
"label": "Instalar faltantes"
|
||||
},
|
||||
"Comfy_Manager_ShowUpdateAvailablePacks": {
|
||||
"label": "Buscar actualizaciones"
|
||||
},
|
||||
"Comfy_Manager_ToggleManagerProgressDialog": {
|
||||
"label": "Alternar diálogo de progreso del administrador"
|
||||
@@ -179,12 +191,21 @@
|
||||
"Comfy_MaskEditor_OpenMaskEditor": {
|
||||
"label": "Abrir editor de máscara para el nodo seleccionado"
|
||||
},
|
||||
"Comfy_Memory_UnloadModels": {
|
||||
"label": "Descargar modelos"
|
||||
},
|
||||
"Comfy_Memory_UnloadModelsAndExecutionCache": {
|
||||
"label": "Descargar modelos y caché de ejecución"
|
||||
},
|
||||
"Comfy_NewBlankWorkflow": {
|
||||
"label": "Nuevo flujo de trabajo en blanco"
|
||||
},
|
||||
"Comfy_OpenClipspace": {
|
||||
"label": "Abrir espacio de clips"
|
||||
},
|
||||
"Comfy_OpenManagerDialog": {
|
||||
"label": "Administrador"
|
||||
},
|
||||
"Comfy_OpenWorkflow": {
|
||||
"label": "Abrir Flujo de Trabajo"
|
||||
},
|
||||
@@ -212,6 +233,12 @@
|
||||
"Comfy_ShowSettingsDialog": {
|
||||
"label": "Mostrar Diálogo de Configuraciones"
|
||||
},
|
||||
"Comfy_ToggleCanvasInfo": {
|
||||
"label": "Rendimiento del lienzo"
|
||||
},
|
||||
"Comfy_ToggleHelpCenter": {
|
||||
"label": "Centro de ayuda"
|
||||
},
|
||||
"Comfy_ToggleTheme": {
|
||||
"label": "Cambiar Tema (Oscuro/Claro)"
|
||||
},
|
||||
|
||||
@@ -272,11 +272,11 @@
|
||||
"category": "Categoría",
|
||||
"choose_file_to_upload": "elige archivo para subir",
|
||||
"clear": "Limpiar",
|
||||
"clearFilters": "Borrar filtros",
|
||||
"close": "Cerrar",
|
||||
"color": "Color",
|
||||
"comingSoon": "Próximamente",
|
||||
"command": "Comando",
|
||||
"commandProhibited": "El comando {command} está prohibido. Contacta a un administrador para más información.",
|
||||
"community": "Comunidad",
|
||||
"completed": "Completado",
|
||||
"confirm": "Confirmar",
|
||||
@@ -299,7 +299,6 @@
|
||||
"disabling": "Deshabilitando",
|
||||
"dismiss": "Descartar",
|
||||
"download": "Descargar",
|
||||
"duplicate": "Duplicar",
|
||||
"edit": "Editar",
|
||||
"empty": "Vacío",
|
||||
"enableAll": "Habilitar todo",
|
||||
@@ -570,10 +569,6 @@
|
||||
"applyingTexture": "Aplicando textura...",
|
||||
"backgroundColor": "Color de fondo",
|
||||
"camera": "Cámara",
|
||||
"cameraType": {
|
||||
"orthographic": "Ortográfica",
|
||||
"perspective": "Perspectiva"
|
||||
},
|
||||
"clearRecording": "Borrar grabación",
|
||||
"edgeThreshold": "Umbral de borde",
|
||||
"export": "Exportar",
|
||||
@@ -594,7 +589,6 @@
|
||||
"wireframe": "Malla"
|
||||
},
|
||||
"model": "Modelo",
|
||||
"openIn3DViewer": "Abrir en el visor 3D",
|
||||
"previewOutput": "Vista previa de salida",
|
||||
"removeBackgroundImage": "Eliminar imagen de fondo",
|
||||
"resizeNodeMatchOutput": "Redimensionar nodo para coincidir con la salida",
|
||||
@@ -605,22 +599,8 @@
|
||||
"switchCamera": "Cambiar cámara",
|
||||
"switchingMaterialMode": "Cambiando modo de material...",
|
||||
"upDirection": "Dirección hacia arriba",
|
||||
"upDirections": {
|
||||
"original": "Original"
|
||||
},
|
||||
"uploadBackgroundImage": "Subir imagen de fondo",
|
||||
"uploadTexture": "Subir textura",
|
||||
"viewer": {
|
||||
"apply": "Aplicar",
|
||||
"cameraSettings": "Configuración de la cámara",
|
||||
"cameraType": "Tipo de cámara",
|
||||
"cancel": "Cancelar",
|
||||
"exportSettings": "Configuración de exportación",
|
||||
"lightSettings": "Configuración de la luz",
|
||||
"modelSettings": "Configuración del modelo",
|
||||
"sceneSettings": "Configuración de la escena",
|
||||
"title": "Visor 3D (Beta)"
|
||||
}
|
||||
"uploadTexture": "Subir textura"
|
||||
},
|
||||
"loadWorkflowWarning": {
|
||||
"coreNodesFromVersion": "Requiere ComfyUI {version}:",
|
||||
@@ -667,6 +647,9 @@
|
||||
"installationQueue": "Cola de Instalación",
|
||||
"lastUpdated": "Última Actualización",
|
||||
"latestVersion": "Última",
|
||||
"legacyManagerUI": "Usar UI antigua",
|
||||
"legacyManagerUIDescription": "Para usar la UI antigua del Manager, inicia ComfyUI con --enable-manager-legacy-ui",
|
||||
"legacyMenuNotAvailable": "El menú del administrador antiguo no está disponible en esta versión de ComfyUI. Por favor, utiliza el nuevo menú del administrador en su lugar.",
|
||||
"license": "Licencia",
|
||||
"loadingVersions": "Cargando versiones...",
|
||||
"nightlyVersion": "Nocturna",
|
||||
@@ -767,6 +750,7 @@
|
||||
"Canvas Toggle Link Visibility": "Alternar visibilidad de enlace en lienzo",
|
||||
"Canvas Toggle Lock": "Alternar bloqueo en lienzo",
|
||||
"Canvas Toggle Minimap": "Lienzo: Alternar minimapa",
|
||||
"Check for Custom Node Updates": "Buscar actualizaciones de nodos personalizados",
|
||||
"Check for Updates": "Buscar actualizaciones",
|
||||
"Clear Pending Tasks": "Borrar tareas pendientes",
|
||||
"Clear Workflow": "Borrar flujo de trabajo",
|
||||
@@ -780,12 +764,13 @@
|
||||
"Contact Support": "Contactar soporte",
|
||||
"Convert Selection to Subgraph": "Convertir selección en subgrafo",
|
||||
"Convert selected nodes to group node": "Convertir nodos seleccionados en nodo de grupo",
|
||||
"Custom Nodes (Legacy)": "Nodos personalizados (heredado)",
|
||||
"Custom Nodes Manager": "Administrador de Nodos Personalizados",
|
||||
"Decrease Brush Size in MaskEditor": "Disminuir tamaño del pincel en MaskEditor",
|
||||
"Delete Selected Items": "Eliminar elementos seleccionados",
|
||||
"Desktop User Guide": "Guía de usuario de escritorio",
|
||||
"Duplicate Current Workflow": "Duplicar flujo de trabajo actual",
|
||||
"Edit": "Editar",
|
||||
"Exit Subgraph": "Salir de subgrafo",
|
||||
"Export": "Exportar",
|
||||
"Export (API)": "Exportar (API)",
|
||||
"Fit Group To Contents": "Ajustar grupo a contenidos",
|
||||
@@ -794,9 +779,12 @@
|
||||
"Group Selected Nodes": "Agrupar nodos seleccionados",
|
||||
"Help": "Ayuda",
|
||||
"Increase Brush Size in MaskEditor": "Aumentar tamaño del pincel en MaskEditor",
|
||||
"Install Missing Custom Nodes": "Instalar nodos personalizados faltantes",
|
||||
"Interrupt": "Interrumpir",
|
||||
"Load Default Workflow": "Cargar flujo de trabajo predeterminado",
|
||||
"Manage group nodes": "Gestionar nodos de grupo",
|
||||
"Manager": "Administrador",
|
||||
"Manager Menu (Legacy)": "Menú de gestión (heredado)",
|
||||
"Move Selected Nodes Down": "Mover nodos seleccionados hacia abajo",
|
||||
"Move Selected Nodes Left": "Mover nodos seleccionados hacia la izquierda",
|
||||
"Move Selected Nodes Right": "Mover nodos seleccionados hacia la derecha",
|
||||
@@ -805,7 +793,6 @@
|
||||
"New": "Nuevo",
|
||||
"Next Opened Workflow": "Siguiente flujo de trabajo abierto",
|
||||
"Open": "Abrir",
|
||||
"Open 3D Viewer (Beta) for Selected Node": "Abrir visor 3D (Beta) para el nodo seleccionado",
|
||||
"Open Custom Nodes Folder": "Abrir carpeta de nodos personalizados",
|
||||
"Open DevTools": "Abrir DevTools",
|
||||
"Open Inputs Folder": "Abrir carpeta de entradas",
|
||||
@@ -830,11 +817,9 @@
|
||||
"Restart": "Reiniciar",
|
||||
"Save": "Guardar",
|
||||
"Save As": "Guardar como",
|
||||
"Show Keybindings Dialog": "Mostrar diálogo de combinaciones de teclas",
|
||||
"Show Settings Dialog": "Mostrar diálogo de configuración",
|
||||
"Sign Out": "Cerrar sesión",
|
||||
"Toggle Bottom Panel": "Alternar panel inferior",
|
||||
"Toggle Essential Bottom Panel": "Alternar panel inferior esencial",
|
||||
"Toggle Focus Mode": "Alternar modo de enfoque",
|
||||
"Toggle Logs Bottom Panel": "Alternar panel inferior de registros",
|
||||
"Toggle Model Library Sidebar": "Alternar barra lateral de la biblioteca de modelos",
|
||||
@@ -843,24 +828,16 @@
|
||||
"Toggle Search Box": "Alternar caja de búsqueda",
|
||||
"Toggle Terminal Bottom Panel": "Alternar panel inferior de terminal",
|
||||
"Toggle Theme (Dark/Light)": "Alternar tema (Oscuro/Claro)",
|
||||
"Toggle View Controls Bottom Panel": "Alternar panel inferior de controles de vista",
|
||||
"Toggle Workflows Sidebar": "Alternar barra lateral de los flujos de trabajo",
|
||||
"Toggle the Custom Nodes Manager": "Alternar el Administrador de Nodos Personalizados",
|
||||
"Toggle the Custom Nodes Manager Progress Bar": "Alternar la Barra de Progreso del Administrador de Nodos Personalizados",
|
||||
"Undo": "Deshacer",
|
||||
"Ungroup selected group nodes": "Desagrupar nodos de grupo seleccionados",
|
||||
"Unpack the selected Subgraph": "Desempaquetar el Subgrafo seleccionado",
|
||||
"Unload Models": "Descargar modelos",
|
||||
"Unload Models and Execution Cache": "Descargar modelos y caché de ejecución",
|
||||
"Workflow": "Flujo de trabajo",
|
||||
"Zoom In": "Acercar",
|
||||
"Zoom Out": "Alejar"
|
||||
},
|
||||
"minimap": {
|
||||
"nodeColors": "Colores de nodos",
|
||||
"renderBypassState": "Mostrar estado de omisión",
|
||||
"renderErrorState": "Mostrar estado de error",
|
||||
"showGroups": "Mostrar marcos/grupos",
|
||||
"showLinks": "Mostrar enlaces"
|
||||
},
|
||||
"missingModelsDialog": {
|
||||
"doNotAskAgain": "No mostrar esto de nuevo",
|
||||
"missingModels": "Modelos faltantes",
|
||||
@@ -1127,7 +1104,6 @@
|
||||
},
|
||||
"settingsCategories": {
|
||||
"3D": "3D",
|
||||
"3DViewer": "Visor 3D",
|
||||
"API Nodes": "Nodos API",
|
||||
"About": "Acerca de",
|
||||
"Appearance": "Apariencia",
|
||||
@@ -1179,31 +1155,10 @@
|
||||
"Window": "Ventana",
|
||||
"Workflow": "Flujo de Trabajo"
|
||||
},
|
||||
"shortcuts": {
|
||||
"essentials": "Esenciales",
|
||||
"keyboardShortcuts": "Atajos de teclado",
|
||||
"manageShortcuts": "Gestionar atajos",
|
||||
"noKeybinding": "Sin asignación de tecla",
|
||||
"subcategories": {
|
||||
"node": "Nodo",
|
||||
"panelControls": "Controles del panel",
|
||||
"queue": "Cola",
|
||||
"view": "Vista",
|
||||
"workflow": "Flujo de trabajo"
|
||||
},
|
||||
"viewControls": "Controles de vista"
|
||||
},
|
||||
"sideToolbar": {
|
||||
"browseTemplates": "Explorar plantillas de ejemplo",
|
||||
"downloads": "Descargas",
|
||||
"helpCenter": "Centro de ayuda",
|
||||
"labels": {
|
||||
"models": "Modelos",
|
||||
"nodes": "Nodos",
|
||||
"queue": "Cola",
|
||||
"templates": "Plantillas",
|
||||
"workflows": "Flujos de trabajo"
|
||||
},
|
||||
"logout": "Cerrar sesión",
|
||||
"modelLibrary": "Biblioteca de modelos",
|
||||
"newBlankWorkflow": "Crear un nuevo flujo de trabajo en blanco",
|
||||
@@ -1241,7 +1196,6 @@
|
||||
},
|
||||
"showFlatList": "Mostrar lista plana"
|
||||
},
|
||||
"templates": "Plantillas",
|
||||
"workflowTab": {
|
||||
"confirmDelete": "¿Estás seguro de que quieres eliminar este flujo de trabajo?",
|
||||
"confirmDeleteTitle": "¿Eliminar flujo de trabajo?",
|
||||
@@ -1288,8 +1242,6 @@
|
||||
"Video": "Video",
|
||||
"Video API": "API de Video"
|
||||
},
|
||||
"loadingMore": "Cargando más plantillas...",
|
||||
"searchPlaceholder": "Buscar plantillas...",
|
||||
"template": {
|
||||
"3D": {
|
||||
"3d_hunyuan3d_image_to_model": "Hunyuan3D 2.0",
|
||||
@@ -1612,7 +1564,6 @@
|
||||
"failedToExportModel": "Error al exportar modelo como {format}",
|
||||
"failedToFetchBalance": "No se pudo obtener el saldo: {error}",
|
||||
"failedToFetchLogs": "Error al obtener los registros del servidor",
|
||||
"failedToInitializeLoad3dViewer": "No se pudo inicializar el visor 3D",
|
||||
"failedToInitiateCreditPurchase": "No se pudo iniciar la compra de créditos: {error}",
|
||||
"failedToPurchaseCredits": "No se pudo comprar créditos: {error}",
|
||||
"fileLoadError": "No se puede encontrar el flujo de trabajo en {fileName}",
|
||||
|
||||
@@ -164,8 +164,20 @@
|
||||
"Comfy_LoadDefaultWorkflow": {
|
||||
"label": "Charger le flux de travail par défaut"
|
||||
},
|
||||
"Comfy_Manager_CustomNodesManager": {
|
||||
"label": "Gestionnaire de Nœuds Personnalisés"
|
||||
"Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": {
|
||||
"label": "Nœuds personnalisés (Beta)"
|
||||
},
|
||||
"Comfy_Manager_CustomNodesManager_ShowLegacyCustomNodesMenu": {
|
||||
"label": "Nœuds personnalisés (hérités)"
|
||||
},
|
||||
"Comfy_Manager_ShowLegacyManagerMenu": {
|
||||
"label": "Menu du gestionnaire (héritage)"
|
||||
},
|
||||
"Comfy_Manager_ShowMissingPacks": {
|
||||
"label": "Installer manquants"
|
||||
},
|
||||
"Comfy_Manager_ShowUpdateAvailablePacks": {
|
||||
"label": "Vérifier les mises à jour"
|
||||
},
|
||||
"Comfy_Manager_ToggleManagerProgressDialog": {
|
||||
"label": "Basculer la boîte de dialogue de progression"
|
||||
@@ -179,12 +191,21 @@
|
||||
"Comfy_MaskEditor_OpenMaskEditor": {
|
||||
"label": "Ouvrir l'éditeur de masque pour le nœud sélectionné"
|
||||
},
|
||||
"Comfy_Memory_UnloadModels": {
|
||||
"label": "Décharger les modèles"
|
||||
},
|
||||
"Comfy_Memory_UnloadModelsAndExecutionCache": {
|
||||
"label": "Décharger les modèles et le cache d'exécution"
|
||||
},
|
||||
"Comfy_NewBlankWorkflow": {
|
||||
"label": "Nouveau flux de travail vierge"
|
||||
},
|
||||
"Comfy_OpenClipspace": {
|
||||
"label": "Espace de clip"
|
||||
},
|
||||
"Comfy_OpenManagerDialog": {
|
||||
"label": "Gestionnaire"
|
||||
},
|
||||
"Comfy_OpenWorkflow": {
|
||||
"label": "Ouvrir le flux de travail"
|
||||
},
|
||||
@@ -212,6 +233,12 @@
|
||||
"Comfy_ShowSettingsDialog": {
|
||||
"label": "Afficher la boîte de dialogue des paramètres"
|
||||
},
|
||||
"Comfy_ToggleCanvasInfo": {
|
||||
"label": "Performance du canvas"
|
||||
},
|
||||
"Comfy_ToggleHelpCenter": {
|
||||
"label": "Centre d'aide"
|
||||
},
|
||||
"Comfy_ToggleTheme": {
|
||||
"label": "Changer de thème (Sombre/Clair)"
|
||||
},
|
||||
|
||||
@@ -272,11 +272,11 @@
|
||||
"category": "Catégorie",
|
||||
"choose_file_to_upload": "choisissez le fichier à télécharger",
|
||||
"clear": "Effacer",
|
||||
"clearFilters": "Effacer les filtres",
|
||||
"close": "Fermer",
|
||||
"color": "Couleur",
|
||||
"comingSoon": "Bientôt disponible",
|
||||
"command": "Commande",
|
||||
"commandProhibited": "La commande {command} est interdite. Contactez un administrateur pour plus d'informations.",
|
||||
"community": "Communauté",
|
||||
"completed": "Terminé",
|
||||
"confirm": "Confirmer",
|
||||
@@ -299,7 +299,6 @@
|
||||
"disabling": "Désactivation",
|
||||
"dismiss": "Fermer",
|
||||
"download": "Télécharger",
|
||||
"duplicate": "Dupliquer",
|
||||
"edit": "Modifier",
|
||||
"empty": "Vide",
|
||||
"enableAll": "Activer tout",
|
||||
@@ -570,10 +569,6 @@
|
||||
"applyingTexture": "Application de la texture...",
|
||||
"backgroundColor": "Couleur de fond",
|
||||
"camera": "Caméra",
|
||||
"cameraType": {
|
||||
"orthographic": "Orthographique",
|
||||
"perspective": "Perspective"
|
||||
},
|
||||
"clearRecording": "Effacer l'enregistrement",
|
||||
"edgeThreshold": "Seuil de Bordure",
|
||||
"export": "Exportation",
|
||||
@@ -594,7 +589,6 @@
|
||||
"wireframe": "Fil de fer"
|
||||
},
|
||||
"model": "Modèle",
|
||||
"openIn3DViewer": "Ouvrir dans la visionneuse 3D",
|
||||
"previewOutput": "Aperçu de la sortie",
|
||||
"removeBackgroundImage": "Supprimer l'image de fond",
|
||||
"resizeNodeMatchOutput": "Redimensionner le nœud pour correspondre à la sortie",
|
||||
@@ -605,22 +599,8 @@
|
||||
"switchCamera": "Changer de caméra",
|
||||
"switchingMaterialMode": "Changement de mode de matériau...",
|
||||
"upDirection": "Direction Haut",
|
||||
"upDirections": {
|
||||
"original": "Original"
|
||||
},
|
||||
"uploadBackgroundImage": "Télécharger l'image de fond",
|
||||
"uploadTexture": "Télécharger Texture",
|
||||
"viewer": {
|
||||
"apply": "Appliquer",
|
||||
"cameraSettings": "Paramètres de la caméra",
|
||||
"cameraType": "Type de caméra",
|
||||
"cancel": "Annuler",
|
||||
"exportSettings": "Paramètres d’exportation",
|
||||
"lightSettings": "Paramètres de l’éclairage",
|
||||
"modelSettings": "Paramètres du modèle",
|
||||
"sceneSettings": "Paramètres de la scène",
|
||||
"title": "Visionneuse 3D (Bêta)"
|
||||
}
|
||||
"uploadTexture": "Télécharger Texture"
|
||||
},
|
||||
"loadWorkflowWarning": {
|
||||
"coreNodesFromVersion": "Nécessite ComfyUI {version} :",
|
||||
@@ -667,6 +647,9 @@
|
||||
"installationQueue": "File d'attente d'installation",
|
||||
"lastUpdated": "Dernière mise à jour",
|
||||
"latestVersion": "Dernière",
|
||||
"legacyManagerUI": "Utiliser l'interface utilisateur héritée",
|
||||
"legacyManagerUIDescription": "Pour utiliser l'interface utilisateur de gestion héritée, démarrez ComfyUI avec --enable-manager-legacy-ui",
|
||||
"legacyMenuNotAvailable": "Le menu du gestionnaire de l'ancienne version n'est pas disponible dans cette version de ComfyUI. Veuillez utiliser le nouveau menu du gestionnaire à la place.",
|
||||
"license": "Licence",
|
||||
"loadingVersions": "Chargement des versions...",
|
||||
"nightlyVersion": "Nocturne",
|
||||
@@ -767,6 +750,7 @@
|
||||
"Canvas Toggle Link Visibility": "Basculer la visibilité du lien de la toile",
|
||||
"Canvas Toggle Lock": "Basculer le verrouillage de la toile",
|
||||
"Canvas Toggle Minimap": "Basculer la mini-carte du canevas",
|
||||
"Check for Custom Node Updates": "Vérifier les mises à jour des nœuds personnalisés",
|
||||
"Check for Updates": "Vérifier les mises à jour",
|
||||
"Clear Pending Tasks": "Effacer les tâches en attente",
|
||||
"Clear Workflow": "Effacer le flux de travail",
|
||||
@@ -780,12 +764,13 @@
|
||||
"Contact Support": "Contacter le support",
|
||||
"Convert Selection to Subgraph": "Convertir la sélection en sous-graphe",
|
||||
"Convert selected nodes to group node": "Convertir les nœuds sélectionnés en nœud de groupe",
|
||||
"Custom Nodes (Legacy)": "Nœuds personnalisés (héritage)",
|
||||
"Custom Nodes Manager": "Gestionnaire de Nœuds Personnalisés",
|
||||
"Decrease Brush Size in MaskEditor": "Réduire la taille du pinceau dans MaskEditor",
|
||||
"Delete Selected Items": "Supprimer les éléments sélectionnés",
|
||||
"Desktop User Guide": "Guide de l'utilisateur de bureau",
|
||||
"Duplicate Current Workflow": "Dupliquer le flux de travail actuel",
|
||||
"Edit": "Éditer",
|
||||
"Exit Subgraph": "Quitter le sous-graphe",
|
||||
"Export": "Exporter",
|
||||
"Export (API)": "Exporter (API)",
|
||||
"Fit Group To Contents": "Ajuster le groupe au contenu",
|
||||
@@ -794,9 +779,12 @@
|
||||
"Group Selected Nodes": "Grouper les nœuds sélectionnés",
|
||||
"Help": "Aide",
|
||||
"Increase Brush Size in MaskEditor": "Augmenter la taille du pinceau dans MaskEditor",
|
||||
"Install Missing Custom Nodes": "Installer les nœuds personnalisés manquants",
|
||||
"Interrupt": "Interrompre",
|
||||
"Load Default Workflow": "Charger le flux de travail par défaut",
|
||||
"Manage group nodes": "Gérer les nœuds de groupe",
|
||||
"Manager": "Gestionnaire",
|
||||
"Manager Menu (Legacy)": "Menu du gestionnaire (héritage)",
|
||||
"Move Selected Nodes Down": "Déplacer les nœuds sélectionnés vers le bas",
|
||||
"Move Selected Nodes Left": "Déplacer les nœuds sélectionnés vers la gauche",
|
||||
"Move Selected Nodes Right": "Déplacer les nœuds sélectionnés vers la droite",
|
||||
@@ -805,7 +793,6 @@
|
||||
"New": "Nouveau",
|
||||
"Next Opened Workflow": "Prochain flux de travail ouvert",
|
||||
"Open": "Ouvrir",
|
||||
"Open 3D Viewer (Beta) for Selected Node": "Ouvrir le visualiseur 3D (bêta) pour le nœud sélectionné",
|
||||
"Open Custom Nodes Folder": "Ouvrir le dossier des nœuds personnalisés",
|
||||
"Open DevTools": "Ouvrir DevTools",
|
||||
"Open Inputs Folder": "Ouvrir le dossier des entrées",
|
||||
@@ -830,11 +817,9 @@
|
||||
"Restart": "Redémarrer",
|
||||
"Save": "Enregistrer",
|
||||
"Save As": "Enregistrer sous",
|
||||
"Show Keybindings Dialog": "Afficher la boîte de dialogue des raccourcis clavier",
|
||||
"Show Settings Dialog": "Afficher la boîte de dialogue des paramètres",
|
||||
"Sign Out": "Se déconnecter",
|
||||
"Toggle Bottom Panel": "Basculer le panneau inférieur",
|
||||
"Toggle Essential Bottom Panel": "Afficher/Masquer le panneau inférieur essentiel",
|
||||
"Toggle Focus Mode": "Basculer le mode focus",
|
||||
"Toggle Logs Bottom Panel": "Basculer le panneau inférieur des journaux",
|
||||
"Toggle Model Library Sidebar": "Afficher/Masquer la barre latérale de la bibliothèque de modèles",
|
||||
@@ -843,24 +828,16 @@
|
||||
"Toggle Search Box": "Basculer la boîte de recherche",
|
||||
"Toggle Terminal Bottom Panel": "Basculer le panneau inférieur du terminal",
|
||||
"Toggle Theme (Dark/Light)": "Basculer le thème (Sombre/Clair)",
|
||||
"Toggle View Controls Bottom Panel": "Afficher/Masquer le panneau inférieur des contrôles de vue",
|
||||
"Toggle Workflows Sidebar": "Afficher/Masquer la barre latérale des workflows",
|
||||
"Toggle the Custom Nodes Manager": "Basculer le gestionnaire de nœuds personnalisés",
|
||||
"Toggle the Custom Nodes Manager Progress Bar": "Basculer la barre de progression du gestionnaire de nœuds personnalisés",
|
||||
"Undo": "Annuler",
|
||||
"Ungroup selected group nodes": "Dégrouper les nœuds de groupe sélectionnés",
|
||||
"Unpack the selected Subgraph": "Décompresser le Subgraph sélectionné",
|
||||
"Unload Models": "Décharger les modèles",
|
||||
"Unload Models and Execution Cache": "Décharger les modèles et le cache d'exécution",
|
||||
"Workflow": "Flux de travail",
|
||||
"Zoom In": "Zoom avant",
|
||||
"Zoom Out": "Zoom arrière"
|
||||
},
|
||||
"minimap": {
|
||||
"nodeColors": "Couleurs des nœuds",
|
||||
"renderBypassState": "Afficher l’état de contournement",
|
||||
"renderErrorState": "Afficher l’état d’erreur",
|
||||
"showGroups": "Afficher les cadres/groupes",
|
||||
"showLinks": "Afficher les liens"
|
||||
},
|
||||
"missingModelsDialog": {
|
||||
"doNotAskAgain": "Ne plus afficher ce message",
|
||||
"missingModels": "Modèles manquants",
|
||||
@@ -1127,7 +1104,6 @@
|
||||
},
|
||||
"settingsCategories": {
|
||||
"3D": "3D",
|
||||
"3DViewer": "Visionneuse 3D",
|
||||
"API Nodes": "Nœuds API",
|
||||
"About": "À Propos",
|
||||
"Appearance": "Apparence",
|
||||
@@ -1179,31 +1155,10 @@
|
||||
"Window": "Fenêtre",
|
||||
"Workflow": "Flux de Travail"
|
||||
},
|
||||
"shortcuts": {
|
||||
"essentials": "Essentiel",
|
||||
"keyboardShortcuts": "Raccourcis clavier",
|
||||
"manageShortcuts": "Gérer les raccourcis",
|
||||
"noKeybinding": "Aucun raccourci",
|
||||
"subcategories": {
|
||||
"node": "Nœud",
|
||||
"panelControls": "Contrôles du panneau",
|
||||
"queue": "File d’attente",
|
||||
"view": "Vue",
|
||||
"workflow": "Flux de travail"
|
||||
},
|
||||
"viewControls": "Contrôles d’affichage"
|
||||
},
|
||||
"sideToolbar": {
|
||||
"browseTemplates": "Parcourir les modèles d'exemple",
|
||||
"downloads": "Téléchargements",
|
||||
"helpCenter": "Centre d'aide",
|
||||
"labels": {
|
||||
"models": "Modèles",
|
||||
"nodes": "Nœuds",
|
||||
"queue": "File d’attente",
|
||||
"templates": "Modèles",
|
||||
"workflows": "Flux de travail"
|
||||
},
|
||||
"logout": "Déconnexion",
|
||||
"modelLibrary": "Bibliothèque de modèles",
|
||||
"newBlankWorkflow": "Créer un nouveau flux de travail vierge",
|
||||
@@ -1241,7 +1196,6 @@
|
||||
},
|
||||
"showFlatList": "Afficher la liste plate"
|
||||
},
|
||||
"templates": "Modèles",
|
||||
"workflowTab": {
|
||||
"confirmDelete": "Êtes-vous sûr de vouloir supprimer ce flux de travail ?",
|
||||
"confirmDeleteTitle": "Supprimer le flux de travail ?",
|
||||
@@ -1288,8 +1242,6 @@
|
||||
"Video": "Vidéo",
|
||||
"Video API": "API vidéo"
|
||||
},
|
||||
"loadingMore": "Chargement de plus de modèles...",
|
||||
"searchPlaceholder": "Rechercher des modèles...",
|
||||
"template": {
|
||||
"3D": {
|
||||
"3d_hunyuan3d_image_to_model": "Hunyuan3D",
|
||||
@@ -1612,7 +1564,6 @@
|
||||
"failedToExportModel": "Échec de l'exportation du modèle en {format}",
|
||||
"failedToFetchBalance": "Échec de la récupération du solde : {error}",
|
||||
"failedToFetchLogs": "Échec de la récupération des journaux du serveur",
|
||||
"failedToInitializeLoad3dViewer": "Échec de l'initialisation du visualiseur 3D",
|
||||
"failedToInitiateCreditPurchase": "Échec de l'initiation de l'achat de crédits : {error}",
|
||||
"failedToPurchaseCredits": "Échec de l'achat de crédits : {error}",
|
||||
"fileLoadError": "Impossible de trouver le flux de travail dans {fileName}",
|
||||
|
||||
@@ -164,8 +164,20 @@
|
||||
"Comfy_LoadDefaultWorkflow": {
|
||||
"label": "デフォルトのワークフローを読み込む"
|
||||
},
|
||||
"Comfy_Manager_CustomNodesManager": {
|
||||
"label": "カスタムノードマネージャ"
|
||||
"Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": {
|
||||
"label": "カスタムノード(ベータ版)"
|
||||
},
|
||||
"Comfy_Manager_CustomNodesManager_ShowLegacyCustomNodesMenu": {
|
||||
"label": "カスタムノード(レガシー)"
|
||||
},
|
||||
"Comfy_Manager_ShowLegacyManagerMenu": {
|
||||
"label": "マネージャーメニュー(レガシー)"
|
||||
},
|
||||
"Comfy_Manager_ShowMissingPacks": {
|
||||
"label": "不足しているパックをインストール"
|
||||
},
|
||||
"Comfy_Manager_ShowUpdateAvailablePacks": {
|
||||
"label": "更新を確認"
|
||||
},
|
||||
"Comfy_Manager_ToggleManagerProgressDialog": {
|
||||
"label": "プログレスダイアログの切り替え"
|
||||
@@ -179,12 +191,21 @@
|
||||
"Comfy_MaskEditor_OpenMaskEditor": {
|
||||
"label": "選択したノードのマスクエディタを開く"
|
||||
},
|
||||
"Comfy_Memory_UnloadModels": {
|
||||
"label": "モデルのアンロード"
|
||||
},
|
||||
"Comfy_Memory_UnloadModelsAndExecutionCache": {
|
||||
"label": "モデルと実行キャッシュのアンロード"
|
||||
},
|
||||
"Comfy_NewBlankWorkflow": {
|
||||
"label": "新しい空のワークフロー"
|
||||
},
|
||||
"Comfy_OpenClipspace": {
|
||||
"label": "クリップスペース"
|
||||
},
|
||||
"Comfy_OpenManagerDialog": {
|
||||
"label": "マネージャー"
|
||||
},
|
||||
"Comfy_OpenWorkflow": {
|
||||
"label": "ワークフローを開く"
|
||||
},
|
||||
@@ -212,6 +233,12 @@
|
||||
"Comfy_ShowSettingsDialog": {
|
||||
"label": "設定ダイアログを表示"
|
||||
},
|
||||
"Comfy_ToggleCanvasInfo": {
|
||||
"label": "キャンバスパフォーマンス"
|
||||
},
|
||||
"Comfy_ToggleHelpCenter": {
|
||||
"label": "ヘルプセンター"
|
||||
},
|
||||
"Comfy_ToggleTheme": {
|
||||
"label": "テーマの切り替え(ダーク/ライト)"
|
||||
},
|
||||
|
||||
@@ -272,11 +272,11 @@
|
||||
"category": "カテゴリ",
|
||||
"choose_file_to_upload": "アップロードするファイルを選択",
|
||||
"clear": "クリア",
|
||||
"clearFilters": "フィルターをクリア",
|
||||
"close": "閉じる",
|
||||
"color": "色",
|
||||
"comingSoon": "近日公開",
|
||||
"command": "コマンド",
|
||||
"commandProhibited": "コマンド {command} は禁止されています。詳細は管理者にお問い合わせください。",
|
||||
"community": "コミュニティ",
|
||||
"completed": "完了",
|
||||
"confirm": "確認",
|
||||
@@ -299,7 +299,6 @@
|
||||
"disabling": "無効化",
|
||||
"dismiss": "閉じる",
|
||||
"download": "ダウンロード",
|
||||
"duplicate": "複製",
|
||||
"edit": "編集",
|
||||
"empty": "空",
|
||||
"enableAll": "すべて有効にする",
|
||||
@@ -570,10 +569,6 @@
|
||||
"applyingTexture": "テクスチャを適用中...",
|
||||
"backgroundColor": "背景色",
|
||||
"camera": "カメラ",
|
||||
"cameraType": {
|
||||
"orthographic": "オルソグラフィック",
|
||||
"perspective": "パースペクティブ"
|
||||
},
|
||||
"clearRecording": "録画をクリア",
|
||||
"edgeThreshold": "エッジ閾値",
|
||||
"export": "エクスポート",
|
||||
@@ -594,7 +589,6 @@
|
||||
"wireframe": "ワイヤーフレーム"
|
||||
},
|
||||
"model": "モデル",
|
||||
"openIn3DViewer": "3Dビューアで開く",
|
||||
"previewOutput": "出力のプレビュー",
|
||||
"removeBackgroundImage": "背景画像を削除",
|
||||
"resizeNodeMatchOutput": "ノードを出力に合わせてリサイズ",
|
||||
@@ -605,22 +599,8 @@
|
||||
"switchCamera": "カメラを切り替える",
|
||||
"switchingMaterialMode": "マテリアルモードの切り替え中...",
|
||||
"upDirection": "上方向",
|
||||
"upDirections": {
|
||||
"original": "オリジナル"
|
||||
},
|
||||
"uploadBackgroundImage": "背景画像をアップロード",
|
||||
"uploadTexture": "テクスチャをアップロード",
|
||||
"viewer": {
|
||||
"apply": "適用",
|
||||
"cameraSettings": "カメラ設定",
|
||||
"cameraType": "カメラタイプ",
|
||||
"cancel": "キャンセル",
|
||||
"exportSettings": "エクスポート設定",
|
||||
"lightSettings": "ライト設定",
|
||||
"modelSettings": "モデル設定",
|
||||
"sceneSettings": "シーン設定",
|
||||
"title": "3Dビューア(ベータ)"
|
||||
}
|
||||
"uploadTexture": "テクスチャをアップロード"
|
||||
},
|
||||
"loadWorkflowWarning": {
|
||||
"coreNodesFromVersion": "ComfyUI {version} が必要です:",
|
||||
@@ -667,6 +647,9 @@
|
||||
"installationQueue": "インストールキュー",
|
||||
"lastUpdated": "最終更新日",
|
||||
"latestVersion": "最新",
|
||||
"legacyManagerUI": "レガシーUIを使用する",
|
||||
"legacyManagerUIDescription": "レガシーManager UIを使用するには、--enable-manager-legacy-uiを付けてComfyUIを起動してください",
|
||||
"legacyMenuNotAvailable": "このバージョンのComfyUIでは、レガシーマネージャーメニューは利用できません。新しいマネージャーメニューを使用してください。",
|
||||
"license": "ライセンス",
|
||||
"loadingVersions": "バージョンを読み込んでいます...",
|
||||
"nightlyVersion": "ナイトリー",
|
||||
@@ -767,6 +750,7 @@
|
||||
"Canvas Toggle Link Visibility": "キャンバスのリンク表示を切り替え",
|
||||
"Canvas Toggle Lock": "キャンバスのロックを切り替え",
|
||||
"Canvas Toggle Minimap": "キャンバス ミニマップの切り替え",
|
||||
"Check for Custom Node Updates": "カスタムノードのアップデートを確認",
|
||||
"Check for Updates": "更新を確認する",
|
||||
"Clear Pending Tasks": "保留中のタスクをクリア",
|
||||
"Clear Workflow": "ワークフローをクリア",
|
||||
@@ -780,12 +764,13 @@
|
||||
"Contact Support": "サポートに連絡",
|
||||
"Convert Selection to Subgraph": "選択範囲をサブグラフに変換",
|
||||
"Convert selected nodes to group node": "選択したノードをグループノードに変換",
|
||||
"Custom Nodes (Legacy)": "カスタムノード(レガシー)",
|
||||
"Custom Nodes Manager": "カスタムノードマネージャ",
|
||||
"Decrease Brush Size in MaskEditor": "マスクエディタでブラシサイズを小さくする",
|
||||
"Delete Selected Items": "選択したアイテムを削除",
|
||||
"Desktop User Guide": "デスクトップユーザーガイド",
|
||||
"Duplicate Current Workflow": "現在のワークフローを複製",
|
||||
"Edit": "編集",
|
||||
"Exit Subgraph": "サブグラフを終了",
|
||||
"Export": "エクスポート",
|
||||
"Export (API)": "エクスポート (API)",
|
||||
"Fit Group To Contents": "グループを内容に合わせる",
|
||||
@@ -794,9 +779,12 @@
|
||||
"Group Selected Nodes": "選択したノードをグループ化",
|
||||
"Help": "ヘルプ",
|
||||
"Increase Brush Size in MaskEditor": "マスクエディタでブラシサイズを大きくする",
|
||||
"Install Missing Custom Nodes": "不足しているカスタムノードをインストール",
|
||||
"Interrupt": "中断",
|
||||
"Load Default Workflow": "デフォルトワークフローを読み込む",
|
||||
"Manage group nodes": "グループノードを管理",
|
||||
"Manager": "マネージャー",
|
||||
"Manager Menu (Legacy)": "マネージャーメニュー(レガシー)",
|
||||
"Move Selected Nodes Down": "選択したノードを下へ移動",
|
||||
"Move Selected Nodes Left": "選択したノードを左へ移動",
|
||||
"Move Selected Nodes Right": "選択したノードを右へ移動",
|
||||
@@ -805,7 +793,6 @@
|
||||
"New": "新規",
|
||||
"Next Opened Workflow": "次に開いたワークフロー",
|
||||
"Open": "開く",
|
||||
"Open 3D Viewer (Beta) for Selected Node": "選択したノードの3Dビューアー(ベータ)を開く",
|
||||
"Open Custom Nodes Folder": "カスタムノードフォルダを開く",
|
||||
"Open DevTools": "DevToolsを開く",
|
||||
"Open Inputs Folder": "入力フォルダを開く",
|
||||
@@ -830,11 +817,9 @@
|
||||
"Restart": "再起動",
|
||||
"Save": "保存",
|
||||
"Save As": "名前を付けて保存",
|
||||
"Show Keybindings Dialog": "キーバインドダイアログを表示",
|
||||
"Show Settings Dialog": "設定ダイアログを表示",
|
||||
"Sign Out": "サインアウト",
|
||||
"Toggle Bottom Panel": "下部パネルの切り替え",
|
||||
"Toggle Essential Bottom Panel": "エッセンシャル下部パネルの切り替え",
|
||||
"Toggle Focus Mode": "フォーカスモードの切り替え",
|
||||
"Toggle Logs Bottom Panel": "ログパネル下部を切り替え",
|
||||
"Toggle Model Library Sidebar": "モデルライブラリサイドバーを切り替え",
|
||||
@@ -843,24 +828,16 @@
|
||||
"Toggle Search Box": "検索ボックスの切り替え",
|
||||
"Toggle Terminal Bottom Panel": "ターミナルパネル下部を切り替え",
|
||||
"Toggle Theme (Dark/Light)": "テーマを切り替え(ダーク/ライト)",
|
||||
"Toggle View Controls Bottom Panel": "ビューコントロール下部パネルの切り替え",
|
||||
"Toggle Workflows Sidebar": "ワークフローサイドバーを切り替え",
|
||||
"Toggle the Custom Nodes Manager": "カスタムノードマネージャーを切り替え",
|
||||
"Toggle the Custom Nodes Manager Progress Bar": "カスタムノードマネージャーの進行状況バーを切り替え",
|
||||
"Undo": "元に戻す",
|
||||
"Ungroup selected group nodes": "選択したグループノードのグループ解除",
|
||||
"Unpack the selected Subgraph": "選択したサブグラフを展開",
|
||||
"Unload Models": "モデルのアンロード",
|
||||
"Unload Models and Execution Cache": "モデルと実行キャッシュのアンロード",
|
||||
"Workflow": "ワークフロー",
|
||||
"Zoom In": "ズームイン",
|
||||
"Zoom Out": "ズームアウト"
|
||||
},
|
||||
"minimap": {
|
||||
"nodeColors": "ノードの色",
|
||||
"renderBypassState": "バイパス状態を表示",
|
||||
"renderErrorState": "エラー状態を表示",
|
||||
"showGroups": "フレーム/グループを表示",
|
||||
"showLinks": "リンクを表示"
|
||||
},
|
||||
"missingModelsDialog": {
|
||||
"doNotAskAgain": "再度表示しない",
|
||||
"missingModels": "モデルが見つかりません",
|
||||
@@ -1127,7 +1104,6 @@
|
||||
},
|
||||
"settingsCategories": {
|
||||
"3D": "3D",
|
||||
"3DViewer": "3Dビューア",
|
||||
"API Nodes": "APIノード",
|
||||
"About": "情報",
|
||||
"Appearance": "外観",
|
||||
@@ -1179,31 +1155,10 @@
|
||||
"Window": "ウィンドウ",
|
||||
"Workflow": "ワークフロー"
|
||||
},
|
||||
"shortcuts": {
|
||||
"essentials": "基本",
|
||||
"keyboardShortcuts": "キーボードショートカット",
|
||||
"manageShortcuts": "ショートカットの管理",
|
||||
"noKeybinding": "キー割り当てなし",
|
||||
"subcategories": {
|
||||
"node": "ノード",
|
||||
"panelControls": "パネルコントロール",
|
||||
"queue": "キュー",
|
||||
"view": "ビュー",
|
||||
"workflow": "ワークフロー"
|
||||
},
|
||||
"viewControls": "表示コントロール"
|
||||
},
|
||||
"sideToolbar": {
|
||||
"browseTemplates": "サンプルテンプレートを表示",
|
||||
"downloads": "ダウンロード",
|
||||
"helpCenter": "ヘルプセンター",
|
||||
"labels": {
|
||||
"models": "モデル",
|
||||
"nodes": "ノード",
|
||||
"queue": "キュー",
|
||||
"templates": "テンプレート",
|
||||
"workflows": "ワークフロー"
|
||||
},
|
||||
"logout": "ログアウト",
|
||||
"modelLibrary": "モデルライブラリ",
|
||||
"newBlankWorkflow": "新しい空のワークフローを作成",
|
||||
@@ -1241,7 +1196,6 @@
|
||||
},
|
||||
"showFlatList": "フラットリストを表示"
|
||||
},
|
||||
"templates": "テンプレート",
|
||||
"workflowTab": {
|
||||
"confirmDelete": "このワークフローを削除してもよろしいですか?",
|
||||
"confirmDeleteTitle": "ワークフローを削除しますか?",
|
||||
@@ -1288,8 +1242,6 @@
|
||||
"Video": "ビデオ",
|
||||
"Video API": "動画API"
|
||||
},
|
||||
"loadingMore": "さらにテンプレートを読み込み中...",
|
||||
"searchPlaceholder": "テンプレートを検索...",
|
||||
"template": {
|
||||
"3D": {
|
||||
"3d_hunyuan3d_image_to_model": "Hunyuan3D",
|
||||
@@ -1612,7 +1564,6 @@
|
||||
"failedToExportModel": "{format}としてモデルのエクスポートに失敗しました",
|
||||
"failedToFetchBalance": "残高の取得に失敗しました: {error}",
|
||||
"failedToFetchLogs": "サーバーログの取得に失敗しました",
|
||||
"failedToInitializeLoad3dViewer": "3Dビューアの初期化に失敗しました",
|
||||
"failedToInitiateCreditPurchase": "クレジット購入の開始に失敗しました: {error}",
|
||||
"failedToPurchaseCredits": "クレジットの購入に失敗しました: {error}",
|
||||
"fileLoadError": "{fileName}でワークフローが見つかりません",
|
||||
|
||||
@@ -164,8 +164,20 @@
|
||||
"Comfy_LoadDefaultWorkflow": {
|
||||
"label": "기본 워크플로 로드"
|
||||
},
|
||||
"Comfy_Manager_CustomNodesManager": {
|
||||
"label": "사용자 정의 노드 관리자"
|
||||
"Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": {
|
||||
"label": "사용자 정의 노드 (베타)"
|
||||
},
|
||||
"Comfy_Manager_CustomNodesManager_ShowLegacyCustomNodesMenu": {
|
||||
"label": "커스텀 노드 (레거시)"
|
||||
},
|
||||
"Comfy_Manager_ShowLegacyManagerMenu": {
|
||||
"label": "매니저 메뉴 (레거시)"
|
||||
},
|
||||
"Comfy_Manager_ShowMissingPacks": {
|
||||
"label": "누락된 팩 설치"
|
||||
},
|
||||
"Comfy_Manager_ShowUpdateAvailablePacks": {
|
||||
"label": "업데이트 확인"
|
||||
},
|
||||
"Comfy_Manager_ToggleManagerProgressDialog": {
|
||||
"label": "진행 상황 대화 상자 전환"
|
||||
@@ -179,12 +191,21 @@
|
||||
"Comfy_MaskEditor_OpenMaskEditor": {
|
||||
"label": "선택한 노드 마스크 편집기 열기"
|
||||
},
|
||||
"Comfy_Memory_UnloadModels": {
|
||||
"label": "모델 언로드"
|
||||
},
|
||||
"Comfy_Memory_UnloadModelsAndExecutionCache": {
|
||||
"label": "모델 및 실행 캐시 언로드"
|
||||
},
|
||||
"Comfy_NewBlankWorkflow": {
|
||||
"label": "새로운 빈 워크플로"
|
||||
},
|
||||
"Comfy_OpenClipspace": {
|
||||
"label": "클립스페이스"
|
||||
},
|
||||
"Comfy_OpenManagerDialog": {
|
||||
"label": "매니저"
|
||||
},
|
||||
"Comfy_OpenWorkflow": {
|
||||
"label": "워크플로 열기"
|
||||
},
|
||||
@@ -212,6 +233,12 @@
|
||||
"Comfy_ShowSettingsDialog": {
|
||||
"label": "설정 대화상자 보기"
|
||||
},
|
||||
"Comfy_ToggleCanvasInfo": {
|
||||
"label": "캔버스 성능"
|
||||
},
|
||||
"Comfy_ToggleHelpCenter": {
|
||||
"label": "도움말 센터"
|
||||
},
|
||||
"Comfy_ToggleTheme": {
|
||||
"label": "밝기 테마 전환 (어두운/밝은)"
|
||||
},
|
||||
|
||||
@@ -272,11 +272,11 @@
|
||||
"category": "카테고리",
|
||||
"choose_file_to_upload": "업로드할 파일 선택",
|
||||
"clear": "지우기",
|
||||
"clearFilters": "필터 지우기",
|
||||
"close": "닫기",
|
||||
"color": "색상",
|
||||
"comingSoon": "곧 출시 예정",
|
||||
"command": "명령",
|
||||
"commandProhibited": "명령 {command}은 금지되었습니다. 자세한 정보는 관리자에게 문의하십시오.",
|
||||
"community": "커뮤니티",
|
||||
"completed": "완료됨",
|
||||
"confirm": "확인",
|
||||
@@ -299,7 +299,6 @@
|
||||
"disabling": "비활성화 중",
|
||||
"dismiss": "닫기",
|
||||
"download": "다운로드",
|
||||
"duplicate": "복제",
|
||||
"edit": "편집",
|
||||
"empty": "비어 있음",
|
||||
"enableAll": "모두 활성화",
|
||||
@@ -570,10 +569,6 @@
|
||||
"applyingTexture": "텍스처 적용 중...",
|
||||
"backgroundColor": "배경색",
|
||||
"camera": "카메라",
|
||||
"cameraType": {
|
||||
"orthographic": "직교",
|
||||
"perspective": "원근"
|
||||
},
|
||||
"clearRecording": "녹화 지우기",
|
||||
"edgeThreshold": "엣지 임계값",
|
||||
"export": "내보내기",
|
||||
@@ -594,7 +589,6 @@
|
||||
"wireframe": "와이어프레임"
|
||||
},
|
||||
"model": "모델",
|
||||
"openIn3DViewer": "3D 뷰어에서 열기",
|
||||
"previewOutput": "출력 미리보기",
|
||||
"removeBackgroundImage": "배경 이미지 제거",
|
||||
"resizeNodeMatchOutput": "노드 크기를 출력에 맞추기",
|
||||
@@ -605,22 +599,8 @@
|
||||
"switchCamera": "카메라 전환",
|
||||
"switchingMaterialMode": "재질 모드 전환 중...",
|
||||
"upDirection": "위 방향",
|
||||
"upDirections": {
|
||||
"original": "원본"
|
||||
},
|
||||
"uploadBackgroundImage": "배경 이미지 업로드",
|
||||
"uploadTexture": "텍스처 업로드",
|
||||
"viewer": {
|
||||
"apply": "적용",
|
||||
"cameraSettings": "카메라 설정",
|
||||
"cameraType": "카메라 유형",
|
||||
"cancel": "취소",
|
||||
"exportSettings": "내보내기 설정",
|
||||
"lightSettings": "조명 설정",
|
||||
"modelSettings": "모델 설정",
|
||||
"sceneSettings": "씬 설정",
|
||||
"title": "3D 뷰어 (베타)"
|
||||
}
|
||||
"uploadTexture": "텍스처 업로드"
|
||||
},
|
||||
"loadWorkflowWarning": {
|
||||
"coreNodesFromVersion": "ComfyUI {version} 이상 필요:",
|
||||
@@ -667,6 +647,9 @@
|
||||
"installationQueue": "설치 대기열",
|
||||
"lastUpdated": "마지막 업데이트",
|
||||
"latestVersion": "최신",
|
||||
"legacyManagerUI": "레거시 UI 사용",
|
||||
"legacyManagerUIDescription": "레거시 매니저 UI를 사용하려면, ComfyUI를 --enable-manager-legacy-ui로 시작하세요",
|
||||
"legacyMenuNotAvailable": "이 버전의 ComfyUI에서는 레거시 매니저 메뉴를 사용할 수 없습니다. 대신 새로운 매니저 메뉴를 사용하십시오.",
|
||||
"license": "라이선스",
|
||||
"loadingVersions": "버전 로딩 중...",
|
||||
"nightlyVersion": "최신 테스트 버전(nightly)",
|
||||
@@ -767,6 +750,7 @@
|
||||
"Canvas Toggle Link Visibility": "캔버스 토글 링크 가시성",
|
||||
"Canvas Toggle Lock": "캔버스 토글 잠금",
|
||||
"Canvas Toggle Minimap": "캔버스 미니맵 전환",
|
||||
"Check for Custom Node Updates": "커스텀 노드 업데이트 확인",
|
||||
"Check for Updates": "업데이트 확인",
|
||||
"Clear Pending Tasks": "보류 중인 작업 제거하기",
|
||||
"Clear Workflow": "워크플로 지우기",
|
||||
@@ -780,12 +764,13 @@
|
||||
"Contact Support": "고객 지원 문의",
|
||||
"Convert Selection to Subgraph": "선택 영역을 서브그래프로 변환",
|
||||
"Convert selected nodes to group node": "선택한 노드를 그룹 노드로 변환",
|
||||
"Custom Nodes (Legacy)": "커스텀 노드(레거시)",
|
||||
"Custom Nodes Manager": "사용자 정의 노드 관리자",
|
||||
"Decrease Brush Size in MaskEditor": "마스크 편집기에서 브러시 크기 줄이기",
|
||||
"Delete Selected Items": "선택한 항목 삭제",
|
||||
"Desktop User Guide": "데스크톱 사용자 가이드",
|
||||
"Duplicate Current Workflow": "현재 워크플로 복제",
|
||||
"Edit": "편집",
|
||||
"Exit Subgraph": "서브그래프 종료",
|
||||
"Export": "내보내기",
|
||||
"Export (API)": "내보내기 (API)",
|
||||
"Fit Group To Contents": "그룹을 내용에 맞게 조정",
|
||||
@@ -794,9 +779,12 @@
|
||||
"Group Selected Nodes": "선택한 노드 그룹화",
|
||||
"Help": "도움말",
|
||||
"Increase Brush Size in MaskEditor": "마스크 편집기에서 브러시 크기 늘리기",
|
||||
"Install Missing Custom Nodes": "누락된 커스텀 노드 설치",
|
||||
"Interrupt": "중단",
|
||||
"Load Default Workflow": "기본 워크플로 불러오기",
|
||||
"Manage group nodes": "그룹 노드 관리",
|
||||
"Manager": "매니저",
|
||||
"Manager Menu (Legacy)": "매니저 메뉴(레거시)",
|
||||
"Move Selected Nodes Down": "선택한 노드 아래로 이동",
|
||||
"Move Selected Nodes Left": "선택한 노드 왼쪽으로 이동",
|
||||
"Move Selected Nodes Right": "선택한 노드 오른쪽으로 이동",
|
||||
@@ -805,7 +793,6 @@
|
||||
"New": "새로 만들기",
|
||||
"Next Opened Workflow": "다음 열린 워크플로",
|
||||
"Open": "열기",
|
||||
"Open 3D Viewer (Beta) for Selected Node": "선택한 노드에 대해 3D 뷰어(베타) 열기",
|
||||
"Open Custom Nodes Folder": "사용자 정의 노드 폴더 열기",
|
||||
"Open DevTools": "개발자 도구 열기",
|
||||
"Open Inputs Folder": "입력 폴더 열기",
|
||||
@@ -830,11 +817,9 @@
|
||||
"Restart": "재시작",
|
||||
"Save": "저장",
|
||||
"Save As": "다른 이름으로 저장",
|
||||
"Show Keybindings Dialog": "키 바인딩 대화상자 표시",
|
||||
"Show Settings Dialog": "설정 대화상자 표시",
|
||||
"Sign Out": "로그아웃",
|
||||
"Toggle Bottom Panel": "하단 패널 전환",
|
||||
"Toggle Essential Bottom Panel": "필수 하단 패널 전환",
|
||||
"Toggle Focus Mode": "포커스 모드 전환",
|
||||
"Toggle Logs Bottom Panel": "로그 하단 패널 전환",
|
||||
"Toggle Model Library Sidebar": "모델 라이브러리 사이드바 전환",
|
||||
@@ -843,24 +828,16 @@
|
||||
"Toggle Search Box": "검색 상자 전환",
|
||||
"Toggle Terminal Bottom Panel": "터미널 하단 패널 전환",
|
||||
"Toggle Theme (Dark/Light)": "테마 전환 (어두운/밝은)",
|
||||
"Toggle View Controls Bottom Panel": "뷰 컨트롤 하단 패널 전환",
|
||||
"Toggle Workflows Sidebar": "워크플로 사이드바 전환",
|
||||
"Toggle the Custom Nodes Manager": "커스텀 노드 매니저 전환",
|
||||
"Toggle Workflows Sidebar": "워크플로우 사이드바 전환",
|
||||
"Toggle the Custom Nodes Manager Progress Bar": "커스텀 노드 매니저 진행률 표시줄 전환",
|
||||
"Undo": "실행 취소",
|
||||
"Ungroup selected group nodes": "선택한 그룹 노드 그룹 해제",
|
||||
"Unpack the selected Subgraph": "선택한 서브그래프 풀기",
|
||||
"Unload Models": "모델 언로드",
|
||||
"Unload Models and Execution Cache": "모델 및 실행 캐시 언로드",
|
||||
"Workflow": "워크플로",
|
||||
"Zoom In": "확대",
|
||||
"Zoom Out": "축소"
|
||||
},
|
||||
"minimap": {
|
||||
"nodeColors": "노드 색상",
|
||||
"renderBypassState": "바이패스 상태 렌더링",
|
||||
"renderErrorState": "에러 상태 렌더링",
|
||||
"showGroups": "프레임/그룹 표시",
|
||||
"showLinks": "링크 표시"
|
||||
},
|
||||
"missingModelsDialog": {
|
||||
"doNotAskAgain": "다시 보지 않기",
|
||||
"missingModels": "모델이 없습니다",
|
||||
@@ -1127,7 +1104,6 @@
|
||||
},
|
||||
"settingsCategories": {
|
||||
"3D": "3D",
|
||||
"3DViewer": "3D뷰어",
|
||||
"API Nodes": "API 노드",
|
||||
"About": "정보",
|
||||
"Appearance": "모양",
|
||||
@@ -1179,31 +1155,10 @@
|
||||
"Window": "창",
|
||||
"Workflow": "워크플로"
|
||||
},
|
||||
"shortcuts": {
|
||||
"essentials": "필수",
|
||||
"keyboardShortcuts": "키보드 단축키",
|
||||
"manageShortcuts": "단축키 관리",
|
||||
"noKeybinding": "단축키 없음",
|
||||
"subcategories": {
|
||||
"node": "노드",
|
||||
"panelControls": "패널 컨트롤",
|
||||
"queue": "대기열",
|
||||
"view": "보기",
|
||||
"workflow": "워크플로우"
|
||||
},
|
||||
"viewControls": "보기 컨트롤"
|
||||
},
|
||||
"sideToolbar": {
|
||||
"browseTemplates": "예제 템플릿 탐색",
|
||||
"downloads": "다운로드",
|
||||
"helpCenter": "도움말 센터",
|
||||
"labels": {
|
||||
"models": "모델",
|
||||
"nodes": "노드",
|
||||
"queue": "대기열",
|
||||
"templates": "템플릿",
|
||||
"workflows": "워크플로우"
|
||||
},
|
||||
"logout": "로그아웃",
|
||||
"modelLibrary": "모델 라이브러리",
|
||||
"newBlankWorkflow": "새 빈 워크플로 만들기",
|
||||
@@ -1241,7 +1196,6 @@
|
||||
},
|
||||
"showFlatList": "평면 목록 표시"
|
||||
},
|
||||
"templates": "템플릿",
|
||||
"workflowTab": {
|
||||
"confirmDelete": "정말로 이 워크플로를 삭제하시겠습니까?",
|
||||
"confirmDeleteTitle": "워크플로 삭제",
|
||||
@@ -1288,8 +1242,6 @@
|
||||
"Video": "비디오",
|
||||
"Video API": "비디오 API"
|
||||
},
|
||||
"loadingMore": "템플릿을 더 불러오는 중...",
|
||||
"searchPlaceholder": "템플릿 검색...",
|
||||
"template": {
|
||||
"3D": {
|
||||
"3d_hunyuan3d_image_to_model": "Hunyuan3D 2.0",
|
||||
@@ -1612,7 +1564,6 @@
|
||||
"failedToExportModel": "{format} 형식으로 모델 내보내기에 실패했습니다",
|
||||
"failedToFetchBalance": "잔액을 가져오지 못했습니다: {error}",
|
||||
"failedToFetchLogs": "서버 로그를 가져오는 데 실패했습니다",
|
||||
"failedToInitializeLoad3dViewer": "3D 뷰어 초기화에 실패했습니다",
|
||||
"failedToInitiateCreditPurchase": "크레딧 구매를 시작하지 못했습니다: {error}",
|
||||
"failedToPurchaseCredits": "크레딧 구매에 실패했습니다: {error}",
|
||||
"fileLoadError": "{fileName}에서 워크플로를 찾을 수 없습니다",
|
||||
|
||||
@@ -164,8 +164,20 @@
|
||||
"Comfy_LoadDefaultWorkflow": {
|
||||
"label": "Загрузить стандартный рабочий процесс"
|
||||
},
|
||||
"Comfy_Manager_CustomNodesManager": {
|
||||
"label": "Менеджер Пользовательских Узлов"
|
||||
"Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": {
|
||||
"label": "Пользовательские узлы (Бета)"
|
||||
},
|
||||
"Comfy_Manager_CustomNodesManager_ShowLegacyCustomNodesMenu": {
|
||||
"label": "Пользовательские узлы (устаревшие)"
|
||||
},
|
||||
"Comfy_Manager_ShowLegacyManagerMenu": {
|
||||
"label": "Меню менеджера (устаревшее)"
|
||||
},
|
||||
"Comfy_Manager_ShowMissingPacks": {
|
||||
"label": "Установить отсутствующие"
|
||||
},
|
||||
"Comfy_Manager_ShowUpdateAvailablePacks": {
|
||||
"label": "Проверить наличие обновлений"
|
||||
},
|
||||
"Comfy_Manager_ToggleManagerProgressDialog": {
|
||||
"label": "Переключить диалоговое окно прогресса"
|
||||
@@ -179,12 +191,21 @@
|
||||
"Comfy_MaskEditor_OpenMaskEditor": {
|
||||
"label": "Открыть редактор масок для выбранной ноды"
|
||||
},
|
||||
"Comfy_Memory_UnloadModels": {
|
||||
"label": "Выгрузить модели"
|
||||
},
|
||||
"Comfy_Memory_UnloadModelsAndExecutionCache": {
|
||||
"label": "Выгрузить модели и кэш выполнения"
|
||||
},
|
||||
"Comfy_NewBlankWorkflow": {
|
||||
"label": "Новый пустой рабочий процесс"
|
||||
},
|
||||
"Comfy_OpenClipspace": {
|
||||
"label": "Клипспейс"
|
||||
},
|
||||
"Comfy_OpenManagerDialog": {
|
||||
"label": "Менеджер"
|
||||
},
|
||||
"Comfy_OpenWorkflow": {
|
||||
"label": "Открыть рабочий процесс"
|
||||
},
|
||||
@@ -212,6 +233,12 @@
|
||||
"Comfy_ShowSettingsDialog": {
|
||||
"label": "Показать диалог настроек"
|
||||
},
|
||||
"Comfy_ToggleCanvasInfo": {
|
||||
"label": "Производительность холста"
|
||||
},
|
||||
"Comfy_ToggleHelpCenter": {
|
||||
"label": "Центр поддержки"
|
||||
},
|
||||
"Comfy_ToggleTheme": {
|
||||
"label": "Переключить тему (Тёмная/Светлая)"
|
||||
},
|
||||
|
||||
@@ -272,11 +272,11 @@
|
||||
"category": "Категория",
|
||||
"choose_file_to_upload": "выберите файл для загрузки",
|
||||
"clear": "Очистить",
|
||||
"clearFilters": "Сбросить фильтры",
|
||||
"close": "Закрыть",
|
||||
"color": "Цвет",
|
||||
"comingSoon": "Скоро будет",
|
||||
"command": "Команда",
|
||||
"commandProhibited": "Команда {command} запрещена. Свяжитесь с администратором для получения дополнительной информации.",
|
||||
"community": "Сообщество",
|
||||
"completed": "Завершено",
|
||||
"confirm": "Подтвердить",
|
||||
@@ -299,7 +299,6 @@
|
||||
"disabling": "Отключение",
|
||||
"dismiss": "Закрыть",
|
||||
"download": "Скачать",
|
||||
"duplicate": "Дублировать",
|
||||
"edit": "Редактировать",
|
||||
"empty": "Пусто",
|
||||
"enableAll": "Включить все",
|
||||
@@ -570,10 +569,6 @@
|
||||
"applyingTexture": "Применение текстуры...",
|
||||
"backgroundColor": "Цвет фона",
|
||||
"camera": "Камера",
|
||||
"cameraType": {
|
||||
"orthographic": "Ортографическая",
|
||||
"perspective": "Перспективная"
|
||||
},
|
||||
"clearRecording": "Очистить запись",
|
||||
"edgeThreshold": "Пороговое значение края",
|
||||
"export": "Экспорт",
|
||||
@@ -594,7 +589,6 @@
|
||||
"wireframe": "Каркас"
|
||||
},
|
||||
"model": "Модель",
|
||||
"openIn3DViewer": "Открыть в 3D просмотрщике",
|
||||
"previewOutput": "Предварительный просмотр",
|
||||
"removeBackgroundImage": "Удалить фоновое изображение",
|
||||
"resizeNodeMatchOutput": "Изменить размер узла под вывод",
|
||||
@@ -605,22 +599,8 @@
|
||||
"switchCamera": "Переключить камеру",
|
||||
"switchingMaterialMode": "Переключение режима материала...",
|
||||
"upDirection": "Направление Вверх",
|
||||
"upDirections": {
|
||||
"original": "Оригинал"
|
||||
},
|
||||
"uploadBackgroundImage": "Загрузить фоновое изображение",
|
||||
"uploadTexture": "Загрузить текстуру",
|
||||
"viewer": {
|
||||
"apply": "Применить",
|
||||
"cameraSettings": "Настройки камеры",
|
||||
"cameraType": "Тип камеры",
|
||||
"cancel": "Отмена",
|
||||
"exportSettings": "Настройки экспорта",
|
||||
"lightSettings": "Настройки освещения",
|
||||
"modelSettings": "Настройки модели",
|
||||
"sceneSettings": "Настройки сцены",
|
||||
"title": "3D Просмотрщик (Бета)"
|
||||
}
|
||||
"uploadTexture": "Загрузить текстуру"
|
||||
},
|
||||
"loadWorkflowWarning": {
|
||||
"coreNodesFromVersion": "Требуется ComfyUI {version}:",
|
||||
@@ -667,6 +647,9 @@
|
||||
"installationQueue": "Очередь установки",
|
||||
"lastUpdated": "Последнее обновление",
|
||||
"latestVersion": "Последняя",
|
||||
"legacyManagerUI": "Использовать устаревший UI",
|
||||
"legacyManagerUIDescription": "Чтобы использовать устаревший UI менеджера, запустите ComfyUI с --enable-manager-legacy-ui",
|
||||
"legacyMenuNotAvailable": "Устаревшее меню менеджера недоступно в этой версии ComfyUI. Пожалуйста, используйте новое меню менеджера.",
|
||||
"license": "Лицензия",
|
||||
"loadingVersions": "Загрузка версий...",
|
||||
"nightlyVersion": "Ночная",
|
||||
@@ -767,6 +750,7 @@
|
||||
"Canvas Toggle Link Visibility": "Переключение видимости ссылки на холст",
|
||||
"Canvas Toggle Lock": "Переключение блокировки холста",
|
||||
"Canvas Toggle Minimap": "Показать/скрыть миникарту на холсте",
|
||||
"Check for Custom Node Updates": "Проверить обновления пользовательских узлов",
|
||||
"Check for Updates": "Проверить наличие обновлений",
|
||||
"Clear Pending Tasks": "Очистить ожидающие задачи",
|
||||
"Clear Workflow": "Очистить рабочий процесс",
|
||||
@@ -780,12 +764,13 @@
|
||||
"Contact Support": "Связаться с поддержкой",
|
||||
"Convert Selection to Subgraph": "Преобразовать выделенное в подграф",
|
||||
"Convert selected nodes to group node": "Преобразовать выбранные ноды в групповую ноду",
|
||||
"Custom Nodes (Legacy)": "Пользовательские узлы (устаревшие)",
|
||||
"Custom Nodes Manager": "Менеджер Пользовательских Узлов",
|
||||
"Decrease Brush Size in MaskEditor": "Уменьшить размер кисти в MaskEditor",
|
||||
"Delete Selected Items": "Удалить выбранные элементы",
|
||||
"Desktop User Guide": "Руководство пользователя для настольных ПК",
|
||||
"Duplicate Current Workflow": "Дублировать текущий рабочий процесс",
|
||||
"Edit": "Редактировать",
|
||||
"Exit Subgraph": "Выйти из подграфа",
|
||||
"Export": "Экспортировать",
|
||||
"Export (API)": "Экспорт (API)",
|
||||
"Fit Group To Contents": "Подогнать группу под содержимое",
|
||||
@@ -794,9 +779,12 @@
|
||||
"Group Selected Nodes": "Сгруппировать выбранные ноды",
|
||||
"Help": "Помощь",
|
||||
"Increase Brush Size in MaskEditor": "Увеличить размер кисти в MaskEditor",
|
||||
"Install Missing Custom Nodes": "Установить отсутствующие пользовательские узлы",
|
||||
"Interrupt": "Прервать",
|
||||
"Load Default Workflow": "Загрузить стандартный рабочий процесс",
|
||||
"Manage group nodes": "Управление групповыми нодами",
|
||||
"Manager": "Менеджер",
|
||||
"Manager Menu (Legacy)": "Меню управления (устаревшее)",
|
||||
"Move Selected Nodes Down": "Переместить выбранные узлы вниз",
|
||||
"Move Selected Nodes Left": "Переместить выбранные узлы влево",
|
||||
"Move Selected Nodes Right": "Переместить выбранные узлы вправо",
|
||||
@@ -805,7 +793,6 @@
|
||||
"New": "Новый",
|
||||
"Next Opened Workflow": "Следующий открытый рабочий процесс",
|
||||
"Open": "Открыть",
|
||||
"Open 3D Viewer (Beta) for Selected Node": "Открыть 3D-просмотрщик (бета) для выбранного узла",
|
||||
"Open Custom Nodes Folder": "Открыть папку пользовательских нод",
|
||||
"Open DevTools": "Открыть инструменты разработчика",
|
||||
"Open Inputs Folder": "Открыть папку входных данных",
|
||||
@@ -830,11 +817,9 @@
|
||||
"Restart": "Перезапустить",
|
||||
"Save": "Сохранить",
|
||||
"Save As": "Сохранить как",
|
||||
"Show Keybindings Dialog": "Показать диалог клавиш быстрого доступа",
|
||||
"Show Settings Dialog": "Показать диалог настроек",
|
||||
"Sign Out": "Выйти",
|
||||
"Toggle Bottom Panel": "Переключить нижнюю панель",
|
||||
"Toggle Essential Bottom Panel": "Показать/скрыть основную нижнюю панель",
|
||||
"Toggle Focus Mode": "Переключить режим фокуса",
|
||||
"Toggle Logs Bottom Panel": "Переключение нижней панели журналов",
|
||||
"Toggle Model Library Sidebar": "Показать/скрыть боковую панель библиотеки моделей",
|
||||
@@ -843,24 +828,16 @@
|
||||
"Toggle Search Box": "Переключить поисковую панель",
|
||||
"Toggle Terminal Bottom Panel": "Переключение нижней панели терминала",
|
||||
"Toggle Theme (Dark/Light)": "Переключение темы (Тёмная/Светлая)",
|
||||
"Toggle View Controls Bottom Panel": "Показать/скрыть панель управления просмотром",
|
||||
"Toggle Workflows Sidebar": "Показать/скрыть боковую панель рабочих процессов",
|
||||
"Toggle the Custom Nodes Manager": "Переключить менеджер пользовательских узлов",
|
||||
"Toggle the Custom Nodes Manager Progress Bar": "Переключить индикатор выполнения менеджера пользовательских узлов",
|
||||
"Undo": "Отменить",
|
||||
"Ungroup selected group nodes": "Разгруппировать выбранные групповые ноды",
|
||||
"Unpack the selected Subgraph": "Распаковать выбранный подграф",
|
||||
"Unload Models": "Выгрузить модели",
|
||||
"Unload Models and Execution Cache": "Выгрузить модели и кэш выполнения",
|
||||
"Workflow": "Рабочий процесс",
|
||||
"Zoom In": "Увеличить",
|
||||
"Zoom Out": "Уменьшить"
|
||||
},
|
||||
"minimap": {
|
||||
"nodeColors": "Цвета узлов",
|
||||
"renderBypassState": "Отображать состояние обхода",
|
||||
"renderErrorState": "Отображать состояние ошибки",
|
||||
"showGroups": "Показать фреймы/группы",
|
||||
"showLinks": "Показать связи"
|
||||
},
|
||||
"missingModelsDialog": {
|
||||
"doNotAskAgain": "Больше не показывать это",
|
||||
"missingModels": "Отсутствующие модели",
|
||||
@@ -1127,7 +1104,6 @@
|
||||
},
|
||||
"settingsCategories": {
|
||||
"3D": "3D",
|
||||
"3DViewer": "3D-просмотрщик",
|
||||
"API Nodes": "API-узлы",
|
||||
"About": "О программе",
|
||||
"Appearance": "Внешний вид",
|
||||
@@ -1179,31 +1155,10 @@
|
||||
"Window": "Окно",
|
||||
"Workflow": "Рабочий процесс"
|
||||
},
|
||||
"shortcuts": {
|
||||
"essentials": "Основные",
|
||||
"keyboardShortcuts": "Горячие клавиши",
|
||||
"manageShortcuts": "Управление горячими клавишами",
|
||||
"noKeybinding": "Нет сочетания клавиш",
|
||||
"subcategories": {
|
||||
"node": "Узел",
|
||||
"panelControls": "Управление панелью",
|
||||
"queue": "Очередь",
|
||||
"view": "Просмотр",
|
||||
"workflow": "Рабочий процесс"
|
||||
},
|
||||
"viewControls": "Управление просмотром"
|
||||
},
|
||||
"sideToolbar": {
|
||||
"browseTemplates": "Просмотреть примеры шаблонов",
|
||||
"downloads": "Загрузки",
|
||||
"helpCenter": "Центр поддержки",
|
||||
"labels": {
|
||||
"models": "Модели",
|
||||
"nodes": "Узлы",
|
||||
"queue": "Очередь",
|
||||
"templates": "Шаблоны",
|
||||
"workflows": "Воркфлоу"
|
||||
},
|
||||
"logout": "Выйти",
|
||||
"modelLibrary": "Библиотека моделей",
|
||||
"newBlankWorkflow": "Создайте новый пустой рабочий процесс",
|
||||
@@ -1241,7 +1196,6 @@
|
||||
},
|
||||
"showFlatList": "Показать плоский список"
|
||||
},
|
||||
"templates": "Шаблоны",
|
||||
"workflowTab": {
|
||||
"confirmDelete": "Вы уверены, что хотите удалить этот рабочий процесс?",
|
||||
"confirmDeleteTitle": "Удалить рабочий процесс?",
|
||||
@@ -1288,8 +1242,6 @@
|
||||
"Video": "Видео",
|
||||
"Video API": "Video API"
|
||||
},
|
||||
"loadingMore": "Загрузка дополнительных шаблонов...",
|
||||
"searchPlaceholder": "Поиск шаблонов...",
|
||||
"template": {
|
||||
"3D": {
|
||||
"3d_hunyuan3d_image_to_model": "Hunyuan3D",
|
||||
@@ -1612,7 +1564,6 @@
|
||||
"failedToExportModel": "Не удалось экспортировать модель как {format}",
|
||||
"failedToFetchBalance": "Не удалось получить баланс: {error}",
|
||||
"failedToFetchLogs": "Не удалось получить серверные логи",
|
||||
"failedToInitializeLoad3dViewer": "Не удалось инициализировать 3D просмотрщик",
|
||||
"failedToInitiateCreditPurchase": "Не удалось начать покупку кредитов: {error}",
|
||||
"failedToPurchaseCredits": "Не удалось купить кредиты: {error}",
|
||||
"fileLoadError": "Не удалось найти рабочий процесс в {fileName}",
|
||||
|
||||
@@ -164,8 +164,20 @@
|
||||
"Comfy_LoadDefaultWorkflow": {
|
||||
"label": "載入預設工作流程"
|
||||
},
|
||||
"Comfy_Manager_CustomNodesManager": {
|
||||
"label": "切換自訂節點管理器"
|
||||
"Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": {
|
||||
"label": "自訂節點管理器"
|
||||
},
|
||||
"Comfy_Manager_CustomNodesManager_ShowLegacyCustomNodesMenu": {
|
||||
"label": "自訂節點(舊版)"
|
||||
},
|
||||
"Comfy_Manager_ShowLegacyManagerMenu": {
|
||||
"label": "管理選單(舊版)"
|
||||
},
|
||||
"Comfy_Manager_ShowMissingPacks": {
|
||||
"label": "安裝缺少的自訂節點"
|
||||
},
|
||||
"Comfy_Manager_ShowUpdateAvailablePacks": {
|
||||
"label": "檢查自訂節點更新"
|
||||
},
|
||||
"Comfy_Manager_ToggleManagerProgressDialog": {
|
||||
"label": "切換自訂節點管理器進度條"
|
||||
@@ -179,12 +191,21 @@
|
||||
"Comfy_MaskEditor_OpenMaskEditor": {
|
||||
"label": "為選取的節點開啟 Mask 編輯器"
|
||||
},
|
||||
"Comfy_Memory_UnloadModels": {
|
||||
"label": "卸載模型"
|
||||
},
|
||||
"Comfy_Memory_UnloadModelsAndExecutionCache": {
|
||||
"label": "卸載模型與執行快取"
|
||||
},
|
||||
"Comfy_NewBlankWorkflow": {
|
||||
"label": "新增空白工作流程"
|
||||
},
|
||||
"Comfy_OpenClipspace": {
|
||||
"label": "Clipspace"
|
||||
},
|
||||
"Comfy_OpenManagerDialog": {
|
||||
"label": "管理器"
|
||||
},
|
||||
"Comfy_OpenWorkflow": {
|
||||
"label": "開啟工作流程"
|
||||
},
|
||||
@@ -212,6 +233,12 @@
|
||||
"Comfy_ShowSettingsDialog": {
|
||||
"label": "顯示設定對話框"
|
||||
},
|
||||
"Comfy_ToggleCanvasInfo": {
|
||||
"label": "畫布效能"
|
||||
},
|
||||
"Comfy_ToggleHelpCenter": {
|
||||
"label": "說明中心"
|
||||
},
|
||||
"Comfy_ToggleTheme": {
|
||||
"label": "切換主題(深色/淺色)"
|
||||
},
|
||||
|
||||
@@ -272,11 +272,11 @@
|
||||
"category": "分類",
|
||||
"choose_file_to_upload": "選擇要上傳的檔案",
|
||||
"clear": "清除",
|
||||
"clearFilters": "清除篩選",
|
||||
"close": "關閉",
|
||||
"color": "顏色",
|
||||
"comingSoon": "即將推出",
|
||||
"command": "指令",
|
||||
"commandProhibited": "指令 {command} 已被禁止。如需更多資訊,請聯絡管理員。",
|
||||
"community": "社群",
|
||||
"completed": "已完成",
|
||||
"confirm": "確認",
|
||||
@@ -299,7 +299,6 @@
|
||||
"disabling": "停用中",
|
||||
"dismiss": "關閉",
|
||||
"download": "下載",
|
||||
"duplicate": "複製",
|
||||
"edit": "編輯",
|
||||
"empty": "空",
|
||||
"enableAll": "全部啟用",
|
||||
@@ -570,10 +569,6 @@
|
||||
"applyingTexture": "正在套用材質貼圖...",
|
||||
"backgroundColor": "背景顏色",
|
||||
"camera": "相機",
|
||||
"cameraType": {
|
||||
"orthographic": "正交",
|
||||
"perspective": "透視"
|
||||
},
|
||||
"clearRecording": "清除錄影",
|
||||
"edgeThreshold": "邊緣閾值",
|
||||
"export": "匯出",
|
||||
@@ -594,7 +589,6 @@
|
||||
"wireframe": "線框"
|
||||
},
|
||||
"model": "模型",
|
||||
"openIn3DViewer": "在 3D 檢視器中開啟",
|
||||
"previewOutput": "預覽輸出",
|
||||
"removeBackgroundImage": "移除背景圖片",
|
||||
"resizeNodeMatchOutput": "調整節點以符合輸出",
|
||||
@@ -605,22 +599,8 @@
|
||||
"switchCamera": "切換相機",
|
||||
"switchingMaterialMode": "正在切換材質模式...",
|
||||
"upDirection": "上方方向",
|
||||
"upDirections": {
|
||||
"original": "原始"
|
||||
},
|
||||
"uploadBackgroundImage": "上傳背景圖片",
|
||||
"uploadTexture": "上傳材質貼圖",
|
||||
"viewer": {
|
||||
"apply": "套用",
|
||||
"cameraSettings": "相機設定",
|
||||
"cameraType": "相機類型",
|
||||
"cancel": "取消",
|
||||
"exportSettings": "匯出設定",
|
||||
"lightSettings": "燈光設定",
|
||||
"modelSettings": "模型設定",
|
||||
"sceneSettings": "場景設定",
|
||||
"title": "3D 檢視器(測試版)"
|
||||
}
|
||||
"uploadTexture": "上傳材質貼圖"
|
||||
},
|
||||
"loadWorkflowWarning": {
|
||||
"coreNodesFromVersion": "需要 ComfyUI {version}:",
|
||||
@@ -667,6 +647,9 @@
|
||||
"installationQueue": "安裝佇列",
|
||||
"lastUpdated": "最後更新",
|
||||
"latestVersion": "最新版本",
|
||||
"legacyManagerUI": "使用舊版介面",
|
||||
"legacyManagerUIDescription": "若要使用舊版管理介面,請以 --enable-manager-legacy-ui 啟動 ComfyUI",
|
||||
"legacyMenuNotAvailable": "舊版管理選單不可用,已預設切換至新版管理選單。",
|
||||
"license": "授權條款",
|
||||
"loadingVersions": "正在載入版本...",
|
||||
"nightlyVersion": "每夜建置版",
|
||||
@@ -767,6 +750,7 @@
|
||||
"Canvas Toggle Link Visibility": "切換連結可見性",
|
||||
"Canvas Toggle Lock": "切換畫布鎖定",
|
||||
"Canvas Toggle Minimap": "畫布切換小地圖",
|
||||
"Check for Custom Node Updates": "檢查自訂節點更新",
|
||||
"Check for Updates": "檢查更新",
|
||||
"Clear Pending Tasks": "清除待處理任務",
|
||||
"Clear Workflow": "清除工作流程",
|
||||
@@ -780,12 +764,13 @@
|
||||
"Contact Support": "聯絡支援",
|
||||
"Convert Selection to Subgraph": "將選取內容轉為子圖",
|
||||
"Convert selected nodes to group node": "將選取節點轉為群組節點",
|
||||
"Custom Nodes (Legacy)": "自訂節點(舊版)",
|
||||
"Custom Nodes Manager": "自訂節點管理員",
|
||||
"Decrease Brush Size in MaskEditor": "在 MaskEditor 中減小筆刷大小",
|
||||
"Delete Selected Items": "刪除選取項目",
|
||||
"Desktop User Guide": "桌面應用程式使用指南",
|
||||
"Duplicate Current Workflow": "複製目前工作流程",
|
||||
"Edit": "編輯",
|
||||
"Exit Subgraph": "離開子圖",
|
||||
"Export": "匯出",
|
||||
"Export (API)": "匯出(API)",
|
||||
"Fit Group To Contents": "群組貼合內容",
|
||||
@@ -794,9 +779,12 @@
|
||||
"Group Selected Nodes": "群組選取節點",
|
||||
"Help": "說明",
|
||||
"Increase Brush Size in MaskEditor": "在 MaskEditor 中增大筆刷大小",
|
||||
"Install Missing Custom Nodes": "安裝缺少的自訂節點",
|
||||
"Interrupt": "中斷",
|
||||
"Load Default Workflow": "載入預設工作流程",
|
||||
"Manage group nodes": "管理群組節點",
|
||||
"Manager": "管理員",
|
||||
"Manager Menu (Legacy)": "管理員選單(舊版)",
|
||||
"Move Selected Nodes Down": "選取節點下移",
|
||||
"Move Selected Nodes Left": "選取節點左移",
|
||||
"Move Selected Nodes Right": "選取節點右移",
|
||||
@@ -805,7 +793,6 @@
|
||||
"New": "新增",
|
||||
"Next Opened Workflow": "下一個已開啟的工作流程",
|
||||
"Open": "開啟",
|
||||
"Open 3D Viewer (Beta) for Selected Node": "為選取的節點開啟 3D 檢視器(Beta 版)",
|
||||
"Open Custom Nodes Folder": "開啟自訂節點資料夾",
|
||||
"Open DevTools": "開啟開發者工具",
|
||||
"Open Inputs Folder": "開啟輸入資料夾",
|
||||
@@ -830,11 +817,9 @@
|
||||
"Restart": "重新啟動",
|
||||
"Save": "儲存",
|
||||
"Save As": "另存新檔",
|
||||
"Show Keybindings Dialog": "顯示快捷鍵對話框",
|
||||
"Show Settings Dialog": "顯示設定對話框",
|
||||
"Sign Out": "登出",
|
||||
"Toggle Bottom Panel": "切換下方面板",
|
||||
"Toggle Essential Bottom Panel": "切換基本下方面板",
|
||||
"Toggle Focus Mode": "切換專注模式",
|
||||
"Toggle Logs Bottom Panel": "切換日誌下方面板",
|
||||
"Toggle Model Library Sidebar": "切換模型庫側邊欄",
|
||||
@@ -843,24 +828,16 @@
|
||||
"Toggle Search Box": "切換搜尋框",
|
||||
"Toggle Terminal Bottom Panel": "切換終端機底部面板",
|
||||
"Toggle Theme (Dark/Light)": "切換主題(深色/淺色)",
|
||||
"Toggle View Controls Bottom Panel": "切換檢視控制下方面板",
|
||||
"Toggle Workflows Sidebar": "切換工作流程側邊欄",
|
||||
"Toggle the Custom Nodes Manager": "切換自訂節點管理器",
|
||||
"Toggle the Custom Nodes Manager Progress Bar": "切換自訂節點管理器進度條",
|
||||
"Undo": "復原",
|
||||
"Ungroup selected group nodes": "取消群組選取的群組節點",
|
||||
"Unpack the selected Subgraph": "解包所選子圖",
|
||||
"Unload Models": "卸載模型",
|
||||
"Unload Models and Execution Cache": "卸載模型與執行快取",
|
||||
"Workflow": "工作流程",
|
||||
"Zoom In": "放大",
|
||||
"Zoom Out": "縮小"
|
||||
},
|
||||
"minimap": {
|
||||
"nodeColors": "節點顏色",
|
||||
"renderBypassState": "顯示繞過狀態",
|
||||
"renderErrorState": "顯示錯誤狀態",
|
||||
"showGroups": "顯示框架/群組",
|
||||
"showLinks": "顯示連結"
|
||||
},
|
||||
"missingModelsDialog": {
|
||||
"doNotAskAgain": "不要再顯示此訊息",
|
||||
"missingModels": "缺少模型",
|
||||
@@ -1127,7 +1104,6 @@
|
||||
},
|
||||
"settingsCategories": {
|
||||
"3D": "3D",
|
||||
"3DViewer": "3D 檢視器",
|
||||
"API Nodes": "API 節點",
|
||||
"About": "關於",
|
||||
"Appearance": "外觀",
|
||||
@@ -1179,31 +1155,10 @@
|
||||
"Window": "視窗",
|
||||
"Workflow": "工作流程"
|
||||
},
|
||||
"shortcuts": {
|
||||
"essentials": "基本",
|
||||
"keyboardShortcuts": "鍵盤快捷鍵",
|
||||
"manageShortcuts": "管理快捷鍵",
|
||||
"noKeybinding": "無快捷鍵",
|
||||
"subcategories": {
|
||||
"node": "節點",
|
||||
"panelControls": "面板控制",
|
||||
"queue": "佇列",
|
||||
"view": "檢視",
|
||||
"workflow": "工作流程"
|
||||
},
|
||||
"viewControls": "檢視控制"
|
||||
},
|
||||
"sideToolbar": {
|
||||
"browseTemplates": "瀏覽範例模板",
|
||||
"downloads": "下載",
|
||||
"helpCenter": "說明中心",
|
||||
"labels": {
|
||||
"models": "模型",
|
||||
"nodes": "節點",
|
||||
"queue": "佇列",
|
||||
"templates": "範本",
|
||||
"workflows": "工作流程"
|
||||
},
|
||||
"logout": "登出",
|
||||
"modelLibrary": "模型庫",
|
||||
"newBlankWorkflow": "建立新的空白工作流程",
|
||||
@@ -1241,7 +1196,6 @@
|
||||
},
|
||||
"showFlatList": "顯示平面清單"
|
||||
},
|
||||
"templates": "範本",
|
||||
"workflowTab": {
|
||||
"confirmDelete": "您確定要刪除這個工作流程嗎?",
|
||||
"confirmDeleteTitle": "刪除工作流程?",
|
||||
@@ -1288,8 +1242,6 @@
|
||||
"Video": "影片",
|
||||
"Video API": "影片 API"
|
||||
},
|
||||
"loadingMore": "正在載入更多範本...",
|
||||
"searchPlaceholder": "搜尋範本...",
|
||||
"template": {
|
||||
"3D": {
|
||||
"3d_hunyuan3d_image_to_model": "Hunyuan3D 2.0",
|
||||
@@ -1612,7 +1564,6 @@
|
||||
"failedToExportModel": "無法將模型匯出為 {format}",
|
||||
"failedToFetchBalance": "取得餘額失敗:{error}",
|
||||
"failedToFetchLogs": "無法取得伺服器日誌",
|
||||
"failedToInitializeLoad3dViewer": "初始化 3D 檢視器失敗",
|
||||
"failedToInitiateCreditPurchase": "啟動點數購買失敗:{error}",
|
||||
"failedToPurchaseCredits": "購買點數失敗:{error}",
|
||||
"fileLoadError": "無法在 {fileName} 中找到工作流程",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"label": "重启"
|
||||
},
|
||||
"Comfy_3DViewer_Open3DViewer": {
|
||||
"label": "為所選節點開啟 3D 檢視器(Beta 版)"
|
||||
"label": "为所选节点开启 3D 浏览器(Beta 版)"
|
||||
},
|
||||
"Comfy_BrowseTemplates": {
|
||||
"label": "浏览模板"
|
||||
@@ -75,7 +75,7 @@
|
||||
"label": "锁定视图"
|
||||
},
|
||||
"Comfy_Canvas_ToggleMinimap": {
|
||||
"label": "畫布切換小地圖"
|
||||
"label": "画布切换小地图"
|
||||
},
|
||||
"Comfy_Canvas_ToggleSelectedNodes_Bypass": {
|
||||
"label": "忽略/取消忽略选中节点"
|
||||
@@ -123,7 +123,7 @@
|
||||
"label": "将选区转换为子图"
|
||||
},
|
||||
"Comfy_Graph_ExitSubgraph": {
|
||||
"label": "退出子圖"
|
||||
"label": "退出子图"
|
||||
},
|
||||
"Comfy_Graph_FitGroupToContents": {
|
||||
"label": "适应节点框到内容"
|
||||
@@ -132,7 +132,7 @@
|
||||
"label": "添加框到选中节点"
|
||||
},
|
||||
"Comfy_Graph_UnpackSubgraph": {
|
||||
"label": "解開所選子圖"
|
||||
"label": "解开所选子图"
|
||||
},
|
||||
"Comfy_GroupNode_ConvertSelectedNodesToGroupNode": {
|
||||
"label": "将选中节点转换为组节点"
|
||||
@@ -164,27 +164,48 @@
|
||||
"Comfy_LoadDefaultWorkflow": {
|
||||
"label": "加载默认工作流"
|
||||
},
|
||||
"Comfy_Manager_CustomNodesManager": {
|
||||
"label": "自定义节点管理器"
|
||||
"Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": {
|
||||
"label": "自定义节点(测试版)"
|
||||
},
|
||||
"Comfy_Manager_CustomNodesManager_ShowLegacyCustomNodesMenu": {
|
||||
"label": "自訂節點(舊版)"
|
||||
},
|
||||
"Comfy_Manager_ShowLegacyManagerMenu": {
|
||||
"label": "管理員選單(舊版)"
|
||||
},
|
||||
"Comfy_Manager_ShowMissingPacks": {
|
||||
"label": "安装缺失的包"
|
||||
},
|
||||
"Comfy_Manager_ShowUpdateAvailablePacks": {
|
||||
"label": "检查更新"
|
||||
},
|
||||
"Comfy_Manager_ToggleManagerProgressDialog": {
|
||||
"label": "切换进度对话框"
|
||||
},
|
||||
"Comfy_MaskEditor_BrushSize_Decrease": {
|
||||
"label": "減小 MaskEditor 中的筆刷大小"
|
||||
"label": "减小 MaskEditor 中的笔刷大小"
|
||||
},
|
||||
"Comfy_MaskEditor_BrushSize_Increase": {
|
||||
"label": "增加 MaskEditor 畫筆大小"
|
||||
"label": "增加 MaskEditor 画笔大小"
|
||||
},
|
||||
"Comfy_MaskEditor_OpenMaskEditor": {
|
||||
"label": "打开选中节点的遮罩编辑器"
|
||||
},
|
||||
"Comfy_Memory_UnloadModels": {
|
||||
"label": "卸载模型"
|
||||
},
|
||||
"Comfy_Memory_UnloadModelsAndExecutionCache": {
|
||||
"label": "卸载模型和执行缓存"
|
||||
},
|
||||
"Comfy_NewBlankWorkflow": {
|
||||
"label": "新建空白工作流"
|
||||
},
|
||||
"Comfy_OpenClipspace": {
|
||||
"label": "打开剪贴板"
|
||||
},
|
||||
"Comfy_OpenManagerDialog": {
|
||||
"label": "管理器"
|
||||
},
|
||||
"Comfy_OpenWorkflow": {
|
||||
"label": "打开工作流"
|
||||
},
|
||||
@@ -212,6 +233,12 @@
|
||||
"Comfy_ShowSettingsDialog": {
|
||||
"label": "显示设置对话框"
|
||||
},
|
||||
"Comfy_ToggleCanvasInfo": {
|
||||
"label": "画布性能"
|
||||
},
|
||||
"Comfy_ToggleHelpCenter": {
|
||||
"label": "说明中心"
|
||||
},
|
||||
"Comfy_ToggleTheme": {
|
||||
"label": "切换主题"
|
||||
},
|
||||
@@ -246,13 +273,13 @@
|
||||
"label": "切换日志底部面板"
|
||||
},
|
||||
"Workspace_ToggleBottomPanelTab_shortcuts-essentials": {
|
||||
"label": "切換基本下方面板"
|
||||
"label": "切换基础底部面板"
|
||||
},
|
||||
"Workspace_ToggleBottomPanelTab_shortcuts-view-controls": {
|
||||
"label": "切換檢視控制底部面板"
|
||||
"label": "切换视图控制底部面板"
|
||||
},
|
||||
"Workspace_ToggleBottomPanel_Shortcuts": {
|
||||
"label": "顯示快捷鍵對話框"
|
||||
"label": "显示快捷键对话框"
|
||||
},
|
||||
"Workspace_ToggleFocusMode": {
|
||||
"label": "切换焦点模式"
|
||||
|
||||
@@ -272,11 +272,11 @@
|
||||
"category": "类别",
|
||||
"choose_file_to_upload": "选择要上传的文件",
|
||||
"clear": "清除",
|
||||
"clearFilters": "清除篩選",
|
||||
"close": "关闭",
|
||||
"color": "颜色",
|
||||
"comingSoon": "即将推出",
|
||||
"command": "指令",
|
||||
"commandProhibited": "命令 {command} 被禁止。请联系管理员获取更多信息。",
|
||||
"community": "社区",
|
||||
"completed": "已完成",
|
||||
"confirm": "确认",
|
||||
@@ -299,7 +299,6 @@
|
||||
"disabling": "禁用中",
|
||||
"dismiss": "關閉",
|
||||
"download": "下载",
|
||||
"duplicate": "复制",
|
||||
"edit": "编辑",
|
||||
"empty": "空",
|
||||
"enableAll": "启用全部",
|
||||
@@ -570,10 +569,6 @@
|
||||
"applyingTexture": "应用纹理中...",
|
||||
"backgroundColor": "背景颜色",
|
||||
"camera": "摄影机",
|
||||
"cameraType": {
|
||||
"orthographic": "正交",
|
||||
"perspective": "透视"
|
||||
},
|
||||
"clearRecording": "清除录制",
|
||||
"edgeThreshold": "边缘阈值",
|
||||
"export": "导出",
|
||||
@@ -594,7 +589,6 @@
|
||||
"wireframe": "线框"
|
||||
},
|
||||
"model": "模型",
|
||||
"openIn3DViewer": "在 3D 檢視器中開啟",
|
||||
"previewOutput": "预览输出",
|
||||
"removeBackgroundImage": "移除背景图片",
|
||||
"resizeNodeMatchOutput": "调整节点以匹配输出",
|
||||
@@ -605,22 +599,8 @@
|
||||
"switchCamera": "切换摄影机类型",
|
||||
"switchingMaterialMode": "切换材质模式中...",
|
||||
"upDirection": "上方向",
|
||||
"upDirections": {
|
||||
"original": "原始"
|
||||
},
|
||||
"uploadBackgroundImage": "上传背景图片",
|
||||
"uploadTexture": "上传纹理",
|
||||
"viewer": {
|
||||
"apply": "套用",
|
||||
"cameraSettings": "相機設定",
|
||||
"cameraType": "相機類型",
|
||||
"cancel": "取消",
|
||||
"exportSettings": "匯出設定",
|
||||
"lightSettings": "燈光設定",
|
||||
"modelSettings": "模型設定",
|
||||
"sceneSettings": "場景設定",
|
||||
"title": "3D 檢視器(測試版)"
|
||||
}
|
||||
"uploadTexture": "上传纹理"
|
||||
},
|
||||
"loadWorkflowWarning": {
|
||||
"coreNodesFromVersion": "需要 ComfyUI {version}:",
|
||||
@@ -667,6 +647,9 @@
|
||||
"installationQueue": "安装队列",
|
||||
"lastUpdated": "最后更新",
|
||||
"latestVersion": "最新",
|
||||
"legacyManagerUI": "使用旧版UI",
|
||||
"legacyManagerUIDescription": "要使用旧版的管理器UI,请启动ComfyUI并使用 --enable-manager-legacy-ui",
|
||||
"legacyMenuNotAvailable": "在此版本的ComfyUI中,不提供旧版的管理器菜单。请使用新的管理器菜单。",
|
||||
"license": "许可证",
|
||||
"loadingVersions": "正在加载版本...",
|
||||
"nightlyVersion": "每夜",
|
||||
@@ -767,6 +750,7 @@
|
||||
"Canvas Toggle Link Visibility": "切换连线可见性",
|
||||
"Canvas Toggle Lock": "切换视图锁定",
|
||||
"Canvas Toggle Minimap": "畫布切換小地圖",
|
||||
"Check for Custom Node Updates": "檢查自訂節點更新",
|
||||
"Check for Updates": "检查更新",
|
||||
"Clear Pending Tasks": "清除待处理任务",
|
||||
"Clear Workflow": "清除工作流",
|
||||
@@ -780,12 +764,13 @@
|
||||
"Contact Support": "联系支持",
|
||||
"Convert Selection to Subgraph": "将选中内容转换为子图",
|
||||
"Convert selected nodes to group node": "将选中节点转换为组节点",
|
||||
"Custom Nodes (Legacy)": "自訂節點(舊版)",
|
||||
"Custom Nodes Manager": "自定义节点管理器",
|
||||
"Decrease Brush Size in MaskEditor": "在 MaskEditor 中減小筆刷大小",
|
||||
"Delete Selected Items": "删除选定的项目",
|
||||
"Desktop User Guide": "桌面端用户指南",
|
||||
"Duplicate Current Workflow": "复制当前工作流",
|
||||
"Edit": "编辑",
|
||||
"Exit Subgraph": "退出子圖",
|
||||
"Export": "导出",
|
||||
"Export (API)": "导出 (API)",
|
||||
"Fit Group To Contents": "适应组内容",
|
||||
@@ -794,9 +779,12 @@
|
||||
"Group Selected Nodes": "将选中节点转换为组节点",
|
||||
"Help": "帮助",
|
||||
"Increase Brush Size in MaskEditor": "在 MaskEditor 中增大筆刷大小",
|
||||
"Install Missing Custom Nodes": "安裝缺少的自訂節點",
|
||||
"Interrupt": "中断",
|
||||
"Load Default Workflow": "加载默认工作流",
|
||||
"Manage group nodes": "管理组节点",
|
||||
"Manager": "管理器",
|
||||
"Manager Menu (Legacy)": "管理選單(舊版)",
|
||||
"Move Selected Nodes Down": "下移所选节点",
|
||||
"Move Selected Nodes Left": "左移所选节点",
|
||||
"Move Selected Nodes Right": "右移所选节点",
|
||||
@@ -805,7 +793,6 @@
|
||||
"New": "新建",
|
||||
"Next Opened Workflow": "下一个打开的工作流",
|
||||
"Open": "打开",
|
||||
"Open 3D Viewer (Beta) for Selected Node": "為所選節點開啟 3D 檢視器(Beta 版)",
|
||||
"Open Custom Nodes Folder": "打开自定义节点文件夹",
|
||||
"Open DevTools": "打开开发者工具",
|
||||
"Open Inputs Folder": "打开输入文件夹",
|
||||
@@ -830,11 +817,9 @@
|
||||
"Restart": "重启",
|
||||
"Save": "保存",
|
||||
"Save As": "另存为",
|
||||
"Show Keybindings Dialog": "顯示快捷鍵對話框",
|
||||
"Show Settings Dialog": "显示设置对话框",
|
||||
"Sign Out": "退出登录",
|
||||
"Toggle Bottom Panel": "切换底部面板",
|
||||
"Toggle Essential Bottom Panel": "切換基本下方面板",
|
||||
"Toggle Focus Mode": "切换专注模式",
|
||||
"Toggle Logs Bottom Panel": "切换日志底部面板",
|
||||
"Toggle Model Library Sidebar": "切換模型庫側邊欄",
|
||||
@@ -843,24 +828,16 @@
|
||||
"Toggle Search Box": "切换搜索框",
|
||||
"Toggle Terminal Bottom Panel": "切换终端底部面板",
|
||||
"Toggle Theme (Dark/Light)": "切换主题(暗/亮)",
|
||||
"Toggle View Controls Bottom Panel": "切換檢視控制下方面板",
|
||||
"Toggle Workflows Sidebar": "切換工作流程側邊欄",
|
||||
"Toggle the Custom Nodes Manager": "切换自定义节点管理器",
|
||||
"Toggle the Custom Nodes Manager Progress Bar": "切换自定义节点管理器进度条",
|
||||
"Undo": "撤销",
|
||||
"Ungroup selected group nodes": "解散选中组节点",
|
||||
"Unpack the selected Subgraph": "解開所選子圖",
|
||||
"Unload Models": "卸载模型",
|
||||
"Unload Models and Execution Cache": "卸载模型和执行缓存",
|
||||
"Workflow": "工作流",
|
||||
"Zoom In": "放大画面",
|
||||
"Zoom Out": "缩小画面"
|
||||
},
|
||||
"minimap": {
|
||||
"nodeColors": "節點顏色",
|
||||
"renderBypassState": "顯示繞過狀態",
|
||||
"renderErrorState": "顯示錯誤狀態",
|
||||
"showGroups": "顯示框架/群組",
|
||||
"showLinks": "顯示連結"
|
||||
},
|
||||
"missingModelsDialog": {
|
||||
"doNotAskAgain": "不再显示此消息",
|
||||
"missingModels": "缺少模型",
|
||||
@@ -1127,7 +1104,6 @@
|
||||
},
|
||||
"settingsCategories": {
|
||||
"3D": "3D",
|
||||
"3DViewer": "3D 檢視器",
|
||||
"API Nodes": "API 节点",
|
||||
"About": "关于",
|
||||
"Appearance": "外观",
|
||||
@@ -1179,31 +1155,10 @@
|
||||
"Window": "窗口",
|
||||
"Workflow": "工作流"
|
||||
},
|
||||
"shortcuts": {
|
||||
"essentials": "基本功能",
|
||||
"keyboardShortcuts": "鍵盤快捷鍵",
|
||||
"manageShortcuts": "管理快捷鍵",
|
||||
"noKeybinding": "無快捷鍵",
|
||||
"subcategories": {
|
||||
"node": "節點",
|
||||
"panelControls": "面板控制",
|
||||
"queue": "佇列",
|
||||
"view": "檢視",
|
||||
"workflow": "工作流程"
|
||||
},
|
||||
"viewControls": "檢視控制"
|
||||
},
|
||||
"sideToolbar": {
|
||||
"browseTemplates": "浏览示例模板",
|
||||
"downloads": "下载",
|
||||
"helpCenter": "帮助中心",
|
||||
"labels": {
|
||||
"models": "模型",
|
||||
"nodes": "節點",
|
||||
"queue": "佇列",
|
||||
"templates": "範本",
|
||||
"workflows": "工作流程"
|
||||
},
|
||||
"logout": "登出",
|
||||
"modelLibrary": "模型库",
|
||||
"newBlankWorkflow": "创建空白工作流",
|
||||
@@ -1241,7 +1196,6 @@
|
||||
},
|
||||
"showFlatList": "平铺结果"
|
||||
},
|
||||
"templates": "範本",
|
||||
"workflowTab": {
|
||||
"confirmDelete": "您确定要删除此工作流吗?",
|
||||
"confirmDeleteTitle": "删除工作流?",
|
||||
@@ -1288,8 +1242,6 @@
|
||||
"Video": "视频生成",
|
||||
"Video API": "视频 API"
|
||||
},
|
||||
"loadingMore": "正在載入更多範本...",
|
||||
"searchPlaceholder": "搜尋範本...",
|
||||
"template": {
|
||||
"3D": {
|
||||
"3d_hunyuan3d_image_to_model": "混元3D 2.0 图生模型",
|
||||
@@ -1612,7 +1564,6 @@
|
||||
"failedToExportModel": "无法将模型导出为 {format}",
|
||||
"failedToFetchBalance": "获取余额失败:{error}",
|
||||
"failedToFetchLogs": "无法获取服务器日志",
|
||||
"failedToInitializeLoad3dViewer": "初始化 3D 檢視器失敗",
|
||||
"failedToInitiateCreditPurchase": "发起积分购买失败:{error}",
|
||||
"failedToPurchaseCredits": "购买积分失败:{error}",
|
||||
"fileLoadError": "无法在 {fileName} 中找到工作流",
|
||||
|
||||
@@ -30,10 +30,10 @@
|
||||
"tooltip": "画布背景的图像 URL。你可以在输出面板中右键点击一张图片,并选择“设为背景”来使用它。"
|
||||
},
|
||||
"Comfy_Canvas_NavigationMode": {
|
||||
"name": "畫布導航模式",
|
||||
"name": "画布导航模式",
|
||||
"options": {
|
||||
"Left-Click Pan (Legacy)": "左鍵拖曳(舊版)",
|
||||
"Standard (New)": "標準(新)"
|
||||
"Left-Click Pan (Legacy)": "左键拖曳(旧版)",
|
||||
"Standard (New)": "标准(新)"
|
||||
}
|
||||
},
|
||||
"Comfy_Canvas_SelectionToolbox": {
|
||||
@@ -120,8 +120,8 @@
|
||||
}
|
||||
},
|
||||
"Comfy_Load3D_3DViewerEnable": {
|
||||
"name": "啟用 3D 檢視器(測試版)",
|
||||
"tooltip": "為所選節點啟用 3D 檢視器(測試版)。此功能可讓您直接在全尺寸 3D 檢視器中瀏覽並互動 3D 模型。"
|
||||
"name": "启用3D查看器(测试版)",
|
||||
"tooltip": "为选定节点启用3D查看器(测试版)。此功能允许你在全尺寸3D查看器中直接可视化和交互3D模型。"
|
||||
},
|
||||
"Comfy_Load3D_BackgroundColor": {
|
||||
"name": "初始背景颜色",
|
||||
@@ -338,7 +338,7 @@
|
||||
"Disabled": "禁用",
|
||||
"Top": "顶部"
|
||||
},
|
||||
"tooltip": "選單列位置。在行動裝置上,選單始終顯示於頂端。"
|
||||
"tooltip": "选单列位置。在行动装置上,选单始终显示于顶端。"
|
||||
},
|
||||
"Comfy_Validation_Workflows": {
|
||||
"name": "校验工作流"
|
||||
|
||||
@@ -495,6 +495,7 @@ const zSettings = z.object({
|
||||
'Comfy.Load3D.LightAdjustmentIncrement': z.number(),
|
||||
'Comfy.Load3D.CameraType': z.enum(['perspective', 'orthographic']),
|
||||
'Comfy.Load3D.3DViewerEnable': z.boolean(),
|
||||
'Comfy.Memory.AllowManualUnload': z.boolean(),
|
||||
'pysssss.SnapToGrid': z.boolean(),
|
||||
/** VHS setting is used for queue video preview support. */
|
||||
'VHS.AdvancedPreviews': z.string(),
|
||||
|
||||
@@ -35,6 +35,7 @@ import type {
|
||||
NodeId
|
||||
} from '@/schemas/comfyWorkflowSchema'
|
||||
import type { ComfyNodeDef } from '@/schemas/nodeDefSchema'
|
||||
import { useToastStore } from '@/stores/toastStore'
|
||||
import type { NodeExecutionId } from '@/types/nodeIdentification'
|
||||
import { WorkflowTemplates } from '@/types/workflowTemplateTypes'
|
||||
|
||||
@@ -1020,6 +1021,56 @@ export class ComfyApi extends EventTarget {
|
||||
return (await axios.get(this.internalURL('/folder_paths'))).data
|
||||
}
|
||||
|
||||
/* Frees memory by unloading models and optionally freeing execution cache
|
||||
* @param {Object} options - The options object
|
||||
* @param {boolean} options.freeExecutionCache - If true, also frees execution cache
|
||||
*/
|
||||
async freeMemory(options: { freeExecutionCache: boolean }) {
|
||||
try {
|
||||
let mode = ''
|
||||
if (options.freeExecutionCache) {
|
||||
mode = '{"unload_models": true, "free_memory": true}'
|
||||
} else {
|
||||
mode = '{"unload_models": true}'
|
||||
}
|
||||
|
||||
const res = await this.fetchApi(`/free`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: mode
|
||||
})
|
||||
|
||||
if (res.status === 200) {
|
||||
if (options.freeExecutionCache) {
|
||||
useToastStore().add({
|
||||
severity: 'success',
|
||||
summary: 'Models and Execution Cache have been cleared.',
|
||||
life: 3000
|
||||
})
|
||||
} else {
|
||||
useToastStore().add({
|
||||
severity: 'success',
|
||||
summary: 'Models have been unloaded.',
|
||||
life: 3000
|
||||
})
|
||||
}
|
||||
} else {
|
||||
useToastStore().add({
|
||||
severity: 'error',
|
||||
summary:
|
||||
'Unloading of models failed. Installed ComfyUI may be an outdated version.',
|
||||
life: 5000
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
useToastStore().add({
|
||||
severity: 'error',
|
||||
summary: 'An error occurred while trying to unload models.',
|
||||
life: 5000
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the custom nodes i18n data from the server.
|
||||
*
|
||||
@@ -1031,7 +1082,7 @@ export class ComfyApi extends EventTarget {
|
||||
|
||||
/**
|
||||
* Checks if the server supports a specific feature.
|
||||
* @param featureName The name of the feature to check
|
||||
* @param featureName The name of the feature to check (supports dot notation for nested values)
|
||||
* @returns true if the feature is supported, false otherwise
|
||||
*/
|
||||
serverSupportsFeature(featureName: string): boolean {
|
||||
@@ -1040,7 +1091,7 @@ export class ComfyApi extends EventTarget {
|
||||
|
||||
/**
|
||||
* Gets a server feature flag value.
|
||||
* @param featureName The name of the feature to get
|
||||
* @param featureName The name of the feature to get (supports dot notation for nested values)
|
||||
* @param defaultValue The default value if the feature is not found
|
||||
* @returns The feature value or default
|
||||
*/
|
||||
|
||||
@@ -27,16 +27,16 @@ enum ManagerRoute {
|
||||
UPDATE_ALL = 'manager/queue/update_all',
|
||||
UNINSTALL = 'manager/queue/uninstall',
|
||||
DISABLE = 'manager/queue/disable',
|
||||
// FIX_NODE is currently unused but kept for potential future implementation
|
||||
FIX_NODE = 'manager/queue/fix',
|
||||
LIST_INSTALLED = 'customnode/installed',
|
||||
GET_NODES = 'customnode/getmappings',
|
||||
GET_PACKS = 'customnode/getlist',
|
||||
IMPORT_FAIL_INFO = 'customnode/import_fail_info',
|
||||
REBOOT = 'manager/reboot'
|
||||
REBOOT = 'manager/reboot',
|
||||
IS_LEGACY_MANAGER_UI = 'manager/is_legacy_manager_ui'
|
||||
}
|
||||
|
||||
const managerApiClient = axios.create({
|
||||
baseURL: api.apiURL(''),
|
||||
baseURL: api.apiURL('/v2/'),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
@@ -247,6 +247,15 @@ export const useComfyManagerService = () => {
|
||||
)
|
||||
}
|
||||
|
||||
const isLegacyManagerUI = async (signal?: AbortSignal) => {
|
||||
const errorContext = 'Checking if user set Manager to use the legacy UI'
|
||||
|
||||
return executeRequest<{ is_legacy_manager_ui: boolean }>(
|
||||
() => managerApiClient.get(ManagerRoute.IS_LEGACY_MANAGER_UI, { signal }),
|
||||
{ errorContext }
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
// State
|
||||
isLoading,
|
||||
@@ -268,6 +277,7 @@ export const useComfyManagerService = () => {
|
||||
updateAllPacks,
|
||||
|
||||
// System operations
|
||||
rebootComfyUI
|
||||
rebootComfyUI,
|
||||
isLegacyManagerUI
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import _ from 'es-toolkit/compat'
|
||||
|
||||
import { useSelectedLiteGraphItems } from '@/composables/canvas/useSelectedLiteGraphItems'
|
||||
import { useNodeAnimatedImage } from '@/composables/node/useNodeAnimatedImage'
|
||||
import { useNodeCanvasImagePreview } from '@/composables/node/useNodeCanvasImagePreview'
|
||||
import { useNodeImage, useNodeVideo } from '@/composables/node/useNodeImage'
|
||||
@@ -63,6 +64,7 @@ export const useLitegraphService = () => {
|
||||
const toastStore = useToastStore()
|
||||
const widgetStore = useWidgetStore()
|
||||
const canvasStore = useCanvasStore()
|
||||
const { toggleSelectedNodesMode } = useSelectedLiteGraphItems()
|
||||
|
||||
// TODO: Dedupe `registerNodeDef`; this should remain synchronous.
|
||||
function registerSubgraphNodeDef(
|
||||
@@ -762,15 +764,8 @@ export const useLitegraphService = () => {
|
||||
options.push({
|
||||
content: 'Bypass',
|
||||
callback: () => {
|
||||
const mode =
|
||||
this.mode === LGraphEventMode.BYPASS
|
||||
? LGraphEventMode.ALWAYS
|
||||
: LGraphEventMode.BYPASS
|
||||
for (const item of app.canvas.selectedItems) {
|
||||
if (item instanceof LGraphNode) item.mode = mode
|
||||
}
|
||||
// @ts-expect-error fixme ts strict error
|
||||
this.graph.change()
|
||||
toggleSelectedNodesMode(LGraphEventMode.BYPASS)
|
||||
app.canvas.setDirty(true, true)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ export const useComfyManagerStore = defineStore('comfyManager', () => {
|
||||
const enabledPacksIds = ref<Set<string>>(new Set())
|
||||
const disabledPacksIds = ref<Set<string>>(new Set())
|
||||
const installedPacksIds = ref<Set<string>>(new Set())
|
||||
const installingPacksIds = ref<Set<string>>(new Set())
|
||||
const isStale = ref(true)
|
||||
const taskLogs = ref<TaskLog[]>([])
|
||||
|
||||
@@ -49,6 +50,9 @@ export const useComfyManagerStore = defineStore('comfyManager', () => {
|
||||
isInstalledPackId(packName) &&
|
||||
enabledPacksIds.value.has(packName)
|
||||
|
||||
const isInstallingPackId = (packName: string | undefined): boolean =>
|
||||
!!packName && installingPacksIds.value.has(packName)
|
||||
|
||||
const packsToIdSet = (packs: ManagerPackInstalled[]) =>
|
||||
packs.reduce((acc, pack) => {
|
||||
const id = pack.cnr_id || pack.aux_id
|
||||
@@ -117,7 +121,11 @@ export const useComfyManagerStore = defineStore('comfyManager', () => {
|
||||
whenever(isStale, refreshInstalledList, { immediate: true })
|
||||
whenever(uncompletedCount, () => showManagerProgressDialog())
|
||||
|
||||
const withLogs = (task: () => Promise<null>, taskName: string) => {
|
||||
const withLogs = (
|
||||
task: () => Promise<null>,
|
||||
taskName: string,
|
||||
packId?: string
|
||||
) => {
|
||||
const { startListening, stopListening, logs } = useServerLogs()
|
||||
|
||||
const loggedTask = async () => {
|
||||
@@ -128,6 +136,9 @@ export const useComfyManagerStore = defineStore('comfyManager', () => {
|
||||
|
||||
const onComplete = async () => {
|
||||
await stopListening()
|
||||
if (packId) {
|
||||
installingPacksIds.value.delete(packId)
|
||||
}
|
||||
setStale()
|
||||
}
|
||||
|
||||
@@ -152,8 +163,11 @@ export const useComfyManagerStore = defineStore('comfyManager', () => {
|
||||
}
|
||||
}
|
||||
|
||||
installingPacksIds.value.add(params.id)
|
||||
const task = () => managerService.installPack(params, signal)
|
||||
enqueueTask(withLogs(task, `${actionDescription} ${params.id}`))
|
||||
enqueueTask(
|
||||
withLogs(task, `${actionDescription} ${params.id}`, params.id)
|
||||
)
|
||||
},
|
||||
{ maxSize: 1 }
|
||||
)
|
||||
@@ -162,14 +176,16 @@ export const useComfyManagerStore = defineStore('comfyManager', () => {
|
||||
installPack.clear()
|
||||
installPack.cancel()
|
||||
const task = () => managerService.uninstallPack(params, signal)
|
||||
enqueueTask(withLogs(task, t('manager.uninstalling', { id: params.id })))
|
||||
enqueueTask(
|
||||
withLogs(task, t('manager.uninstalling', { id: params.id }), params.id)
|
||||
)
|
||||
}
|
||||
|
||||
const updatePack = useCachedRequest<ManagerPackInfo, void>(
|
||||
async (params: ManagerPackInfo, signal?: AbortSignal) => {
|
||||
updateAllPacks.cancel()
|
||||
const task = () => managerService.updatePack(params, signal)
|
||||
enqueueTask(withLogs(task, t('g.updating', { id: params.id })))
|
||||
enqueueTask(withLogs(task, t('g.updating', { id: params.id }), params.id))
|
||||
},
|
||||
{ maxSize: 1 }
|
||||
)
|
||||
@@ -184,7 +200,7 @@ export const useComfyManagerStore = defineStore('comfyManager', () => {
|
||||
|
||||
const disablePack = (params: ManagerPackInfo, signal?: AbortSignal) => {
|
||||
const task = () => managerService.disablePack(params, signal)
|
||||
enqueueTask(withLogs(task, t('g.disabling', { id: params.id })))
|
||||
enqueueTask(withLogs(task, t('g.disabling', { id: params.id }), params.id))
|
||||
}
|
||||
|
||||
const getInstalledPackVersion = (packId: string) => {
|
||||
@@ -212,6 +228,7 @@ export const useComfyManagerStore = defineStore('comfyManager', () => {
|
||||
installedPacksIds,
|
||||
isPackInstalled: isInstalledPackId,
|
||||
isPackEnabled: isEnabledPackId,
|
||||
isPackInstalling: isInstallingPackId,
|
||||
getInstalledPackVersion,
|
||||
refreshInstalledList,
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ export interface ComfyCommand {
|
||||
versionAdded?: string
|
||||
confirmation?: string // If non-nullish, this command will prompt for confirmation
|
||||
source?: string
|
||||
active?: () => boolean // Getter to check if the command is active/toggled on
|
||||
category?: 'essentials' | 'view-controls' // For shortcuts panel organization
|
||||
}
|
||||
|
||||
@@ -30,6 +31,7 @@ export class ComfyCommandImpl implements ComfyCommand {
|
||||
versionAdded?: string
|
||||
confirmation?: string
|
||||
source?: string
|
||||
active?: () => boolean
|
||||
category?: 'essentials' | 'view-controls'
|
||||
|
||||
constructor(command: ComfyCommand) {
|
||||
@@ -42,6 +44,7 @@ export class ComfyCommandImpl implements ComfyCommand {
|
||||
this.versionAdded = command.versionAdded
|
||||
this.confirmation = command.confirmation
|
||||
this.source = command.source
|
||||
this.active = command.active
|
||||
this.category = command.category
|
||||
}
|
||||
|
||||
|
||||
25
src/stores/helpCenterStore.ts
Normal file
25
src/stores/helpCenterStore.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export const useHelpCenterStore = defineStore('helpCenter', () => {
|
||||
const isVisible = ref(false)
|
||||
|
||||
const toggle = () => {
|
||||
isVisible.value = !isVisible.value
|
||||
}
|
||||
|
||||
const show = () => {
|
||||
isVisible.value = true
|
||||
}
|
||||
|
||||
const hide = () => {
|
||||
isVisible.value = false
|
||||
}
|
||||
|
||||
return {
|
||||
isVisible,
|
||||
toggle,
|
||||
show,
|
||||
hide
|
||||
}
|
||||
})
|
||||
@@ -10,6 +10,7 @@ import { useCommandStore } from './commandStore'
|
||||
export const useMenuItemStore = defineStore('menuItem', () => {
|
||||
const commandStore = useCommandStore()
|
||||
const menuItems = ref<MenuItem[]>([])
|
||||
const menuItemHasActiveStateChildren = ref<Record<string, boolean>>({})
|
||||
|
||||
const registerMenuGroup = (path: string[], items: MenuItem[]) => {
|
||||
let currentLevel = menuItems.value
|
||||
@@ -45,6 +46,14 @@ export const useMenuItemStore = defineStore('menuItem', () => {
|
||||
}
|
||||
// Add the new items to the last level
|
||||
currentLevel.push(...items)
|
||||
|
||||
// Store if any of the children have active state as we will hide the icon if they do
|
||||
const parentPath = path.join('.')
|
||||
if (!menuItemHasActiveStateChildren.value[parentPath]) {
|
||||
menuItemHasActiveStateChildren.value[parentPath] = items.some(
|
||||
(item) => item.comfyCommand?.active
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const registerCommands = (path: string[], commandIds: string[]) => {
|
||||
@@ -57,7 +66,8 @@ export const useMenuItemStore = defineStore('menuItem', () => {
|
||||
label: command.menubarLabel,
|
||||
icon: command.icon,
|
||||
tooltip: command.tooltip,
|
||||
comfyCommand: command
|
||||
comfyCommand: command,
|
||||
parentPath: path.join('.')
|
||||
}) as MenuItem
|
||||
)
|
||||
registerMenuGroup(path, items)
|
||||
@@ -92,6 +102,7 @@ export const useMenuItemStore = defineStore('menuItem', () => {
|
||||
registerMenuGroup,
|
||||
registerCommands,
|
||||
loadExtensionMenuCommands,
|
||||
registerCoreMenuCommands
|
||||
registerCoreMenuCommands,
|
||||
menuItemHasActiveStateChildren
|
||||
}
|
||||
})
|
||||
|
||||
@@ -226,6 +226,14 @@ export const useReleaseStore = defineStore('release', () => {
|
||||
return
|
||||
}
|
||||
|
||||
// Skip fetching if API nodes are disabled via argv
|
||||
if (
|
||||
systemStatsStore.systemStats?.system?.argv?.includes(
|
||||
'--disable-api-nodes'
|
||||
)
|
||||
) {
|
||||
return
|
||||
}
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useQueueSidebarTab } from '@/composables/sidebarTabs/useQueueSidebarTab
|
||||
import { useWorkflowsSidebarTab } from '@/composables/sidebarTabs/useWorkflowsSidebarTab'
|
||||
import { t, te } from '@/i18n'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useMenuItemStore } from '@/stores/menuItemStore'
|
||||
import { SidebarTabExtension } from '@/types/extensionTypes'
|
||||
|
||||
export const useSidebarTabStore = defineStore('sidebarTab', () => {
|
||||
@@ -38,16 +39,34 @@ export const useSidebarTabStore = defineStore('sidebarTab', () => {
|
||||
: String(tab.tooltip)
|
||||
: undefined
|
||||
|
||||
const menubarLabelFunction = () => {
|
||||
const menubarLabelKeys: Record<string, string> = {
|
||||
queue: 'menu.queue',
|
||||
'node-library': 'sideToolbar.nodeLibrary',
|
||||
'model-library': 'sideToolbar.modelLibrary',
|
||||
workflows: 'sideToolbar.workflows'
|
||||
}
|
||||
|
||||
const key = menubarLabelKeys[tab.id]
|
||||
if (key && te(key)) {
|
||||
return t(key)
|
||||
}
|
||||
|
||||
return tab.title
|
||||
}
|
||||
|
||||
useCommandStore().registerCommand({
|
||||
id: `Workspace.ToggleSidebarTab.${tab.id}`,
|
||||
icon: typeof tab.icon === 'string' ? tab.icon : undefined,
|
||||
label: labelFunction,
|
||||
menubarLabel: menubarLabelFunction,
|
||||
tooltip: tooltipFunction,
|
||||
versionAdded: '1.3.9',
|
||||
category: 'view-controls' as const,
|
||||
function: () => {
|
||||
toggleSidebarTab(tab.id)
|
||||
},
|
||||
active: () => activeSidebarTab.value?.id === tab.id,
|
||||
source: 'System'
|
||||
})
|
||||
}
|
||||
@@ -73,6 +92,25 @@ export const useSidebarTabStore = defineStore('sidebarTab', () => {
|
||||
registerSidebarTab(useNodeLibrarySidebarTab())
|
||||
registerSidebarTab(useModelLibrarySidebarTab())
|
||||
registerSidebarTab(useWorkflowsSidebarTab())
|
||||
|
||||
const menuStore = useMenuItemStore()
|
||||
|
||||
menuStore.registerCommands(
|
||||
['View'],
|
||||
[
|
||||
'Workspace.ToggleBottomPanel',
|
||||
'Comfy.BrowseTemplates',
|
||||
'Workspace.ToggleFocusMode',
|
||||
'Comfy.ToggleCanvasInfo',
|
||||
'Comfy.Canvas.ToggleMinimap',
|
||||
'Comfy.Canvas.ToggleLinkVisibility'
|
||||
]
|
||||
)
|
||||
|
||||
menuStore.registerCommands(
|
||||
['View'],
|
||||
['Comfy.Canvas.ZoomIn', 'Comfy.Canvas.ZoomOut', 'Comfy.Canvas.FitView']
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -19,7 +19,7 @@ export const IsInstallingKey: InjectionKey<Ref<boolean>> =
|
||||
Symbol('isInstalling')
|
||||
|
||||
export enum ManagerWsQueueStatus {
|
||||
DONE = 'done',
|
||||
DONE = 'all-done',
|
||||
IN_PROGRESS = 'in_progress'
|
||||
}
|
||||
|
||||
|
||||
@@ -16,12 +16,17 @@ export const whileMouseDown = (
|
||||
callback(iteration++)
|
||||
}, interval)
|
||||
|
||||
const dispose = useEventListener(element, 'mouseup', () => {
|
||||
const dispose = () => {
|
||||
clearInterval(intervalId)
|
||||
dispose()
|
||||
})
|
||||
disposeGlobal()
|
||||
disposeLocal()
|
||||
}
|
||||
|
||||
// Listen for mouseup globally to catch cases where user drags out of element
|
||||
const disposeGlobal = useEventListener(document, 'mouseup', dispose)
|
||||
const disposeLocal = useEventListener(element, 'mouseup', dispose)
|
||||
|
||||
return {
|
||||
dispose
|
||||
dispose: dispose
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@ const mockT = vi.fn((key: string) => {
|
||||
'shortcuts.subcategories.node': 'Node',
|
||||
'shortcuts.subcategories.queue': 'Queue',
|
||||
'shortcuts.subcategories.view': 'View',
|
||||
'shortcuts.subcategories.panelControls': 'Panel Controls'
|
||||
'shortcuts.subcategories.panelControls': 'Panel Controls',
|
||||
'commands.Workflow_New.label': 'New Blank Workflow'
|
||||
}
|
||||
return translations[key] || key
|
||||
})
|
||||
@@ -76,9 +77,7 @@ describe('ShortcutsList', () => {
|
||||
expect(wrapper.text()).toContain('Queue')
|
||||
|
||||
// Check that commands are rendered
|
||||
expect(wrapper.text()).toContain('New Workflow')
|
||||
expect(wrapper.text()).toContain('Add Node')
|
||||
expect(wrapper.text()).toContain('Clear Queue')
|
||||
expect(wrapper.text()).toContain('New Blank Workflow')
|
||||
})
|
||||
|
||||
it('should format keyboard shortcuts correctly', () => {
|
||||
|
||||
@@ -0,0 +1,440 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import PrimeVue from 'primevue/config'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { nextTick } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import ManagerProgressFooter from '@/components/dialog/footer/ManagerProgressFooter.vue'
|
||||
import { useComfyManagerService } from '@/services/comfyManagerService'
|
||||
import {
|
||||
useComfyManagerStore,
|
||||
useManagerProgressDialogStore
|
||||
} from '@/stores/comfyManagerStore'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
import { TaskLog } from '@/types/comfyManagerTypes'
|
||||
|
||||
// Mock modules
|
||||
vi.mock('@/stores/comfyManagerStore')
|
||||
vi.mock('@/stores/dialogStore')
|
||||
vi.mock('@/stores/settingStore')
|
||||
vi.mock('@/stores/commandStore')
|
||||
vi.mock('@/services/comfyManagerService')
|
||||
|
||||
// Mock useEventListener to capture the event handler
|
||||
let reconnectHandler: (() => void) | null = null
|
||||
vi.mock('@vueuse/core', async () => {
|
||||
const actual = await vi.importActual('@vueuse/core')
|
||||
return {
|
||||
...actual,
|
||||
useEventListener: vi.fn(
|
||||
(_target: any, event: string, handler: any, _options: any) => {
|
||||
if (event === 'reconnected') {
|
||||
reconnectHandler = handler
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
vi.mock('@/services/workflowService', () => ({
|
||||
useWorkflowService: vi.fn(() => ({
|
||||
reloadCurrentWorkflow: vi.fn().mockResolvedValue(undefined)
|
||||
}))
|
||||
}))
|
||||
vi.mock('@/stores/workspace/colorPaletteStore', () => ({
|
||||
useColorPaletteStore: vi.fn(() => ({
|
||||
completedActivePalette: {
|
||||
light_theme: false
|
||||
}
|
||||
}))
|
||||
}))
|
||||
|
||||
// Helper function to mount component with required setup
|
||||
const mountComponent = (options: { captureError?: boolean } = {}) => {
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages: {
|
||||
en: {}
|
||||
}
|
||||
})
|
||||
|
||||
const config: any = {
|
||||
global: {
|
||||
plugins: [PrimeVue, i18n],
|
||||
mocks: {
|
||||
$t: (key: string) => key // Mock i18n translation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add error handler for tests that expect errors
|
||||
if (options.captureError) {
|
||||
config.global.config = {
|
||||
errorHandler: () => {
|
||||
// Suppress error in test
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mount(ManagerProgressFooter, config)
|
||||
}
|
||||
|
||||
describe('ManagerProgressFooter', () => {
|
||||
const mockTaskLogs: TaskLog[] = []
|
||||
|
||||
const mockComfyManagerStore = {
|
||||
uncompletedCount: 0,
|
||||
taskLogs: mockTaskLogs,
|
||||
allTasksDone: true,
|
||||
clearLogs: vi.fn(),
|
||||
setStale: vi.fn(),
|
||||
// Add other required properties
|
||||
isLoading: { value: false },
|
||||
error: { value: null },
|
||||
statusMessage: { value: 'DONE' },
|
||||
installedPacks: {},
|
||||
installedPacksIds: new Set(),
|
||||
isPackInstalled: vi.fn(),
|
||||
isPackEnabled: vi.fn(),
|
||||
getInstalledPackVersion: vi.fn(),
|
||||
refreshInstalledList: vi.fn(),
|
||||
installPack: vi.fn(),
|
||||
uninstallPack: vi.fn(),
|
||||
updatePack: vi.fn(),
|
||||
updateAllPacks: vi.fn(),
|
||||
disablePack: vi.fn(),
|
||||
enablePack: vi.fn()
|
||||
}
|
||||
|
||||
const mockDialogStore = {
|
||||
closeDialog: vi.fn(),
|
||||
// Add other required properties
|
||||
dialogStack: { value: [] },
|
||||
showDialog: vi.fn(),
|
||||
$id: 'dialog',
|
||||
$state: {} as any,
|
||||
$patch: vi.fn(),
|
||||
$reset: vi.fn(),
|
||||
$subscribe: vi.fn(),
|
||||
$dispose: vi.fn(),
|
||||
$onAction: vi.fn()
|
||||
}
|
||||
|
||||
const mockSettingStore = {
|
||||
get: vi.fn().mockReturnValue(false),
|
||||
set: vi.fn(),
|
||||
// Add other required properties
|
||||
settingValues: { value: {} },
|
||||
settingsById: { value: {} },
|
||||
exists: vi.fn(),
|
||||
getDefaultValue: vi.fn(),
|
||||
loadSettingValues: vi.fn(),
|
||||
updateValue: vi.fn(),
|
||||
$id: 'setting',
|
||||
$state: {} as any,
|
||||
$patch: vi.fn(),
|
||||
$reset: vi.fn(),
|
||||
$subscribe: vi.fn(),
|
||||
$dispose: vi.fn(),
|
||||
$onAction: vi.fn()
|
||||
}
|
||||
|
||||
const mockProgressDialogStore = {
|
||||
isExpanded: false,
|
||||
toggle: vi.fn(),
|
||||
collapse: vi.fn(),
|
||||
expand: vi.fn()
|
||||
}
|
||||
|
||||
const mockCommandStore = {
|
||||
execute: vi.fn().mockResolvedValue(undefined)
|
||||
}
|
||||
|
||||
const mockComfyManagerService = {
|
||||
rebootComfyUI: vi.fn().mockResolvedValue(null)
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
// Reset task logs
|
||||
mockTaskLogs.length = 0
|
||||
mockComfyManagerStore.taskLogs = mockTaskLogs
|
||||
// Reset event handler
|
||||
reconnectHandler = null
|
||||
|
||||
vi.mocked(useComfyManagerStore).mockReturnValue(
|
||||
mockComfyManagerStore as any
|
||||
)
|
||||
vi.mocked(useDialogStore).mockReturnValue(mockDialogStore as any)
|
||||
vi.mocked(useSettingStore).mockReturnValue(mockSettingStore as any)
|
||||
vi.mocked(useManagerProgressDialogStore).mockReturnValue(
|
||||
mockProgressDialogStore as any
|
||||
)
|
||||
vi.mocked(useCommandStore).mockReturnValue(mockCommandStore as any)
|
||||
vi.mocked(useComfyManagerService).mockReturnValue(
|
||||
mockComfyManagerService as any
|
||||
)
|
||||
})
|
||||
|
||||
describe('State 1: Queue Running', () => {
|
||||
it('should display loading spinner and progress counter when queue is running', async () => {
|
||||
// Setup queue running state
|
||||
mockComfyManagerStore.uncompletedCount = 3
|
||||
mockTaskLogs.push(
|
||||
{ taskName: 'Installing pack1', logs: [] },
|
||||
{ taskName: 'Installing pack2', logs: [] },
|
||||
{ taskName: 'Installing pack3', logs: [] }
|
||||
)
|
||||
|
||||
const wrapper = mountComponent()
|
||||
|
||||
// Check loading spinner exists (DotSpinner component)
|
||||
expect(wrapper.find('.inline-flex').exists()).toBe(true)
|
||||
|
||||
// Check current task name is displayed
|
||||
expect(wrapper.text()).toContain('Installing pack3')
|
||||
|
||||
// Check progress counter (completed: 2 of 3)
|
||||
expect(wrapper.text()).toMatch(/2.*3/)
|
||||
|
||||
// Check expand/collapse button exists
|
||||
const expandButton = wrapper.find('[aria-label="Expand"]')
|
||||
expect(expandButton.exists()).toBe(true)
|
||||
|
||||
// Check Apply Changes button is NOT shown
|
||||
expect(wrapper.text()).not.toContain('manager.applyChanges')
|
||||
})
|
||||
|
||||
it('should toggle expansion when expand button is clicked', async () => {
|
||||
mockComfyManagerStore.uncompletedCount = 1
|
||||
mockTaskLogs.push({ taskName: 'Installing', logs: [] })
|
||||
|
||||
const wrapper = mountComponent()
|
||||
|
||||
const expandButton = wrapper.find('[aria-label="Expand"]')
|
||||
await expandButton.trigger('click')
|
||||
|
||||
expect(mockProgressDialogStore.toggle).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('State 2: Tasks Completed (Waiting for Restart)', () => {
|
||||
it('should display check mark and Apply Changes button when all tasks are done', async () => {
|
||||
// Setup tasks completed state
|
||||
mockComfyManagerStore.uncompletedCount = 0
|
||||
mockTaskLogs.push(
|
||||
{ taskName: 'Installed pack1', logs: [] },
|
||||
{ taskName: 'Installed pack2', logs: [] }
|
||||
)
|
||||
mockComfyManagerStore.allTasksDone = true
|
||||
|
||||
const wrapper = mountComponent()
|
||||
|
||||
// Check check mark emoji
|
||||
expect(wrapper.text()).toContain('✅')
|
||||
|
||||
// Check restart message (split into 3 parts)
|
||||
expect(wrapper.text()).toContain('manager.clickToFinishSetup')
|
||||
expect(wrapper.text()).toContain('manager.applyChanges')
|
||||
expect(wrapper.text()).toContain('manager.toFinishSetup')
|
||||
|
||||
// Check Apply Changes button exists
|
||||
const applyButton = wrapper
|
||||
.findAll('button')
|
||||
.find((btn) => btn.text().includes('manager.applyChanges'))
|
||||
expect(applyButton).toBeTruthy()
|
||||
|
||||
// Check no progress counter
|
||||
expect(wrapper.text()).not.toMatch(/\d+.*of.*\d+/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('State 3: Restarting', () => {
|
||||
it('should display restarting message and spinner during restart', async () => {
|
||||
// Setup completed state first
|
||||
mockComfyManagerStore.uncompletedCount = 0
|
||||
mockComfyManagerStore.allTasksDone = true
|
||||
|
||||
const wrapper = mountComponent()
|
||||
|
||||
// Click Apply Changes to trigger restart
|
||||
const applyButton = wrapper
|
||||
.findAll('button')
|
||||
.find((btn) => btn.text().includes('manager.applyChanges'))
|
||||
await applyButton?.trigger('click')
|
||||
|
||||
// Wait for state update
|
||||
await nextTick()
|
||||
|
||||
// Check restarting message
|
||||
expect(wrapper.text()).toContain('manager.restartingBackend')
|
||||
|
||||
// Check loading spinner during restart
|
||||
expect(wrapper.find('.inline-flex').exists()).toBe(true)
|
||||
|
||||
// Check Apply Changes button is hidden
|
||||
expect(wrapper.text()).not.toContain('manager.applyChanges')
|
||||
})
|
||||
})
|
||||
|
||||
describe('State 4: Restart Completed', () => {
|
||||
it('should display success message and auto-close after 3 seconds', async () => {
|
||||
vi.useFakeTimers()
|
||||
|
||||
// Setup completed state
|
||||
mockComfyManagerStore.uncompletedCount = 0
|
||||
mockComfyManagerStore.allTasksDone = true
|
||||
|
||||
const wrapper = mountComponent()
|
||||
|
||||
// Trigger restart
|
||||
const applyButton = wrapper
|
||||
.findAll('button')
|
||||
.find((btn) => btn.text().includes('manager.applyChanges'))
|
||||
await applyButton?.trigger('click')
|
||||
|
||||
// Wait for event listener to be set up
|
||||
await nextTick()
|
||||
|
||||
// Trigger the reconnect handler directly
|
||||
if (reconnectHandler) {
|
||||
await reconnectHandler()
|
||||
}
|
||||
|
||||
// Wait for restart completed state
|
||||
await nextTick()
|
||||
|
||||
// Check success message
|
||||
expect(wrapper.text()).toContain('🎉')
|
||||
expect(wrapper.text()).toContain(
|
||||
'manager.extensionsSuccessfullyInstalled'
|
||||
)
|
||||
|
||||
// Check dialog closes after 3 seconds
|
||||
vi.advanceTimersByTime(3000)
|
||||
|
||||
await nextTick()
|
||||
|
||||
expect(mockDialogStore.closeDialog).toHaveBeenCalledWith({
|
||||
key: 'global-manager-progress-dialog'
|
||||
})
|
||||
expect(mockComfyManagerStore.clearLogs).toHaveBeenCalled()
|
||||
|
||||
vi.useRealTimers()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Common Features', () => {
|
||||
it('should always display close button', async () => {
|
||||
const wrapper = mountComponent()
|
||||
|
||||
const closeButton = wrapper.find('[aria-label="Close"]')
|
||||
expect(closeButton.exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('should close dialog when close button is clicked', async () => {
|
||||
const wrapper = mountComponent()
|
||||
|
||||
const closeButton = wrapper.find('[aria-label="Close"]')
|
||||
await closeButton.trigger('click')
|
||||
|
||||
expect(mockDialogStore.closeDialog).toHaveBeenCalledWith({
|
||||
key: 'global-manager-progress-dialog'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Toast Management', () => {
|
||||
it('should suppress reconnection toasts during restart', async () => {
|
||||
mockComfyManagerStore.uncompletedCount = 0
|
||||
mockComfyManagerStore.allTasksDone = true
|
||||
mockSettingStore.get.mockReturnValue(false) // Original setting
|
||||
|
||||
const wrapper = mountComponent()
|
||||
|
||||
// Click Apply Changes
|
||||
const applyButton = wrapper
|
||||
.findAll('button')
|
||||
.find((btn) => btn.text().includes('manager.applyChanges'))
|
||||
await applyButton?.trigger('click')
|
||||
|
||||
// Check toast setting was disabled
|
||||
expect(mockSettingStore.set).toHaveBeenCalledWith(
|
||||
'Comfy.Toast.DisableReconnectingToast',
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
it('should restore toast settings after restart completes', async () => {
|
||||
mockComfyManagerStore.uncompletedCount = 0
|
||||
mockComfyManagerStore.allTasksDone = true
|
||||
mockSettingStore.get.mockReturnValue(false) // Original setting
|
||||
|
||||
const wrapper = mountComponent()
|
||||
|
||||
// Click Apply Changes
|
||||
const applyButton = wrapper
|
||||
.findAll('button')
|
||||
.find((btn) => btn.text().includes('manager.applyChanges'))
|
||||
await applyButton?.trigger('click')
|
||||
|
||||
// Wait for event listener to be set up
|
||||
await nextTick()
|
||||
|
||||
// Trigger the reconnect handler directly
|
||||
if (reconnectHandler) {
|
||||
await reconnectHandler()
|
||||
}
|
||||
|
||||
// Wait for settings restoration
|
||||
await nextTick()
|
||||
|
||||
expect(mockSettingStore.set).toHaveBeenCalledWith(
|
||||
'Comfy.Toast.DisableReconnectingToast',
|
||||
false // Restored to original
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Error Handling', () => {
|
||||
it('should restore state and close dialog on restart error', async () => {
|
||||
mockComfyManagerStore.uncompletedCount = 0
|
||||
mockComfyManagerStore.allTasksDone = true
|
||||
|
||||
// Mock restart to throw error
|
||||
mockComfyManagerService.rebootComfyUI.mockRejectedValue(
|
||||
new Error('Restart failed')
|
||||
)
|
||||
|
||||
const wrapper = mountComponent({ captureError: true })
|
||||
|
||||
// Click Apply Changes
|
||||
const applyButton = wrapper
|
||||
.findAll('button')
|
||||
.find((btn) => btn.text().includes('manager.applyChanges'))
|
||||
|
||||
expect(applyButton).toBeTruthy()
|
||||
|
||||
// The component throws the error but Vue Test Utils catches it
|
||||
// We need to check if the error handling logic was executed
|
||||
await applyButton!.trigger('click').catch(() => {
|
||||
// Error is expected, ignore it
|
||||
})
|
||||
|
||||
// Wait for error handling
|
||||
await nextTick()
|
||||
|
||||
// Check dialog was closed on error
|
||||
expect(mockDialogStore.closeDialog).toHaveBeenCalled()
|
||||
// Check toast settings were restored
|
||||
expect(mockSettingStore.set).toHaveBeenCalledWith(
|
||||
'Comfy.Toast.DisableReconnectingToast',
|
||||
false
|
||||
)
|
||||
// Check that the error handler was called
|
||||
expect(mockComfyManagerService.rebootComfyUI).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1512,7 +1512,10 @@ describe('useNodePricing', () => {
|
||||
{ model: 'gpt-4o', expected: '$0.0025/$0.01 per 1K tokens' },
|
||||
{ model: 'gpt-4.1-nano', expected: '$0.0001/$0.0004 per 1K tokens' },
|
||||
{ model: 'gpt-4.1-mini', expected: '$0.0004/$0.0016 per 1K tokens' },
|
||||
{ model: 'gpt-4.1', expected: '$0.002/$0.008 per 1K tokens' }
|
||||
{ model: 'gpt-4.1', expected: '$0.002/$0.008 per 1K tokens' },
|
||||
{ model: 'gpt-5-nano', expected: '$0.00005/$0.0004 per 1K tokens' },
|
||||
{ model: 'gpt-5-mini', expected: '$0.00025/$0.002 per 1K tokens' },
|
||||
{ model: 'gpt-5', expected: '$0.00125/$0.01 per 1K tokens' }
|
||||
]
|
||||
|
||||
testCases.forEach(({ model, expected }) => {
|
||||
|
||||
121
tests-ui/tests/composables/useFeatureFlags.test.ts
Normal file
121
tests-ui/tests/composables/useFeatureFlags.test.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { isReactive, isReadonly } from 'vue'
|
||||
|
||||
import {
|
||||
ServerFeatureFlag,
|
||||
useFeatureFlags
|
||||
} from '@/composables/useFeatureFlags'
|
||||
import { api } from '@/scripts/api'
|
||||
|
||||
// Mock the API module
|
||||
vi.mock('@/scripts/api', () => ({
|
||||
api: {
|
||||
getServerFeature: vi.fn()
|
||||
}
|
||||
}))
|
||||
|
||||
describe('useFeatureFlags', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('flags object', () => {
|
||||
it('should provide reactive readonly flags', () => {
|
||||
const { flags } = useFeatureFlags()
|
||||
|
||||
expect(isReadonly(flags)).toBe(true)
|
||||
expect(isReactive(flags)).toBe(true)
|
||||
})
|
||||
|
||||
it('should access supportsPreviewMetadata', () => {
|
||||
vi.mocked(api.getServerFeature).mockImplementation(
|
||||
(path, defaultValue) => {
|
||||
if (path === ServerFeatureFlag.SUPPORTS_PREVIEW_METADATA)
|
||||
return true as any
|
||||
return defaultValue
|
||||
}
|
||||
)
|
||||
|
||||
const { flags } = useFeatureFlags()
|
||||
expect(flags.supportsPreviewMetadata).toBe(true)
|
||||
expect(api.getServerFeature).toHaveBeenCalledWith(
|
||||
ServerFeatureFlag.SUPPORTS_PREVIEW_METADATA
|
||||
)
|
||||
})
|
||||
|
||||
it('should access maxUploadSize', () => {
|
||||
vi.mocked(api.getServerFeature).mockImplementation(
|
||||
(path, defaultValue) => {
|
||||
if (path === ServerFeatureFlag.MAX_UPLOAD_SIZE)
|
||||
return 209715200 as any // 200MB
|
||||
return defaultValue
|
||||
}
|
||||
)
|
||||
|
||||
const { flags } = useFeatureFlags()
|
||||
expect(flags.maxUploadSize).toBe(209715200)
|
||||
expect(api.getServerFeature).toHaveBeenCalledWith(
|
||||
ServerFeatureFlag.MAX_UPLOAD_SIZE
|
||||
)
|
||||
})
|
||||
|
||||
it('should return undefined when features are not available and no default provided', () => {
|
||||
vi.mocked(api.getServerFeature).mockImplementation(
|
||||
(_path, defaultValue) => defaultValue as any
|
||||
)
|
||||
|
||||
const { flags } = useFeatureFlags()
|
||||
expect(flags.supportsPreviewMetadata).toBeUndefined()
|
||||
expect(flags.maxUploadSize).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('featureFlag', () => {
|
||||
it('should create reactive computed for custom feature flags', () => {
|
||||
vi.mocked(api.getServerFeature).mockImplementation(
|
||||
(path, defaultValue) => {
|
||||
if (path === 'custom.feature') return 'custom-value' as any
|
||||
return defaultValue
|
||||
}
|
||||
)
|
||||
|
||||
const { featureFlag } = useFeatureFlags()
|
||||
const customFlag = featureFlag('custom.feature', 'default')
|
||||
|
||||
expect(customFlag.value).toBe('custom-value')
|
||||
expect(api.getServerFeature).toHaveBeenCalledWith(
|
||||
'custom.feature',
|
||||
'default'
|
||||
)
|
||||
})
|
||||
|
||||
it('should handle nested paths', () => {
|
||||
vi.mocked(api.getServerFeature).mockImplementation(
|
||||
(path, defaultValue) => {
|
||||
if (path === 'extension.custom.nested.feature') return true as any
|
||||
return defaultValue
|
||||
}
|
||||
)
|
||||
|
||||
const { featureFlag } = useFeatureFlags()
|
||||
const nestedFlag = featureFlag('extension.custom.nested.feature', false)
|
||||
|
||||
expect(nestedFlag.value).toBe(true)
|
||||
})
|
||||
|
||||
it('should work with ServerFeatureFlag enum', () => {
|
||||
vi.mocked(api.getServerFeature).mockImplementation(
|
||||
(path, defaultValue) => {
|
||||
if (path === ServerFeatureFlag.MAX_UPLOAD_SIZE)
|
||||
return 104857600 as any
|
||||
return defaultValue
|
||||
}
|
||||
)
|
||||
|
||||
const { featureFlag } = useFeatureFlags()
|
||||
const maxUploadSize = featureFlag(ServerFeatureFlag.MAX_UPLOAD_SIZE)
|
||||
|
||||
expect(maxUploadSize.value).toBe(104857600)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -28,7 +28,7 @@ describe('useManagerQueue', () => {
|
||||
const getEventListenerCallback = () =>
|
||||
vi.mocked(api.addEventListener).mock.calls[0][1]
|
||||
|
||||
const simulateServerStatus = async (status: 'done' | 'in_progress') => {
|
||||
const simulateServerStatus = async (status: 'all-done' | 'in_progress') => {
|
||||
const event = new CustomEvent('cm-queue-status', {
|
||||
detail: { status }
|
||||
})
|
||||
@@ -49,7 +49,7 @@ describe('useManagerQueue', () => {
|
||||
const queue = useManagerQueue()
|
||||
|
||||
expect(queue.queueLength.value).toBe(0)
|
||||
expect(queue.statusMessage.value).toBe('done')
|
||||
expect(queue.statusMessage.value).toBe('all-done')
|
||||
expect(queue.allTasksDone.value).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -104,7 +104,7 @@ describe('useManagerQueue', () => {
|
||||
await nextTick()
|
||||
|
||||
// Should maintain the default status
|
||||
expect(queue.statusMessage.value).toBe('done')
|
||||
expect(queue.statusMessage.value).toBe('all-done')
|
||||
})
|
||||
|
||||
it('should handle missing status property gracefully', async () => {
|
||||
@@ -119,7 +119,7 @@ describe('useManagerQueue', () => {
|
||||
await nextTick()
|
||||
|
||||
// Should maintain the default status
|
||||
expect(queue.statusMessage.value).toBe('done')
|
||||
expect(queue.statusMessage.value).toBe('all-done')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -127,7 +127,7 @@ describe('useManagerQueue', () => {
|
||||
it('should start the next task when server is idle and queue has items', async () => {
|
||||
const { queue, mockTask } = createQueueWithMockTask()
|
||||
|
||||
await simulateServerStatus('done')
|
||||
await simulateServerStatus('all-done')
|
||||
|
||||
// Task should have been started
|
||||
expect(mockTask.task).toHaveBeenCalled()
|
||||
@@ -138,7 +138,7 @@ describe('useManagerQueue', () => {
|
||||
const { mockTask } = createQueueWithMockTask()
|
||||
|
||||
// Start the task
|
||||
await simulateServerStatus('done')
|
||||
await simulateServerStatus('all-done')
|
||||
expect(mockTask.task).toHaveBeenCalled()
|
||||
|
||||
// Simulate task completion
|
||||
@@ -148,7 +148,7 @@ describe('useManagerQueue', () => {
|
||||
await simulateServerStatus('in_progress')
|
||||
expect(mockTask.onComplete).not.toHaveBeenCalled()
|
||||
|
||||
await simulateServerStatus('done')
|
||||
await simulateServerStatus('all-done')
|
||||
expect(mockTask.onComplete).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -159,7 +159,7 @@ describe('useManagerQueue', () => {
|
||||
queue.enqueueTask(mockTask)
|
||||
|
||||
// Start the task
|
||||
await simulateServerStatus('done')
|
||||
await simulateServerStatus('all-done')
|
||||
expect(mockTask.task).toHaveBeenCalled()
|
||||
|
||||
// Simulate task completion
|
||||
@@ -167,7 +167,7 @@ describe('useManagerQueue', () => {
|
||||
|
||||
// Simulate server cycle
|
||||
await simulateServerStatus('in_progress')
|
||||
await simulateServerStatus('done')
|
||||
await simulateServerStatus('all-done')
|
||||
|
||||
// Should not throw errors even without onComplete
|
||||
expect(queue.allTasksDone.value).toBe(true)
|
||||
@@ -184,14 +184,14 @@ describe('useManagerQueue', () => {
|
||||
expect(queue.queueLength.value).toBe(2)
|
||||
|
||||
// Process first task
|
||||
await simulateServerStatus('done')
|
||||
await simulateServerStatus('all-done')
|
||||
expect(mockTask1.task).toHaveBeenCalled()
|
||||
expect(queue.queueLength.value).toBe(1)
|
||||
|
||||
// Complete first task
|
||||
await mockTask1.task.mock.results[0].value
|
||||
await simulateServerStatus('in_progress')
|
||||
await simulateServerStatus('done')
|
||||
await simulateServerStatus('all-done')
|
||||
expect(mockTask1.onComplete).toHaveBeenCalled()
|
||||
|
||||
// Process second task
|
||||
@@ -201,7 +201,7 @@ describe('useManagerQueue', () => {
|
||||
// Complete second task
|
||||
await mockTask2.task.mock.results[0].value
|
||||
await simulateServerStatus('in_progress')
|
||||
await simulateServerStatus('done')
|
||||
await simulateServerStatus('all-done')
|
||||
expect(mockTask2.onComplete).toHaveBeenCalled()
|
||||
|
||||
// Queue should be empty and all tasks done
|
||||
@@ -219,7 +219,7 @@ describe('useManagerQueue', () => {
|
||||
queue.enqueueTask(mockTask)
|
||||
|
||||
// Start the task
|
||||
await simulateServerStatus('done')
|
||||
await simulateServerStatus('all-done')
|
||||
expect(mockTask.task).toHaveBeenCalled()
|
||||
|
||||
// Let the promise rejection happen
|
||||
@@ -231,7 +231,7 @@ describe('useManagerQueue', () => {
|
||||
|
||||
// Simulate server cycle
|
||||
await simulateServerStatus('in_progress')
|
||||
await simulateServerStatus('done')
|
||||
await simulateServerStatus('all-done')
|
||||
|
||||
// onComplete should still be called for failed tasks
|
||||
expect(mockTask.onComplete).toHaveBeenCalled()
|
||||
@@ -252,7 +252,7 @@ describe('useManagerQueue', () => {
|
||||
])
|
||||
|
||||
// Task 1
|
||||
await simulateServerStatus('done')
|
||||
await simulateServerStatus('all-done')
|
||||
expect(mockTask1.task).toHaveBeenCalled()
|
||||
|
||||
// Verify state of onComplete callbacks
|
||||
@@ -266,7 +266,7 @@ describe('useManagerQueue', () => {
|
||||
|
||||
// Task 2
|
||||
await simulateServerStatus('in_progress')
|
||||
await simulateServerStatus('done')
|
||||
await simulateServerStatus('all-done')
|
||||
expect(mockTask2.task).toHaveBeenCalled()
|
||||
|
||||
// Verify state of onComplete callbacks
|
||||
@@ -279,7 +279,7 @@ describe('useManagerQueue', () => {
|
||||
|
||||
// Task 3
|
||||
await simulateServerStatus('in_progress')
|
||||
await simulateServerStatus('done')
|
||||
await simulateServerStatus('all-done')
|
||||
|
||||
// Verify state of onComplete callbacks
|
||||
expect(mockTask3.task).toHaveBeenCalled()
|
||||
@@ -297,7 +297,7 @@ describe('useManagerQueue', () => {
|
||||
|
||||
// Add first task and start processing
|
||||
queue.enqueueTask(mockTask1)
|
||||
await simulateServerStatus('done')
|
||||
await simulateServerStatus('all-done')
|
||||
expect(mockTask1.task).toHaveBeenCalled()
|
||||
|
||||
// Add second task while first is processing
|
||||
@@ -307,7 +307,7 @@ describe('useManagerQueue', () => {
|
||||
// Complete first task
|
||||
await mockTask1.task.mock.results[0].value
|
||||
await simulateServerStatus('in_progress')
|
||||
await simulateServerStatus('done')
|
||||
await simulateServerStatus('all-done')
|
||||
|
||||
// Second task should now be processed
|
||||
expect(mockTask2.task).toHaveBeenCalled()
|
||||
@@ -318,9 +318,9 @@ describe('useManagerQueue', () => {
|
||||
|
||||
// Cycle server status without any tasks
|
||||
await simulateServerStatus('in_progress')
|
||||
await simulateServerStatus('done')
|
||||
await simulateServerStatus('all-done')
|
||||
await simulateServerStatus('in_progress')
|
||||
await simulateServerStatus('done')
|
||||
await simulateServerStatus('all-done')
|
||||
|
||||
// Should not cause any errors
|
||||
expect(queue.allTasksDone.value).toBe(true)
|
||||
|
||||
@@ -6,6 +6,8 @@ import { useComfyManagerService } from '@/services/comfyManagerService'
|
||||
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
|
||||
import {
|
||||
InstalledPacksResponse,
|
||||
ManagerChannel,
|
||||
ManagerDatabaseSource,
|
||||
ManagerPackInstalled
|
||||
} from '@/types/comfyManagerTypes'
|
||||
|
||||
@@ -13,6 +15,34 @@ vi.mock('@/services/comfyManagerService', () => ({
|
||||
useComfyManagerService: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('@/services/dialogService', () => ({
|
||||
useDialogService: () => ({
|
||||
showManagerProgressDialog: vi.fn()
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('@/composables/useManagerQueue', () => {
|
||||
const enqueueTaskMock = vi.fn()
|
||||
|
||||
return {
|
||||
useManagerQueue: () => ({
|
||||
statusMessage: ref(''),
|
||||
allTasksDone: ref(false),
|
||||
enqueueTask: enqueueTaskMock,
|
||||
uncompletedCount: ref(0)
|
||||
}),
|
||||
enqueueTask: enqueueTaskMock
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/composables/useServerLogs', () => ({
|
||||
useServerLogs: () => ({
|
||||
startListening: vi.fn(),
|
||||
stopListening: vi.fn(),
|
||||
logs: ref([])
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('vue-i18n', () => ({
|
||||
useI18n: () => ({
|
||||
t: vi.fn((key) => key)
|
||||
@@ -33,11 +63,7 @@ interface EnabledDisabledTestCase {
|
||||
}
|
||||
|
||||
describe('useComfyManagerStore', () => {
|
||||
let mockManagerService: {
|
||||
isLoading: ReturnType<typeof ref<boolean>>
|
||||
error: ReturnType<typeof ref<string | null>>
|
||||
listInstalledPacks: ReturnType<typeof vi.fn>
|
||||
}
|
||||
let mockManagerService: ReturnType<typeof useComfyManagerService>
|
||||
|
||||
const triggerPacksChange = async (
|
||||
installedPacks: InstalledPacksResponse,
|
||||
@@ -55,10 +81,21 @@ describe('useComfyManagerStore', () => {
|
||||
mockManagerService = {
|
||||
isLoading: ref(false),
|
||||
error: ref(null),
|
||||
listInstalledPacks: vi.fn().mockResolvedValue({})
|
||||
startQueue: vi.fn().mockResolvedValue(null),
|
||||
resetQueue: vi.fn().mockResolvedValue(null),
|
||||
getQueueStatus: vi.fn().mockResolvedValue(null),
|
||||
listInstalledPacks: vi.fn().mockResolvedValue({}),
|
||||
getImportFailInfo: vi.fn().mockResolvedValue(null),
|
||||
installPack: vi.fn().mockResolvedValue(null),
|
||||
uninstallPack: vi.fn().mockResolvedValue(null),
|
||||
enablePack: vi.fn().mockResolvedValue(null),
|
||||
disablePack: vi.fn().mockResolvedValue(null),
|
||||
updatePack: vi.fn().mockResolvedValue(null),
|
||||
updateAllPacks: vi.fn().mockResolvedValue(null),
|
||||
rebootComfyUI: vi.fn().mockResolvedValue(null),
|
||||
isLegacyManagerUI: vi.fn().mockResolvedValue(false)
|
||||
}
|
||||
|
||||
// @ts-expect-error Mocking the return type of useComfyManagerService
|
||||
vi.mocked(useComfyManagerService).mockReturnValue(mockManagerService)
|
||||
})
|
||||
|
||||
@@ -313,4 +350,90 @@ describe('useComfyManagerStore', () => {
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
describe('isPackInstalling', () => {
|
||||
it('should return false for packs not being installed', () => {
|
||||
const store = useComfyManagerStore()
|
||||
expect(store.isPackInstalling('test-pack')).toBe(false)
|
||||
expect(store.isPackInstalling(undefined)).toBe(false)
|
||||
expect(store.isPackInstalling('')).toBe(false)
|
||||
})
|
||||
|
||||
it('should track pack as installing when installPack is called', async () => {
|
||||
const store = useComfyManagerStore()
|
||||
|
||||
// Call installPack
|
||||
await store.installPack.call({
|
||||
id: 'test-pack',
|
||||
repository: 'https://github.com/test/test-pack',
|
||||
channel: ManagerChannel.DEV,
|
||||
mode: ManagerDatabaseSource.CACHE,
|
||||
selected_version: 'latest',
|
||||
version: 'latest'
|
||||
})
|
||||
|
||||
// Check that the pack is marked as installing
|
||||
expect(store.isPackInstalling('test-pack')).toBe(true)
|
||||
})
|
||||
|
||||
it('should remove pack from installing list when explicitly removed', async () => {
|
||||
const store = useComfyManagerStore()
|
||||
|
||||
// Call installPack
|
||||
await store.installPack.call({
|
||||
id: 'test-pack',
|
||||
repository: 'https://github.com/test/test-pack',
|
||||
channel: ManagerChannel.DEV,
|
||||
mode: ManagerDatabaseSource.CACHE,
|
||||
selected_version: 'latest',
|
||||
version: 'latest'
|
||||
})
|
||||
|
||||
// Verify pack is installing
|
||||
expect(store.isPackInstalling('test-pack')).toBe(true)
|
||||
|
||||
// Call installPack again for another pack to demonstrate multiple installs
|
||||
await store.installPack.call({
|
||||
id: 'another-pack',
|
||||
repository: 'https://github.com/test/another-pack',
|
||||
channel: ManagerChannel.DEV,
|
||||
mode: ManagerDatabaseSource.CACHE,
|
||||
selected_version: 'latest',
|
||||
version: 'latest'
|
||||
})
|
||||
|
||||
// Both should be installing
|
||||
expect(store.isPackInstalling('test-pack')).toBe(true)
|
||||
expect(store.isPackInstalling('another-pack')).toBe(true)
|
||||
})
|
||||
|
||||
it('should track multiple packs installing independently', async () => {
|
||||
const store = useComfyManagerStore()
|
||||
|
||||
// Install pack 1
|
||||
await store.installPack.call({
|
||||
id: 'pack-1',
|
||||
repository: 'https://github.com/test/pack-1',
|
||||
channel: ManagerChannel.DEV,
|
||||
mode: ManagerDatabaseSource.CACHE,
|
||||
selected_version: 'latest',
|
||||
version: 'latest'
|
||||
})
|
||||
|
||||
// Install pack 2
|
||||
await store.installPack.call({
|
||||
id: 'pack-2',
|
||||
repository: 'https://github.com/test/pack-2',
|
||||
channel: ManagerChannel.DEV,
|
||||
mode: ManagerDatabaseSource.CACHE,
|
||||
selected_version: 'latest',
|
||||
version: 'latest'
|
||||
})
|
||||
|
||||
// Both should be installing
|
||||
expect(store.isPackInstalling('pack-1')).toBe(true)
|
||||
expect(store.isPackInstalling('pack-2')).toBe(true)
|
||||
expect(store.isPackInstalling('pack-3')).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -251,6 +251,53 @@ describe('useReleaseStore', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should skip fetching when --disable-api-nodes is present', async () => {
|
||||
mockSystemStatsStore.systemStats.system.argv = ['--disable-api-nodes']
|
||||
|
||||
await store.initialize()
|
||||
|
||||
expect(mockReleaseService.getReleases).not.toHaveBeenCalled()
|
||||
expect(store.isLoading).toBe(false)
|
||||
})
|
||||
|
||||
it('should skip fetching when --disable-api-nodes is one of multiple args', async () => {
|
||||
mockSystemStatsStore.systemStats.system.argv = [
|
||||
'--port',
|
||||
'8080',
|
||||
'--disable-api-nodes',
|
||||
'--verbose'
|
||||
]
|
||||
|
||||
await store.initialize()
|
||||
|
||||
expect(mockReleaseService.getReleases).not.toHaveBeenCalled()
|
||||
expect(store.isLoading).toBe(false)
|
||||
})
|
||||
|
||||
it('should fetch normally when --disable-api-nodes is not present', async () => {
|
||||
mockSystemStatsStore.systemStats.system.argv = [
|
||||
'--port',
|
||||
'8080',
|
||||
'--verbose'
|
||||
]
|
||||
mockReleaseService.getReleases.mockResolvedValue([mockRelease])
|
||||
|
||||
await store.initialize()
|
||||
|
||||
expect(mockReleaseService.getReleases).toHaveBeenCalled()
|
||||
expect(store.releases).toEqual([mockRelease])
|
||||
})
|
||||
|
||||
it('should fetch normally when argv is undefined', async () => {
|
||||
mockSystemStatsStore.systemStats.system.argv = undefined
|
||||
mockReleaseService.getReleases.mockResolvedValue([mockRelease])
|
||||
|
||||
await store.initialize()
|
||||
|
||||
expect(mockReleaseService.getReleases).toHaveBeenCalled()
|
||||
expect(store.releases).toEqual([mockRelease])
|
||||
})
|
||||
|
||||
it('should handle API errors gracefully', async () => {
|
||||
mockReleaseService.getReleases.mockResolvedValue(null)
|
||||
mockReleaseService.error.value = 'API Error'
|
||||
@@ -307,6 +354,63 @@ describe('useReleaseStore', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('--disable-api-nodes argument handling', () => {
|
||||
it('should skip fetchReleases when --disable-api-nodes is present', async () => {
|
||||
mockSystemStatsStore.systemStats.system.argv = ['--disable-api-nodes']
|
||||
|
||||
await store.fetchReleases()
|
||||
|
||||
expect(mockReleaseService.getReleases).not.toHaveBeenCalled()
|
||||
expect(store.isLoading).toBe(false)
|
||||
})
|
||||
|
||||
it('should skip fetchReleases when --disable-api-nodes is among other args', async () => {
|
||||
mockSystemStatsStore.systemStats.system.argv = [
|
||||
'--port',
|
||||
'8080',
|
||||
'--disable-api-nodes',
|
||||
'--verbose'
|
||||
]
|
||||
|
||||
await store.fetchReleases()
|
||||
|
||||
expect(mockReleaseService.getReleases).not.toHaveBeenCalled()
|
||||
expect(store.isLoading).toBe(false)
|
||||
})
|
||||
|
||||
it('should proceed with fetchReleases when --disable-api-nodes is not present', async () => {
|
||||
mockSystemStatsStore.systemStats.system.argv = [
|
||||
'--port',
|
||||
'8080',
|
||||
'--verbose'
|
||||
]
|
||||
mockReleaseService.getReleases.mockResolvedValue([mockRelease])
|
||||
|
||||
await store.fetchReleases()
|
||||
|
||||
expect(mockReleaseService.getReleases).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should proceed with fetchReleases when argv is null', async () => {
|
||||
mockSystemStatsStore.systemStats.system.argv = null
|
||||
mockReleaseService.getReleases.mockResolvedValue([mockRelease])
|
||||
|
||||
await store.fetchReleases()
|
||||
|
||||
expect(mockReleaseService.getReleases).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should proceed with fetchReleases when system stats are not available', async () => {
|
||||
mockSystemStatsStore.systemStats = null
|
||||
mockReleaseService.getReleases.mockResolvedValue([mockRelease])
|
||||
|
||||
await store.fetchReleases()
|
||||
|
||||
expect(mockSystemStatsStore.fetchSystemStats).toHaveBeenCalled()
|
||||
expect(mockReleaseService.getReleases).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('action handlers', () => {
|
||||
beforeEach(() => {
|
||||
store.releases = [mockRelease]
|
||||
|
||||
Reference in New Issue
Block a user