Compare commits

..

5 Commits

Author SHA1 Message Date
bymyself
2e80a5789c [test] Fix axios mocking in firebaseAuthStore tests
Use vi.mock() with factory function to ensure axios mock is set up before module imports. This fixes test failures caused by the store creating axios client at module level.

Fixes test execution in CI
2025-08-15 15:37:06 -07:00
bymyself
81ddfc2f58 [fix] Update firebaseAuthStore tests to mock axios client
- Move axios mock setup before store import to ensure client is mocked
- Set up default successful responses for customer API endpoints
- Fix TypeScript issues with isAxiosError mock type
2025-08-15 14:40:29 -07:00
bymyself
e19f0b2da9 [fix] Add missing await for response.json() calls in template workflows 2025-08-15 14:14:03 -07:00
bymyself
afecff6c94 [feat] Complete network call consolidation to use consistent clients
Template Workflows:
- Replace direct fetch with api.fetchApi() for API endpoints
- Keep direct fetch for static file URLs (already using api.fileURL())

Model Exporter:
- Add logic to distinguish ComfyUI URLs from external URLs
- Use api.apiURL() for ComfyUI URLs, direct fetch for external URLs
- Maintain existing download functionality

Other files already following correct patterns:
- Upload Audio: Already using api.fetchApi()
- 3D Loading Utils: Already using api.fetchApi() (fetch call is for blob conversion)
- Download Utility: Uses direct fetch for external URLs (correct)

All network calls now use consistent client patterns where appropriate.
2025-08-15 13:34:37 -07:00
bymyself
5afeee258f [feat] Consolidate firebaseAuthStore network calls to use axios client
- Replace all direct fetch() calls with axios client instance
- Maintain exact same error handling behavior and logic flow
- Use consistent pattern with other services (customerEventsService)
- All customer API endpoints now use centralized client
- Prepares for header registration system implementation

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-15 12:28:52 -07:00
59 changed files with 1116 additions and 2278 deletions

View File

@@ -111,7 +111,50 @@ echo "Last stable release: $LAST_STABLE"
```
7. **HUMAN ANALYSIS**: Review change summary and verify scope
### Step 3: Breaking Change Analysis
### 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
1. Analyze API changes in:
- Public TypeScript interfaces
@@ -126,7 +169,7 @@ echo "Last stable release: $LAST_STABLE"
3. Generate breaking change summary
4. **COMPATIBILITY REVIEW**: Breaking changes documented and justified?
### Step 4: Analyze Dependency Updates
### Step 7: Analyze Dependency Updates
1. **Check significant dependency updates:**
```bash
@@ -152,117 +195,7 @@ echo "Last stable release: $LAST_STABLE"
done
```
### 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
### Step 8: Generate Comprehensive Release Notes
1. Extract commit messages since base release:
```bash
@@ -324,7 +257,7 @@ echo "Last stable release: $LAST_STABLE"
- Ensure consistent bullet format: `- Description (#PR_NUMBER)`
5. **CONTENT REVIEW**: Release notes follow standard format?
### Step 10: Create Version Bump PR
### Step 9: Create Version Bump PR
**For standard version bumps (patch/minor/major):**
```bash
@@ -370,7 +303,7 @@ echo "Workflow triggered. Waiting for PR creation..."
```
4. **PR REVIEW**: Version bump PR created with standardized release notes?
### Step 11: Critical Release PR Verification
### Step 10: Critical Release PR Verification
1. **CRITICAL**: Verify PR has "Release" label:
```bash
@@ -392,7 +325,7 @@ echo "Workflow triggered. Waiting for PR creation..."
```
7. **FINAL CODE REVIEW**: Release label present and no [skip ci]?
### Step 12: Pre-Merge Validation
### Step 11: Pre-Merge Validation
1. **Review Requirements**: Release PRs require approval
2. Monitor CI checks - watch for update-locales
@@ -400,7 +333,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 13: Execute Release
### Step 12: Execute Release
1. **FINAL CONFIRMATION**: Merge PR to trigger release?
2. Merge the Release PR:
@@ -433,7 +366,7 @@ echo "Workflow triggered. Waiting for PR creation..."
gh run watch ${WORKFLOW_RUN_ID}
```
### Step 14: Enhance GitHub Release
### Step 13: Enhance GitHub Release
1. Wait for automatic release creation:
```bash
@@ -461,7 +394,7 @@ echo "Workflow triggered. Waiting for PR creation..."
gh release view v${NEW_VERSION}
```
### Step 15: Verify Multi-Channel Distribution
### Step 14: Verify Multi-Channel Distribution
1. **GitHub Release:**
```bash
@@ -499,7 +432,7 @@ echo "Workflow triggered. Waiting for PR creation..."
4. **DISTRIBUTION VERIFICATION**: All channels published successfully?
### Step 16: Post-Release Monitoring Setup
### Step 15: Post-Release Monitoring Setup
1. **Monitor immediate release health:**
```bash
@@ -575,42 +508,87 @@ echo "Workflow triggered. Waiting for PR creation..."
4. **RELEASE COMPLETION**: All post-release setup completed?
### Step 17: Create Release Summary
### Step 16: Generate GTM Feature Summary
1. **Create comprehensive release summary:**
1. **Extract and analyze PR data:**
```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
echo "📊 Checking for marketing-worthy features..."
# Extract all PR data inline
PR_DATA=$(
PR_LIST=$(git log ${BASE_TAG}..HEAD --grep="Merge pull request" --pretty=format:"%s" | grep -oE "#[0-9]+" | tr -d '#' | sort -u)
echo "["
first=true
for PR in $PR_LIST; do
[[ "$first" == true ]] && first=false || echo ","
gh pr view $PR --json number,title,author,body,files,labels,closedAt 2>/dev/null || continue
done
echo "]"
)
# Save for analysis
echo "$PR_DATA" > prs-${NEW_VERSION}.json
```
2. **RELEASE COMPLETION**: All steps completed successfully?
2. **Analyze for GTM-worthy features:**
```
<task>
Review these PRs to identify if ANY would interest a marketing/growth team.
Consider if a PR:
- Changes something users directly interact with or experience
- Makes something noticeably better, faster, or easier
- Introduces capabilities users have been asking for
- Has visual assets (screenshots, GIFs, videos) that could be shared
- Tells a compelling story about improvement or innovation
- Would make users excited if they heard about it
Many releases contain only technical improvements, bug fixes, or internal changes -
that's perfectly normal. Only flag PRs that would genuinely interest end users.
If you find marketing-worthy PRs, note:
- PR number, title, and author
- Any media links from the description
- One sentence on why it's worth showcasing
If nothing is marketing-worthy, just say "No marketing-worthy features in this release."
</task>
PR data: [contents of prs-${NEW_VERSION}.json]
```
3. **Generate GTM notification (only if needed):**
```
If there are marketing-worthy features, create a message for #gtm with:
🚀 Frontend Release v${NEW_VERSION}
Timeline: Available now in nightly, ~2-3 weeks for core
Features worth showcasing:
[List the selected PRs with media links and authors]
Testing: --front-end-version ${NEW_VERSION}
If there are NO marketing-worthy features, generate:
"No marketing-worthy features in v${NEW_VERSION} - mostly internal improvements and bug fixes."
```
4. **Save the output:**
```bash
# Claude generates the GTM summary and saves it
# Save to gtm-summary-${NEW_VERSION}.md
# 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
```
## Advanced Safety Features

View File

@@ -13,10 +13,6 @@ 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.
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.
Note: For Traditional Chinese (Taiwan), use Taiwan-specific terminology and traditional characters.
`
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 67 KiB

View File

@@ -73,77 +73,6 @@ 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('File')

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@comfyorg/comfyui-frontend",
"version": "1.26.4",
"version": "1.26.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@comfyorg/comfyui-frontend",
"version": "1.26.4",
"version": "1.26.3",
"license": "GPL-3.0-only",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",

View File

@@ -1,7 +1,7 @@
{
"name": "@comfyorg/comfyui-frontend",
"private": true,
"version": "1.26.4",
"version": "1.26.3",
"type": "module",
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
"homepage": "https://comfy.org",

View File

@@ -20,7 +20,7 @@
>
<div class="shortcut-info flex-grow pr-4">
<div class="shortcut-name text-sm font-medium">
{{ t(`commands.${normalizeI18nKey(command.id)}.label`) }}
{{ command.label || command.id }}
</div>
</div>
@@ -50,7 +50,6 @@ import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import type { ComfyCommandImpl } from '@/stores/commandStore'
import { normalizeI18nKey } from '@/utils/formatUtil'
const { t } = useI18n()

View File

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

View File

@@ -31,7 +31,7 @@
</div>
</template>
</ListBox>
<div v-if="!isLegacyManager" class="flex justify-end py-3">
<div v-if="isManagerInstalled" class="flex justify-end py-3">
<PackInstallButton
:disabled="isLoading || !!error || missingNodePacks.length === 0"
:node-packs="missingNodePacks"
@@ -45,12 +45,14 @@
<script setup lang="ts">
import Button from 'primevue/button'
import ListBox from 'primevue/listbox'
import { computed, onMounted, ref } from 'vue'
import { computed } 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'
@@ -58,11 +60,22 @@ 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()
const isLegacyManager = ref(false)
// 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 uniqueNodes = computed(() => {
const seenTypes = new Set()
@@ -90,13 +103,6 @@ const openManager = () => {
initialTab: ManagerTab.Missing
})
}
onMounted(async () => {
const isLegacyResponse = await useComfyManagerService().isLegacyManagerUI()
if (isLegacyResponse?.is_legacy_manager_ui) {
isLegacyManager.value = true
}
})
</script>
<style scoped>

View File

@@ -1,82 +0,0 @@
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)
})
})

View File

@@ -4,22 +4,6 @@
<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>

View File

@@ -10,6 +10,7 @@
:loading="isInstalling"
:loading-message="$t('g.installing')"
@action="installAllPacks"
@click="onClick"
/>
</template>
@@ -36,6 +37,10 @@ const { nodePacks, variant, label } = defineProps<{
const isInstalling = inject(IsInstallingKey, ref(false))
const onClick = (): void => {
isInstalling.value = true
}
const managerStore = useComfyManagerStore()
const createPayload = (installItem: NodePack) => {
@@ -60,6 +65,8 @@ const installPack = (item: NodePack) =>
const installAllPacks = async () => {
if (!nodePacks?.length) return
isInstalling.value = true
const uninstalledPacks = nodePacks.filter(
(pack) => !managerStore.isPackInstalled(pack.id)
)

View File

@@ -84,9 +84,10 @@
</template>
<script setup lang="ts">
import { whenever } from '@vueuse/core'
import Card from 'primevue/card'
import ProgressSpinner from 'primevue/progressspinner'
import { computed, provide } from 'vue'
import { computed, provide, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import PackVersionBadge from '@/components/dialog/content/manager/PackVersionBadge.vue'
@@ -113,17 +114,18 @@ const isLightTheme = computed(
() => colorPaletteStore.completedActivePalette.light_theme
)
const { isPackInstalled, isPackEnabled, isPackInstalling } =
useComfyManagerStore()
const isInstalling = computed(() => isPackInstalling(nodePack?.id))
const isInstalling = ref(false)
provide(IsInstallingKey, isInstalling)
const { isPackInstalled, isPackEnabled } = useComfyManagerStore()
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
)

View File

@@ -1,47 +1,40 @@
<template>
<div
class="w-full px-6 py-2 shadow-lg flex items-center justify-between"
class="w-full px-6 py-4 shadow-lg flex items-center justify-between"
:class="{
'rounded-t-none': progressDialogContent.isExpanded,
'rounded-lg': !progressDialogContent.isExpanded
}"
>
<div class="flex items-center text-base leading-none">
<div class="justify-center text-sm font-bold leading-none">
<div class="flex items-center">
<!-- 1. Queue running (install/enable/disable etc.) -->
<template v-if="isQueueRunning">
<DotSpinner duration="1s" class="mr-2" />
<template v-if="isInProgress">
<i class="pi pi-spin pi-spinner mr-2 text-3xl" />
<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>
<span class="mr-2"></span>
<span>
{{ $t('manager.clickToFinishSetup') }}
'{{ $t('manager.applyChanges') }}'
{{ $t('manager.toFinishSetup') }}
</span>
<i class="pi pi-check-circle mr-2 text-green-500" />
<span class="leading-none">{{
$t('manager.restartToApplyChanges')
}}</span>
</template>
</div>
</div>
<div class="flex items-center gap-4">
<!-- 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>
<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>
<Button
:icon="
progressDialogContent.isExpanded
@@ -51,46 +44,20 @@
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
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"
/>
size="small"
severity="secondary"
aria-label="Close"
@click.stop="closeDialog"
/>
</div>
</div>
</div>
</template>
@@ -98,10 +65,9 @@
<script setup lang="ts">
import { useEventListener } from '@vueuse/core'
import Button from 'primevue/button'
import { computed, ref } from 'vue'
import { computed } 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'
@@ -111,34 +77,19 @@ 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()
// 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 isInProgress = computed(() => comfyManagerStore.uncompletedCount > 0)
const closeDialog = () => {
dialogStore.closeDialog({ key: 'global-manager-progress-dialog' })
}
const fallbackTaskName = t('manager.installingDependencies')
const fallbackTaskName = t('g.installing')
const currentTaskName = computed(() => {
if (!comfyManagerStore.taskLogs.length) return fallbackTaskName
const task = comfyManagerStore.taskLogs.at(-1)
@@ -146,52 +97,21 @@ const currentTaskName = computed(() => {
})
const handleRestart = async () => {
// Store original toast setting value
const originalToastSetting = settingStore.get(
'Comfy.Toast.DisableReconnectingToast'
)
const onReconnect = async () => {
// Refresh manager state
try {
await settingStore.set('Comfy.Toast.DisableReconnectingToast', true)
comfyManagerStore.clearLogs()
comfyManagerStore.setStale()
isRestarting.value = true
// Refresh node definitions
await useCommandStore().execute('Comfy.RefreshNodeDefinitions')
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
// Reload workflow
await useWorkflowService().reloadCurrentWorkflow()
}
useEventListener(api, 'reconnected', onReconnect, { once: true })
await useComfyManagerService().rebootComfyUI()
closeDialog()
}
</script>

View File

@@ -90,16 +90,18 @@ 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 && triggerEvent) {
if (disconnectOnReset) {
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

View File

@@ -59,7 +59,7 @@
@mousedown="
isZoomCommand(item) ? handleZoomMouseDown(item, $event) : undefined
"
@click="handleItemClick(item, $event)"
@click="isZoomCommand(item) ? handleZoomClick($event) : undefined"
>
<i
v-if="hasActiveStateSiblings(item)"
@@ -285,19 +285,11 @@ const handleZoomMouseDown = (item: MenuItem, event: MouseEvent) => {
}
}
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 handleZoomClick = (event: MouseEvent) => {
event.preventDefault()
event.stopPropagation()
// Prevent the menu from closing for zoom commands
return false
}
const hasActiveStateSiblings = (item: MenuItem): boolean => {

View File

@@ -1362,27 +1362,9 @@ 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'
}
}

View File

@@ -15,12 +15,10 @@ 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'
@@ -34,7 +32,6 @@ 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
@@ -713,54 +710,12 @@ export function useCoreCommands(): ComfyCommand[] {
}
},
{
id: 'Comfy.Manager.CustomNodesManager.ShowCustomNodesMenu',
icon: 'pi pi-objects-column',
label: 'Custom Nodes Manager',
id: 'Comfy.Manager.CustomNodesManager',
icon: 'pi pi-puzzle',
label: 'Toggle the 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.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
})
dialogService.toggleManagerDialog()
}
},
{
@@ -923,84 +878,6 @@ 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 })
}
}
]

View File

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

View File

@@ -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,7 +27,6 @@ export type MinimapOptionKey =
export function useMinimap() {
const settingStore = useSettingStore()
const canvasStore = useCanvasStore()
const workflowStore = useWorkflowStore()
const colorPaletteStore = useColorPaletteStore()
const containerRef = ref<HTMLDivElement>()
@@ -148,11 +147,7 @@ export function useMinimap() {
}
const canvas = computed(() => canvasStore.canvas)
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 graph = ref(app.canvas?.graph)
const containerStyles = computed(() => ({
width: `${width}px`,
@@ -632,8 +627,7 @@ export function useMinimap() {
c.setDirty(true, true)
}
// Map to store original callbacks per graph ID
const originalCallbacksMap = new Map<string, GraphCallbacks>()
let originalCallbacks: GraphCallbacks = {}
const handleGraphChanged = useThrottleFn(() => {
needsFullRedraw.value = true
@@ -647,18 +641,11 @@ export function useMinimap() {
const g = graph.value
if (!g) return
// 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 = {
originalCallbacks = {
onNodeAdded: g.onNodeAdded,
onNodeRemoved: g.onNodeRemoved,
onConnectionChange: g.onConnectionChange
}
originalCallbacksMap.set(g.id, originalCallbacks)
g.onNodeAdded = function (node) {
originalCallbacks.onNodeAdded?.call(this, node)
@@ -683,19 +670,15 @@ export function useMinimap() {
const g = graph.value
if (!g) return
const originalCallbacks = originalCallbacksMap.get(g.id)
if (!originalCallbacks) {
console.error(
'Attempted to cleanup event listeners for graph that was never set up'
)
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
}
g.onNodeAdded = originalCallbacks.onNodeAdded
g.onNodeRemoved = originalCallbacks.onNodeRemoved
g.onConnectionChange = originalCallbacks.onConnectionChange
originalCallbacksMap.delete(g.id)
}
const init = async () => {
@@ -768,19 +751,6 @@ 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) {

View File

@@ -160,12 +160,15 @@ export function useTemplateWorkflows() {
*/
const fetchTemplateJson = async (id: string, sourceModule: string) => {
if (sourceModule === 'default') {
// Default templates provided by frontend are served on this separate endpoint
return fetch(api.fileURL(`/templates/${id}.json`)).then((r) => r.json())
// Default templates provided by frontend are served as static files
const response = await fetch(api.fileURL(`/templates/${id}.json`))
return await response.json()
} else {
return fetch(
api.apiURL(`/workflow_templates/${sourceModule}/${id}.json`)
).then((r) => r.json())
// Custom node templates served via API
const response = await api.fetchApi(
`/workflow_templates/${sourceModule}/${id}.json`
)
return await response.json()
}
}

View File

@@ -13,14 +13,6 @@ export const CORE_MENU_COMMANDS = [
],
[['Edit'], ['Comfy.Undo', 'Comfy.Redo']],
[['Edit'], ['Comfy.OpenClipspace']],
[
['Manager'],
[
'Comfy.Manager.CustomNodesManager.ShowCustomNodesMenu',
'Comfy.Manager.ShowMissingPacks',
'Comfy.Manager.ShowUpdateAvailablePacks'
]
],
[
['Help'],
[

View File

@@ -13,13 +13,6 @@ 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',
@@ -779,8 +772,7 @@ export const CORE_SETTINGS: SettingParams[] = [
{
id: 'LiteGraph.Canvas.LowQualityRenderingZoomThreshold',
name: 'Low quality rendering zoom threshold',
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.',
tooltip: 'Render low quality shapes when zoomed out',
type: 'slider',
attrs: {
min: 0.1,

View File

@@ -4,6 +4,7 @@ import { OBJExporter } from 'three/examples/jsm/exporters/OBJExporter'
import { STLExporter } from 'three/examples/jsm/exporters/STLExporter'
import { t } from '@/i18n'
import { api } from '@/scripts/api'
import { useToastStore } from '@/stores/toastStore'
export class ModelExporter {
@@ -36,7 +37,18 @@ export class ModelExporter {
desiredFilename: string
): Promise<void> {
try {
const response = await fetch(url)
// Check if this is a ComfyUI relative URL
const isComfyUrl = url.startsWith('/') || url.includes('/view?')
let response: Response
if (isComfyUrl) {
// Use ComfyUI API client for internal URLs
response = await fetch(api.apiURL(url))
} else {
// Use direct fetch for external URLs
response = await fetch(url)
}
const blob = await response.blob()
const link = document.createElement('a')

View File

@@ -3608,7 +3608,6 @@ export class LGraphCanvas
subgraphs: []
}
// NOTE: logic for traversing nested subgraphs depends on this being a set.
const subgraphs = new Set<Subgraph>()
// Create serialisable objects
@@ -3647,13 +3646,8 @@ export class LGraphCanvas
}
// Add unique subgraph entries
// NOTE: subgraphs is appended to mid iteration.
// TODO: Must find all nested subgraphs
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)
}
@@ -3770,19 +3764,12 @@ 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)

View File

@@ -414,18 +414,6 @@ 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)

View File

@@ -16,10 +16,7 @@ import type {
GraphOrSubgraph,
Subgraph
} from '@/lib/litegraph/src/subgraph/Subgraph'
import type {
ExportedSubgraphInstance,
ISerialisedNode
} from '@/lib/litegraph/src/types/serialisation'
import type { ExportedSubgraphInstance } 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'
@@ -543,36 +540,4 @@ 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()
}
}

View File

@@ -1,6 +1,6 @@
import { describe, expect, it, vi } from 'vitest'
import { describe, expect } from 'vitest'
import { LGraph, LGraphNode, LLink } from '@/lib/litegraph/src/litegraph'
import { LLink } from '@/lib/litegraph/src/litegraph'
import { test } from './testExtensions'
@@ -14,84 +14,4 @@ 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)
})
})
})

View File

@@ -164,20 +164,8 @@
"Comfy_LoadDefaultWorkflow": {
"label": "Load Default Workflow"
},
"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_CustomNodesManager": {
"label": "Toggle the Custom Nodes Manager"
},
"Comfy_Manager_ToggleManagerProgressDialog": {
"label": "Toggle the Custom Nodes Manager Progress Bar"
@@ -191,12 +179,6 @@
"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"
},

View File

@@ -25,6 +25,7 @@
"confirmed": "Confirmed",
"reset": "Reset",
"resetAll": "Reset All",
"clearFilters": "Clear Filters",
"resetAllKeybindingsTooltip": "Reset all keybindings to default",
"customizeFolder": "Customize Folder",
"icon": "Icon",
@@ -98,6 +99,12 @@
"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",
@@ -134,18 +141,15 @@
"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"
"nodesRunning": "nodes running",
"duplicate": "Duplicate"
},
"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.",
@@ -155,12 +159,6 @@
"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",
@@ -434,12 +432,19 @@
"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",
@@ -535,7 +540,14 @@
"clipspace": "Open Clipspace",
"resetView": "Reset canvas view",
"clear": "Clear workflow",
"toggleBottomPanel": "Toggle Bottom Panel"
"toggleBottomPanel": "Toggle Bottom Panel",
"theme": "Theme",
"dark": "Dark",
"light": "Light",
"manageExtensions": "Manage Extensions",
"settings": "Settings",
"help": "Help",
"queue": "Queue Panel"
},
"tabMenu": {
"duplicateTab": "Duplicate Tab",
@@ -548,6 +560,8 @@
},
"templateWorkflows": {
"title": "Get Started with a Template",
"loadingMore": "Loading more templates...",
"searchPlaceholder": "Search templates...",
"category": {
"ComfyUI Examples": "ComfyUI Examples",
"Custom Nodes": "Custom Nodes",
@@ -877,7 +891,8 @@
"fitView": "Fit View",
"selectMode": "Select Mode",
"panMode": "Pan Mode",
"toggleLinkVisibility": "Toggle Link Visibility"
"toggleLinkVisibility": "Toggle Link Visibility",
"toggleMinimap": "Toggle Minimap"
},
"groupNode": {
"create": "Create group node",
@@ -926,9 +941,8 @@
"Image Layer": "Image Layer"
},
"menuLabels": {
"Workflow": "Workflow",
"File": "File",
"Edit": "Edit",
"Manager": "Manager",
"Help": "Help",
"Check for Updates": "Check for Updates",
"Open Custom Nodes Folder": "Open Custom Nodes Folder",
@@ -942,18 +956,20 @@
"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",
"Fit view to selected nodes": "Fit view to selected nodes",
"Zoom to fit": "Zoom to fit",
"Move Selected Nodes Down": "Move Selected Nodes Down",
"Move Selected Nodes Left": "Move Selected Nodes Left",
"Move Selected Nodes Right": "Move Selected Nodes Right",
"Move Selected Nodes Up": "Move Selected Nodes Up",
"Reset View": "Reset View",
"Resize Selected Nodes": "Resize Selected Nodes",
"Canvas Toggle Link Visibility": "Canvas Toggle Link Visibility",
"Node Links": "Node Links",
"Canvas Toggle Lock": "Canvas Toggle Lock",
"Minimap": "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",
@@ -969,8 +985,10 @@
"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",
@@ -981,17 +999,14 @@
"ComfyUI Issues": "ComfyUI Issues",
"Interrupt": "Interrupt",
"Load Default Workflow": "Load Default Workflow",
"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": "Toggle the Custom Nodes Manager",
"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",
"Manager": "Manager",
"Open": "Open",
"Queue Prompt": "Queue Prompt",
"Queue Prompt (Front)": "Queue Prompt (Front)",
@@ -1001,6 +1016,8 @@
"Save": "Save",
"Save As": "Save As",
"Show Settings Dialog": "Show Settings Dialog",
"Canvas Performance": "Canvas Performance",
"Help Center": "Help Center",
"Toggle Theme (Dark/Light)": "Toggle Theme (Dark/Light)",
"Undo": "Undo",
"Open Sign In Dialog": "Open Sign In Dialog",
@@ -1009,14 +1026,17 @@
"Next Opened Workflow": "Next Opened Workflow",
"Previous Opened Workflow": "Previous Opened Workflow",
"Toggle Search Box": "Toggle Search Box",
"Toggle Bottom Panel": "Toggle Bottom Panel",
"Bottom Panel": "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 Focus Mode": "Toggle Focus Mode",
"Toggle Model Library Sidebar": "Toggle Model Library Sidebar",
"Toggle Node Library Sidebar": "Toggle Node Library Sidebar",
"Toggle Queue Sidebar": "Toggle Queue Sidebar",
"Toggle Workflows Sidebar": "Toggle Workflows Sidebar"
"Toggle Essential Bottom Panel": "Toggle Essential Bottom Panel",
"Toggle View Controls Bottom Panel": "Toggle View Controls Bottom Panel",
"Focus Mode": "Focus Mode",
"Model Library": "Model Library",
"Node Library": "Node Library",
"Queue Panel": "Queue Panel",
"Workflows": "Workflows"
},
"desktopMenu": {
"reinstall": "Reinstall",
@@ -1075,7 +1095,8 @@
"User": "User",
"Credits": "Credits",
"API Nodes": "API Nodes",
"Notification Preferences": "Notification Preferences"
"Notification Preferences": "Notification Preferences",
"3DViewer": "3DViewer"
},
"serverConfigItems": {
"listen": {
@@ -1359,6 +1380,13 @@
"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",
@@ -1420,12 +1448,31 @@
"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"
"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"
},
"toastMessages": {
"nothingToQueue": "Nothing to queue",
@@ -1463,7 +1510,8 @@
"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"
"failedToConvertToSubgraph": "Failed to convert items to subgraph",
"failedToInitializeLoad3dViewer": "Failed to initialize 3D Viewer"
},
"auth": {
"apiKey": {
@@ -1616,5 +1664,32 @@
"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"
}
}

View File

@@ -390,7 +390,7 @@
},
"LiteGraph_Canvas_LowQualityRenderingZoomThreshold": {
"name": "Low quality rendering zoom threshold",
"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."
"tooltip": "Render low quality shapes when zoomed out"
},
"LiteGraph_Canvas_MaximumFps": {
"name": "Maximum FPS",

View File

@@ -164,20 +164,8 @@
"Comfy_LoadDefaultWorkflow": {
"label": "Cargar flujo de trabajo predeterminado"
},
"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_CustomNodesManager": {
"label": "Administrador de nodos personalizados"
},
"Comfy_Manager_ToggleManagerProgressDialog": {
"label": "Alternar diálogo de progreso del administrador"
@@ -191,12 +179,6 @@
"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"
},

View File

@@ -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,6 +299,7 @@
"disabling": "Deshabilitando",
"dismiss": "Descartar",
"download": "Descargar",
"duplicate": "Duplicar",
"edit": "Editar",
"empty": "Vacío",
"enableAll": "Habilitar todo",
@@ -569,6 +570,10 @@
"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",
@@ -589,6 +594,7 @@
"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",
@@ -599,8 +605,22 @@
"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"
"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)"
}
},
"loadWorkflowWarning": {
"coreNodesFromVersion": "Requiere ComfyUI {version}:",
@@ -647,9 +667,6 @@
"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",
@@ -732,6 +749,7 @@
"manageExtensions": "Gestionar extensiones",
"onChange": "Al cambiar",
"onChangeTooltip": "El flujo de trabajo se encolará una vez que se haga un cambio",
"queue": "Panel de cola",
"refresh": "Actualizar definiciones de nodos",
"resetView": "Restablecer vista del lienzo",
"run": "Ejecutar",
@@ -745,12 +763,11 @@
"menuLabels": {
"About ComfyUI": "Acerca de ComfyUI",
"Add Edit Model Step": "Agregar paso de edición de modelo",
"Bottom Panel": "Panel inferior",
"Browse Templates": "Explorar plantillas",
"Bypass/Unbypass Selected Nodes": "Evitar/No evitar nodos seleccionados",
"Canvas Toggle Link Visibility": "Alternar visibilidad de enlace en lienzo",
"Canvas Performance": "Rendimiento del 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",
@@ -764,27 +781,28 @@
"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)",
"File": "Archivo",
"Fit Group To Contents": "Ajustar grupo a contenidos",
"Fit view to selected nodes": "Ajustar vista a los nodos seleccionados",
"Focus Mode": "Modo de enfoque",
"Give Feedback": "Dar retroalimentación",
"Group Selected Nodes": "Agrupar nodos seleccionados",
"Help": "Ayuda",
"Help Center": "Centro de 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)",
"Minimap": "Minimapa",
"Model Library": "Biblioteca de modelos",
"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",
@@ -792,7 +810,10 @@
"Mute/Unmute Selected Nodes": "Silenciar/Activar sonido de nodos seleccionados",
"New": "Nuevo",
"Next Opened Workflow": "Siguiente flujo de trabajo abierto",
"Node Library": "Biblioteca de nodos",
"Node Links": "Enlaces de nodos",
"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",
@@ -805,6 +826,7 @@
"Pin/Unpin Selected Items": "Anclar/Desanclar elementos seleccionados",
"Pin/Unpin Selected Nodes": "Anclar/Desanclar nodos seleccionados",
"Previous Opened Workflow": "Flujo de trabajo abierto anterior",
"Queue Panel": "Panel de cola",
"Queue Prompt": "Indicador de cola",
"Queue Prompt (Front)": "Indicador de cola (Frente)",
"Queue Selected Output Nodes": "Encolar nodos de salida seleccionados",
@@ -817,26 +839,31 @@
"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 Focus Mode": "Alternar modo de enfoque",
"Toggle Essential Bottom Panel": "Alternar panel inferior esencial",
"Toggle Logs Bottom Panel": "Alternar panel inferior de registros",
"Toggle Model Library Sidebar": "Alternar barra lateral de la biblioteca de modelos",
"Toggle Node Library Sidebar": "Alternar barra lateral de la biblioteca de nodos",
"Toggle Queue Sidebar": "Alternar barra lateral de la cola",
"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 Workflows Sidebar": "Alternar barra lateral de los flujos de trabajo",
"Toggle View Controls Bottom Panel": "Alternar panel inferior de controles de vista",
"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",
"Unload Models": "Descargar modelos",
"Unload Models and Execution Cache": "Descargar modelos y caché de ejecución",
"Workflow": "Flujo de trabajo",
"Unpack the selected Subgraph": "Desempaquetar el Subgrafo seleccionado",
"Workflows": "Flujos de trabajo",
"Zoom In": "Acercar",
"Zoom Out": "Alejar"
"Zoom Out": "Alejar",
"Zoom to fit": "Ajustar al tamaño"
},
"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",
@@ -1104,6 +1131,7 @@
},
"settingsCategories": {
"3D": "3D",
"3DViewer": "Visor 3D",
"API Nodes": "Nodos API",
"About": "Acerca de",
"Appearance": "Apariencia",
@@ -1155,10 +1183,31 @@
"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",
@@ -1196,6 +1245,7 @@
},
"showFlatList": "Mostrar lista plana"
},
"templates": "Plantillas",
"workflowTab": {
"confirmDelete": "¿Estás seguro de que quieres eliminar este flujo de trabajo?",
"confirmDeleteTitle": "¿Eliminar flujo de trabajo?",
@@ -1242,6 +1292,8 @@
"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",
@@ -1564,6 +1616,7 @@
"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}",

View File

@@ -164,20 +164,8 @@
"Comfy_LoadDefaultWorkflow": {
"label": "Charger le flux de travail par défaut"
},
"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_CustomNodesManager": {
"label": "Gestionnaire de Nœuds Personnalisés"
},
"Comfy_Manager_ToggleManagerProgressDialog": {
"label": "Basculer la boîte de dialogue de progression"
@@ -191,12 +179,6 @@
"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"
},

View File

@@ -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,6 +299,7 @@
"disabling": "Désactivation",
"dismiss": "Fermer",
"download": "Télécharger",
"duplicate": "Dupliquer",
"edit": "Modifier",
"empty": "Vide",
"enableAll": "Activer tout",
@@ -569,6 +570,10 @@
"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",
@@ -589,6 +594,7 @@
"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",
@@ -599,8 +605,22 @@
"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"
"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 dexportation",
"lightSettings": "Paramètres de léclairage",
"modelSettings": "Paramètres du modèle",
"sceneSettings": "Paramètres de la scène",
"title": "Visionneuse 3D (Bêta)"
}
},
"loadWorkflowWarning": {
"coreNodesFromVersion": "Nécessite ComfyUI {version} :",
@@ -647,9 +667,6 @@
"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",
@@ -732,6 +749,7 @@
"manageExtensions": "Gérer les extensions",
"onChange": "Sur modification",
"onChangeTooltip": "Le flux de travail sera mis en file d'attente une fois une modification effectuée",
"queue": "Panneau de file dattente",
"refresh": "Actualiser les définitions des nœuds",
"resetView": "Réinitialiser la vue du canevas",
"run": "Exécuter",
@@ -745,12 +763,11 @@
"menuLabels": {
"About ComfyUI": "À propos de ComfyUI",
"Add Edit Model Step": "Ajouter une étape dédition de modèle",
"Bottom Panel": "Panneau inférieur",
"Browse Templates": "Parcourir les modèles",
"Bypass/Unbypass Selected Nodes": "Contourner/Ne pas contourner les nœuds sélectionnés",
"Canvas Toggle Link Visibility": "Basculer la visibilité du lien de la toile",
"Canvas Performance": "Performance du canevas",
"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",
@@ -764,27 +781,28 @@
"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)",
"File": "Fichier",
"Fit Group To Contents": "Ajuster le groupe au contenu",
"Fit view to selected nodes": "Ajuster la vue aux nœuds sélectionnés",
"Focus Mode": "Mode focus",
"Give Feedback": "Donnez votre avis",
"Group Selected Nodes": "Grouper les nœuds sélectionnés",
"Help": "Aide",
"Help Center": "Centre daide",
"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)",
"Minimap": "Minicarte",
"Model Library": "Bibliothèque de modèles",
"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",
@@ -792,7 +810,10 @@
"Mute/Unmute Selected Nodes": "Mettre en sourdine/Activer le son des nœuds sélectionnés",
"New": "Nouveau",
"Next Opened Workflow": "Prochain flux de travail ouvert",
"Node Library": "Bibliothèque de nœuds",
"Node Links": "Liens de nœuds",
"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",
@@ -805,6 +826,7 @@
"Pin/Unpin Selected Items": "Épingler/Désépingler les éléments sélectionnés",
"Pin/Unpin Selected Nodes": "Épingler/Désépingler les nœuds sélectionnés",
"Previous Opened Workflow": "Flux de travail ouvert précédent",
"Queue Panel": "Panneau de file dattente",
"Queue Prompt": "Invite de file d'attente",
"Queue Prompt (Front)": "Invite de file d'attente (Front)",
"Queue Selected Output Nodes": "Mettre en file dattente les nœuds de sortie sélectionnés",
@@ -817,26 +839,31 @@
"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 Focus Mode": "Basculer le mode focus",
"Toggle Essential Bottom Panel": "Afficher/Masquer le panneau inférieur essentiel",
"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",
"Toggle Node Library Sidebar": "Afficher/Masquer la barre latérale de la bibliothèque de nœuds",
"Toggle Queue Sidebar": "Afficher/Masquer la barre latérale de la file dattente",
"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 Workflows Sidebar": "Afficher/Masquer la barre latérale des workflows",
"Toggle View Controls Bottom Panel": "Afficher/Masquer le panneau inférieur des contrôles de vue",
"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",
"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",
"Unpack the selected Subgraph": "Décompresser le Subgraph sélectionné",
"Workflows": "Flux de travail",
"Zoom In": "Zoom avant",
"Zoom Out": "Zoom arrière"
"Zoom Out": "Zoom arrière",
"Zoom to fit": "Ajuster à lécran"
},
"minimap": {
"nodeColors": "Couleurs des nœuds",
"renderBypassState": "Afficher létat de contournement",
"renderErrorState": "Afficher létat derreur",
"showGroups": "Afficher les cadres/groupes",
"showLinks": "Afficher les liens"
},
"missingModelsDialog": {
"doNotAskAgain": "Ne plus afficher ce message",
@@ -1104,6 +1131,7 @@
},
"settingsCategories": {
"3D": "3D",
"3DViewer": "Visionneuse 3D",
"API Nodes": "Nœuds API",
"About": "À Propos",
"Appearance": "Apparence",
@@ -1155,10 +1183,31 @@
"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 dattente",
"view": "Vue",
"workflow": "Flux de travail"
},
"viewControls": "Contrôles daffichage"
},
"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 dattente",
"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",
@@ -1196,6 +1245,7 @@
},
"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 ?",
@@ -1242,6 +1292,8 @@
"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",
@@ -1564,6 +1616,7 @@
"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}",

View File

@@ -164,20 +164,8 @@
"Comfy_LoadDefaultWorkflow": {
"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_CustomNodesManager": {
"label": "カスタムノードマネージャ"
},
"Comfy_Manager_ToggleManagerProgressDialog": {
"label": "プログレスダイアログの切り替え"
@@ -191,12 +179,6 @@
"Comfy_MaskEditor_OpenMaskEditor": {
"label": "選択したノードのマスクエディタを開く"
},
"Comfy_Memory_UnloadModels": {
"label": "モデルのアンロード"
},
"Comfy_Memory_UnloadModelsAndExecutionCache": {
"label": "モデルと実行キャッシュのアンロード"
},
"Comfy_NewBlankWorkflow": {
"label": "新しい空のワークフロー"
},

View File

@@ -272,11 +272,11 @@
"category": "カテゴリ",
"choose_file_to_upload": "アップロードするファイルを選択",
"clear": "クリア",
"clearFilters": "フィルターをクリア",
"close": "閉じる",
"color": "色",
"comingSoon": "近日公開",
"command": "コマンド",
"commandProhibited": "コマンド {command} は禁止されています。詳細は管理者にお問い合わせください。",
"community": "コミュニティ",
"completed": "完了",
"confirm": "確認",
@@ -299,6 +299,7 @@
"disabling": "無効化",
"dismiss": "閉じる",
"download": "ダウンロード",
"duplicate": "複製",
"edit": "編集",
"empty": "空",
"enableAll": "すべて有効にする",
@@ -569,6 +570,10 @@
"applyingTexture": "テクスチャを適用中...",
"backgroundColor": "背景色",
"camera": "カメラ",
"cameraType": {
"orthographic": "オルソグラフィック",
"perspective": "パースペクティブ"
},
"clearRecording": "録画をクリア",
"edgeThreshold": "エッジ閾値",
"export": "エクスポート",
@@ -589,6 +594,7 @@
"wireframe": "ワイヤーフレーム"
},
"model": "モデル",
"openIn3DViewer": "3Dビューアで開く",
"previewOutput": "出力のプレビュー",
"removeBackgroundImage": "背景画像を削除",
"resizeNodeMatchOutput": "ノードを出力に合わせてリサイズ",
@@ -599,8 +605,22 @@
"switchCamera": "カメラを切り替える",
"switchingMaterialMode": "マテリアルモードの切り替え中...",
"upDirection": "上方向",
"upDirections": {
"original": "オリジナル"
},
"uploadBackgroundImage": "背景画像をアップロード",
"uploadTexture": "テクスチャをアップロード"
"uploadTexture": "テクスチャをアップロード",
"viewer": {
"apply": "適用",
"cameraSettings": "カメラ設定",
"cameraType": "カメラタイプ",
"cancel": "キャンセル",
"exportSettings": "エクスポート設定",
"lightSettings": "ライト設定",
"modelSettings": "モデル設定",
"sceneSettings": "シーン設定",
"title": "3Dビューアベータ"
}
},
"loadWorkflowWarning": {
"coreNodesFromVersion": "ComfyUI {version} が必要です:",
@@ -647,9 +667,6 @@
"installationQueue": "インストールキュー",
"lastUpdated": "最終更新日",
"latestVersion": "最新",
"legacyManagerUI": "レガシーUIを使用する",
"legacyManagerUIDescription": "レガシーManager UIを使用するには、--enable-manager-legacy-uiを付けてComfyUIを起動してください",
"legacyMenuNotAvailable": "このバージョンのComfyUIでは、レガシーマネージャーメニューは利用できません。新しいマネージャーメニューを使用してください。",
"license": "ライセンス",
"loadingVersions": "バージョンを読み込んでいます...",
"nightlyVersion": "ナイトリー",
@@ -732,6 +749,7 @@
"manageExtensions": "拡張機能の管理",
"onChange": "変更時",
"onChangeTooltip": "変更が行われるとワークフローがキューに追加されます",
"queue": "キューパネル",
"refresh": "ノードを更新",
"resetView": "ビューをリセット",
"run": "実行する",
@@ -745,12 +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 Custom Node Updates": "カスタムノードのアップデートを確認",
"Check for Updates": "更新を確認する",
"Clear Pending Tasks": "保留中のタスクをクリア",
"Clear Workflow": "ワークフローをクリア",
@@ -764,27 +781,28 @@
"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)",
"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": "マスクエディタでブラシサイズを大きくする",
"Install Missing Custom Nodes": "不足しているカスタムノードをインストール",
"Interrupt": "中断",
"Load Default Workflow": "デフォルトワークフローを読み込む",
"Manage group nodes": "グループノードを管理",
"Manager": "マネージャー",
"Manager Menu (Legacy)": "マネージャーメニュー(レガシー)",
"Minimap": "ミニマップ",
"Model Library": "モデルライブラリ",
"Move Selected Nodes Down": "選択したノードを下へ移動",
"Move Selected Nodes Left": "選択したノードを左へ移動",
"Move Selected Nodes Right": "選択したノードを右へ移動",
@@ -792,7 +810,10 @@
"Mute/Unmute Selected Nodes": "選択したノードのミュート/ミュート解除",
"New": "新規",
"Next Opened Workflow": "次に開いたワークフロー",
"Node Library": "ノードライブラリ",
"Node Links": "ノードリンク",
"Open": "開く",
"Open 3D Viewer (Beta) for Selected Node": "選択したードの3Dビューアーベータを開く",
"Open Custom Nodes Folder": "カスタムノードフォルダを開く",
"Open DevTools": "DevToolsを開く",
"Open Inputs Folder": "入力フォルダを開く",
@@ -805,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": "選択した出力ノードをキューに追加",
@@ -817,26 +839,31 @@
"Restart": "再起動",
"Save": "保存",
"Save As": "名前を付けて保存",
"Show Keybindings Dialog": "キーバインドダイアログを表示",
"Show Settings Dialog": "設定ダイアログを表示",
"Sign Out": "サインアウト",
"Toggle Bottom Panel": "下部パネルの切り替え",
"Toggle Focus Mode": "フォーカスモードの切り替え",
"Toggle Essential Bottom Panel": "エッセンシャル下部パネルの切り替え",
"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 Workflows Sidebar": "ワークフローサイドバーを切り替え",
"Toggle View Controls Bottom Panel": "ビューコントロール下部パネルの切り替え",
"Toggle the Custom Nodes Manager": "カスタムノードマネージャーを切り替え",
"Toggle the Custom Nodes Manager Progress Bar": "カスタムノードマネージャーの進行状況バーを切り替え",
"Undo": "元に戻す",
"Ungroup selected group nodes": "選択したグループノードのグループ解除",
"Unload Models": "モデルのアンロード",
"Unload Models and Execution Cache": "モデルと実行キャッシュのアンロー",
"Workflow": "ワークフロー",
"Unpack the selected Subgraph": "選択したサブグラフを展開",
"Workflows": "ワークフロー",
"Zoom In": "ズームイン",
"Zoom Out": "ズームアウト"
"Zoom Out": "ズームアウト",
"Zoom to fit": "全体表示にズーム"
},
"minimap": {
"nodeColors": "ノードの色",
"renderBypassState": "バイパス状態を表示",
"renderErrorState": "エラー状態を表示",
"showGroups": "フレーム/グループを表示",
"showLinks": "リンクを表示"
},
"missingModelsDialog": {
"doNotAskAgain": "再度表示しない",
@@ -1104,6 +1131,7 @@
},
"settingsCategories": {
"3D": "3D",
"3DViewer": "3Dビューア",
"API Nodes": "APIード",
"About": "情報",
"Appearance": "外観",
@@ -1155,10 +1183,31 @@
"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": "新しい空のワークフローを作成",
@@ -1196,6 +1245,7 @@
},
"showFlatList": "フラットリストを表示"
},
"templates": "テンプレート",
"workflowTab": {
"confirmDelete": "このワークフローを削除してもよろしいですか?",
"confirmDeleteTitle": "ワークフローを削除しますか?",
@@ -1242,6 +1292,8 @@
"Video": "ビデオ",
"Video API": "動画API"
},
"loadingMore": "さらにテンプレートを読み込み中...",
"searchPlaceholder": "テンプレートを検索...",
"template": {
"3D": {
"3d_hunyuan3d_image_to_model": "Hunyuan3D",
@@ -1564,6 +1616,7 @@
"failedToExportModel": "{format}としてモデルのエクスポートに失敗しました",
"failedToFetchBalance": "残高の取得に失敗しました: {error}",
"failedToFetchLogs": "サーバーログの取得に失敗しました",
"failedToInitializeLoad3dViewer": "3Dビューアの初期化に失敗しました",
"failedToInitiateCreditPurchase": "クレジット購入の開始に失敗しました: {error}",
"failedToPurchaseCredits": "クレジットの購入に失敗しました: {error}",
"fileLoadError": "{fileName}でワークフローが見つかりません",

View File

@@ -164,20 +164,8 @@
"Comfy_LoadDefaultWorkflow": {
"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_CustomNodesManager": {
"label": "사용자 정의 노드 관리자"
},
"Comfy_Manager_ToggleManagerProgressDialog": {
"label": "진행 상황 대화 상자 전환"
@@ -191,12 +179,6 @@
"Comfy_MaskEditor_OpenMaskEditor": {
"label": "선택한 노드 마스크 편집기 열기"
},
"Comfy_Memory_UnloadModels": {
"label": "모델 언로드"
},
"Comfy_Memory_UnloadModelsAndExecutionCache": {
"label": "모델 및 실행 캐시 언로드"
},
"Comfy_NewBlankWorkflow": {
"label": "새로운 빈 워크플로"
},

View File

@@ -272,11 +272,11 @@
"category": "카테고리",
"choose_file_to_upload": "업로드할 파일 선택",
"clear": "지우기",
"clearFilters": "필터 지우기",
"close": "닫기",
"color": "색상",
"comingSoon": "곧 출시 예정",
"command": "명령",
"commandProhibited": "명령 {command}은 금지되었습니다. 자세한 정보는 관리자에게 문의하십시오.",
"community": "커뮤니티",
"completed": "완료됨",
"confirm": "확인",
@@ -299,6 +299,7 @@
"disabling": "비활성화 중",
"dismiss": "닫기",
"download": "다운로드",
"duplicate": "복제",
"edit": "편집",
"empty": "비어 있음",
"enableAll": "모두 활성화",
@@ -569,6 +570,10 @@
"applyingTexture": "텍스처 적용 중...",
"backgroundColor": "배경색",
"camera": "카메라",
"cameraType": {
"orthographic": "직교",
"perspective": "원근"
},
"clearRecording": "녹화 지우기",
"edgeThreshold": "엣지 임계값",
"export": "내보내기",
@@ -589,6 +594,7 @@
"wireframe": "와이어프레임"
},
"model": "모델",
"openIn3DViewer": "3D 뷰어에서 열기",
"previewOutput": "출력 미리보기",
"removeBackgroundImage": "배경 이미지 제거",
"resizeNodeMatchOutput": "노드 크기를 출력에 맞추기",
@@ -599,8 +605,22 @@
"switchCamera": "카메라 전환",
"switchingMaterialMode": "재질 모드 전환 중...",
"upDirection": "위 방향",
"upDirections": {
"original": "원본"
},
"uploadBackgroundImage": "배경 이미지 업로드",
"uploadTexture": "텍스처 업로드"
"uploadTexture": "텍스처 업로드",
"viewer": {
"apply": "적용",
"cameraSettings": "카메라 설정",
"cameraType": "카메라 유형",
"cancel": "취소",
"exportSettings": "내보내기 설정",
"lightSettings": "조명 설정",
"modelSettings": "모델 설정",
"sceneSettings": "씬 설정",
"title": "3D 뷰어 (베타)"
}
},
"loadWorkflowWarning": {
"coreNodesFromVersion": "ComfyUI {version} 이상 필요:",
@@ -647,9 +667,6 @@
"installationQueue": "설치 대기열",
"lastUpdated": "마지막 업데이트",
"latestVersion": "최신",
"legacyManagerUI": "레거시 UI 사용",
"legacyManagerUIDescription": "레거시 매니저 UI를 사용하려면, ComfyUI를 --enable-manager-legacy-ui로 시작하세요",
"legacyMenuNotAvailable": "이 버전의 ComfyUI에서는 레거시 매니저 메뉴를 사용할 수 없습니다. 대신 새로운 매니저 메뉴를 사용하십시오.",
"license": "라이선스",
"loadingVersions": "버전 로딩 중...",
"nightlyVersion": "최신 테스트 버전(nightly)",
@@ -732,6 +749,7 @@
"manageExtensions": "확장 프로그램 관리",
"onChange": "변경 시",
"onChangeTooltip": "변경이 있는 경우에만 워크플로를 실행 대기열에 추가합니다.",
"queue": "대기열 패널",
"refresh": "노드 정의 새로 고침",
"resetView": "캔버스 보기 재설정",
"run": "실행",
@@ -745,12 +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 Custom Node Updates": "커스텀 노드 업데이트 확인",
"Check for Updates": "업데이트 확인",
"Clear Pending Tasks": "보류 중인 작업 제거하기",
"Clear Workflow": "워크플로 지우기",
@@ -764,27 +781,28 @@
"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)",
"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": "마스크 편집기에서 브러시 크기 늘리기",
"Install Missing Custom Nodes": "누락된 커스텀 노드 설치",
"Interrupt": "중단",
"Load Default Workflow": "기본 워크플로 불러오기",
"Manage group nodes": "그룹 노드 관리",
"Manager": "매니저",
"Manager Menu (Legacy)": "매니저 메뉴(레거시)",
"Minimap": "미니맵",
"Model Library": "모델 라이브러리",
"Move Selected Nodes Down": "선택한 노드 아래로 이동",
"Move Selected Nodes Left": "선택한 노드 왼쪽으로 이동",
"Move Selected Nodes Right": "선택한 노드 오른쪽으로 이동",
@@ -792,7 +810,10 @@
"Mute/Unmute Selected Nodes": "선택한 노드 활성화/비활성화",
"New": "새로 만들기",
"Next Opened Workflow": "다음 열린 워크플로",
"Node Library": "노드 라이브러리",
"Node Links": "노드 링크",
"Open": "열기",
"Open 3D Viewer (Beta) for Selected Node": "선택한 노드에 대해 3D 뷰어(베타) 열기",
"Open Custom Nodes Folder": "사용자 정의 노드 폴더 열기",
"Open DevTools": "개발자 도구 열기",
"Open Inputs Folder": "입력 폴더 열기",
@@ -805,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": "선택한 출력 노드 대기열에 추가",
@@ -817,26 +839,31 @@
"Restart": "재시작",
"Save": "저장",
"Save As": "다른 이름으로 저장",
"Show Keybindings Dialog": "키 바인딩 대화상자 표시",
"Show Settings Dialog": "설정 대화상자 표시",
"Sign Out": "로그아웃",
"Toggle Bottom Panel": "하단 패널 전환",
"Toggle Focus Mode": "포커스 모드 전환",
"Toggle Essential Bottom Panel": "필수 하단 패널 전환",
"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 Workflows Sidebar": "워크플로우 사이드바 전환",
"Toggle View Controls Bottom Panel": "뷰 컨트롤 하단 패널 전환",
"Toggle the Custom Nodes Manager": "커스텀 노드 매니저 전환",
"Toggle the Custom Nodes Manager Progress Bar": "커스텀 노드 매니저 진행률 표시줄 전환",
"Undo": "실행 취소",
"Ungroup selected group nodes": "선택한 그룹 노드 그룹 해제",
"Unload Models": "모델 언로드",
"Unload Models and Execution Cache": "모델 및 실행 캐시 언로드",
"Workflow": "워크플로",
"Unpack the selected Subgraph": "선택한 서브그래프 풀기",
"Workflows": "워크플로우",
"Zoom In": "확대",
"Zoom Out": "축소"
"Zoom Out": "축소",
"Zoom to fit": "화면에 맞추기"
},
"minimap": {
"nodeColors": "노드 색상",
"renderBypassState": "바이패스 상태 렌더링",
"renderErrorState": "에러 상태 렌더링",
"showGroups": "프레임/그룹 표시",
"showLinks": "링크 표시"
},
"missingModelsDialog": {
"doNotAskAgain": "다시 보지 않기",
@@ -1104,6 +1131,7 @@
},
"settingsCategories": {
"3D": "3D",
"3DViewer": "3D뷰어",
"API Nodes": "API 노드",
"About": "정보",
"Appearance": "모양",
@@ -1155,10 +1183,31 @@
"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": "새 빈 워크플로 만들기",
@@ -1196,6 +1245,7 @@
},
"showFlatList": "평면 목록 표시"
},
"templates": "템플릿",
"workflowTab": {
"confirmDelete": "정말로 이 워크플로를 삭제하시겠습니까?",
"confirmDeleteTitle": "워크플로 삭제",
@@ -1242,6 +1292,8 @@
"Video": "비디오",
"Video API": "비디오 API"
},
"loadingMore": "템플릿을 더 불러오는 중...",
"searchPlaceholder": "템플릿 검색...",
"template": {
"3D": {
"3d_hunyuan3d_image_to_model": "Hunyuan3D 2.0",
@@ -1564,6 +1616,7 @@
"failedToExportModel": "{format} 형식으로 모델 내보내기에 실패했습니다",
"failedToFetchBalance": "잔액을 가져오지 못했습니다: {error}",
"failedToFetchLogs": "서버 로그를 가져오는 데 실패했습니다",
"failedToInitializeLoad3dViewer": "3D 뷰어 초기화에 실패했습니다",
"failedToInitiateCreditPurchase": "크레딧 구매를 시작하지 못했습니다: {error}",
"failedToPurchaseCredits": "크레딧 구매에 실패했습니다: {error}",
"fileLoadError": "{fileName}에서 워크플로를 찾을 수 없습니다",

View File

@@ -164,20 +164,8 @@
"Comfy_LoadDefaultWorkflow": {
"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_CustomNodesManager": {
"label": "Менеджер Пользовательских Узлов"
},
"Comfy_Manager_ToggleManagerProgressDialog": {
"label": "Переключить диалоговое окно прогресса"
@@ -191,12 +179,6 @@
"Comfy_MaskEditor_OpenMaskEditor": {
"label": "Открыть редактор масок для выбранной ноды"
},
"Comfy_Memory_UnloadModels": {
"label": "Выгрузить модели"
},
"Comfy_Memory_UnloadModelsAndExecutionCache": {
"label": "Выгрузить модели и кэш выполнения"
},
"Comfy_NewBlankWorkflow": {
"label": "Новый пустой рабочий процесс"
},

View File

@@ -272,11 +272,11 @@
"category": "Категория",
"choose_file_to_upload": "выберите файл для загрузки",
"clear": "Очистить",
"clearFilters": "Сбросить фильтры",
"close": "Закрыть",
"color": "Цвет",
"comingSoon": "Скоро будет",
"command": "Команда",
"commandProhibited": "Команда {command} запрещена. Свяжитесь с администратором для получения дополнительной информации.",
"community": "Сообщество",
"completed": "Завершено",
"confirm": "Подтвердить",
@@ -299,6 +299,7 @@
"disabling": "Отключение",
"dismiss": "Закрыть",
"download": "Скачать",
"duplicate": "Дублировать",
"edit": "Редактировать",
"empty": "Пусто",
"enableAll": "Включить все",
@@ -569,6 +570,10 @@
"applyingTexture": "Применение текстуры...",
"backgroundColor": "Цвет фона",
"camera": "Камера",
"cameraType": {
"orthographic": "Ортографическая",
"perspective": "Перспективная"
},
"clearRecording": "Очистить запись",
"edgeThreshold": "Пороговое значение края",
"export": "Экспорт",
@@ -589,6 +594,7 @@
"wireframe": "Каркас"
},
"model": "Модель",
"openIn3DViewer": "Открыть в 3D просмотрщике",
"previewOutput": "Предварительный просмотр",
"removeBackgroundImage": "Удалить фоновое изображение",
"resizeNodeMatchOutput": "Изменить размер узла под вывод",
@@ -599,8 +605,22 @@
"switchCamera": "Переключить камеру",
"switchingMaterialMode": "Переключение режима материала...",
"upDirection": "Направление Вверх",
"upDirections": {
"original": "Оригинал"
},
"uploadBackgroundImage": "Загрузить фоновое изображение",
"uploadTexture": "Загрузить текстуру"
"uploadTexture": "Загрузить текстуру",
"viewer": {
"apply": "Применить",
"cameraSettings": "Настройки камеры",
"cameraType": "Тип камеры",
"cancel": "Отмена",
"exportSettings": "Настройки экспорта",
"lightSettings": "Настройки освещения",
"modelSettings": "Настройки модели",
"sceneSettings": "Настройки сцены",
"title": "3D Просмотрщик (Бета)"
}
},
"loadWorkflowWarning": {
"coreNodesFromVersion": "Требуется ComfyUI {version}:",
@@ -647,9 +667,6 @@
"installationQueue": "Очередь установки",
"lastUpdated": "Последнее обновление",
"latestVersion": "Последняя",
"legacyManagerUI": "Использовать устаревший UI",
"legacyManagerUIDescription": "Чтобы использовать устаревший UI менеджера, запустите ComfyUI с --enable-manager-legacy-ui",
"legacyMenuNotAvailable": "Устаревшее меню менеджера недоступно в этой версии ComfyUI. Пожалуйста, используйте новое меню менеджера.",
"license": "Лицензия",
"loadingVersions": "Загрузка версий...",
"nightlyVersion": "Ночная",
@@ -732,6 +749,7 @@
"manageExtensions": "Управление расширениями",
"onChange": "При изменении",
"onChangeTooltip": "Рабочий процесс будет поставлен в очередь после внесения изменений",
"queue": "Панель очереди",
"refresh": "Обновить определения нод",
"resetView": "Сбросить вид холста",
"run": "Запустить",
@@ -745,12 +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 Custom Node Updates": "Проверить обновления пользовательских узлов",
"Check for Updates": "Проверить наличие обновлений",
"Clear Pending Tasks": "Очистить ожидающие задачи",
"Clear Workflow": "Очистить рабочий процесс",
@@ -764,27 +781,28 @@
"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)",
"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": "Увеличить размер кисти в MaskEditor",
"Install Missing Custom Nodes": "Установить отсутствующие пользовательские узлы",
"Interrupt": "Прервать",
"Load Default Workflow": "Загрузить стандартный рабочий процесс",
"Manage group nodes": "Управление групповыми нодами",
"Manager": "Менеджер",
"Manager Menu (Legacy)": "Меню управления (устаревшее)",
"Minimap": "Мини-карта",
"Model Library": "Библиотека моделей",
"Move Selected Nodes Down": "Переместить выбранные узлы вниз",
"Move Selected Nodes Left": "Переместить выбранные узлы влево",
"Move Selected Nodes Right": "Переместить выбранные узлы вправо",
@@ -792,7 +810,10 @@
"Mute/Unmute Selected Nodes": "Отключить/включить звук для выбранных нод",
"New": "Новый",
"Next Opened Workflow": "Следующий открытый рабочий процесс",
"Node Library": "Библиотека узлов",
"Node Links": "Связи узлов",
"Open": "Открыть",
"Open 3D Viewer (Beta) for Selected Node": "Открыть 3D-просмотрщик (бета) для выбранного узла",
"Open Custom Nodes Folder": "Открыть папку пользовательских нод",
"Open DevTools": "Открыть инструменты разработчика",
"Open Inputs Folder": "Открыть папку входных данных",
@@ -805,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": "Добавить выбранные выходные узлы в очередь",
@@ -817,26 +839,31 @@
"Restart": "Перезапустить",
"Save": "Сохранить",
"Save As": "Сохранить как",
"Show Keybindings Dialog": "Показать диалог клавиш быстрого доступа",
"Show Settings Dialog": "Показать диалог настроек",
"Sign Out": "Выйти",
"Toggle Bottom Panel": ереключить нижнюю панель",
"Toggle Focus Mode": "Переключить режим фокуса",
"Toggle Essential Bottom Panel": оказать/скрыть основную нижнюю панель",
"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 Workflows Sidebar": "Показать/скрыть боковую панель рабочих процессов",
"Toggle View Controls Bottom Panel": "Показать/скрыть панель управления просмотром",
"Toggle the Custom Nodes Manager": "Переключить менеджер пользовательских узлов",
"Toggle the Custom Nodes Manager Progress Bar": "Переключить индикатор выполнения менеджера пользовательских узлов",
"Undo": "Отменить",
"Ungroup selected group nodes": "Разгруппировать выбранные групповые ноды",
"Unload Models": "Выгрузить модели",
"Unload Models and Execution Cache": "Выгрузить модели и кэш выполнения",
"Workflow": "Рабочий процесс",
"Unpack the selected Subgraph": "Распаковать выбранный подграф",
"Workflows": "Рабочие процессы",
"Zoom In": "Увеличить",
"Zoom Out": "Уменьшить"
"Zoom Out": "Уменьшить",
"Zoom to fit": "Масштабировать по размеру"
},
"minimap": {
"nodeColors": "Цвета узлов",
"renderBypassState": "Отображать состояние обхода",
"renderErrorState": "Отображать состояние ошибки",
"showGroups": "Показать фреймы/группы",
"showLinks": "Показать связи"
},
"missingModelsDialog": {
"doNotAskAgain": "Больше не показывать это",
@@ -1104,6 +1131,7 @@
},
"settingsCategories": {
"3D": "3D",
"3DViewer": "3D-просмотрщик",
"API Nodes": "API-узлы",
"About": "О программе",
"Appearance": "Внешний вид",
@@ -1155,10 +1183,31 @@
"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": "Создайте новый пустой рабочий процесс",
@@ -1196,6 +1245,7 @@
},
"showFlatList": "Показать плоский список"
},
"templates": "Шаблоны",
"workflowTab": {
"confirmDelete": "Вы уверены, что хотите удалить этот рабочий процесс?",
"confirmDeleteTitle": "Удалить рабочий процесс?",
@@ -1242,6 +1292,8 @@
"Video": "Видео",
"Video API": "Video API"
},
"loadingMore": "Загрузка дополнительных шаблонов...",
"searchPlaceholder": "Поиск шаблонов...",
"template": {
"3D": {
"3d_hunyuan3d_image_to_model": "Hunyuan3D",
@@ -1564,6 +1616,7 @@
"failedToExportModel": "Не удалось экспортировать модель как {format}",
"failedToFetchBalance": "Не удалось получить баланс: {error}",
"failedToFetchLogs": "Не удалось получить серверные логи",
"failedToInitializeLoad3dViewer": "Не удалось инициализировать 3D просмотрщик",
"failedToInitiateCreditPurchase": "Не удалось начать покупку кредитов: {error}",
"failedToPurchaseCredits": "Не удалось купить кредиты: {error}",
"fileLoadError": "Не удалось найти рабочий процесс в {fileName}",

View File

@@ -164,20 +164,8 @@
"Comfy_LoadDefaultWorkflow": {
"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_CustomNodesManager": {
"label": "切換自訂節點管理器"
},
"Comfy_Manager_ToggleManagerProgressDialog": {
"label": "切換自訂節點管理器進度條"
@@ -191,12 +179,6 @@
"Comfy_MaskEditor_OpenMaskEditor": {
"label": "為選取的節點開啟 Mask 編輯器"
},
"Comfy_Memory_UnloadModels": {
"label": "卸載模型"
},
"Comfy_Memory_UnloadModelsAndExecutionCache": {
"label": "卸載模型與執行快取"
},
"Comfy_NewBlankWorkflow": {
"label": "新增空白工作流程"
},

View File

@@ -272,11 +272,11 @@
"category": "分類",
"choose_file_to_upload": "選擇要上傳的檔案",
"clear": "清除",
"clearFilters": "清除篩選",
"close": "關閉",
"color": "顏色",
"comingSoon": "即將推出",
"command": "指令",
"commandProhibited": "指令 {command} 已被禁止。如需更多資訊,請聯絡管理員。",
"community": "社群",
"completed": "已完成",
"confirm": "確認",
@@ -299,6 +299,7 @@
"disabling": "停用中",
"dismiss": "關閉",
"download": "下載",
"duplicate": "複製",
"edit": "編輯",
"empty": "空",
"enableAll": "全部啟用",
@@ -569,6 +570,10 @@
"applyingTexture": "正在套用材質貼圖...",
"backgroundColor": "背景顏色",
"camera": "相機",
"cameraType": {
"orthographic": "正交",
"perspective": "透視"
},
"clearRecording": "清除錄影",
"edgeThreshold": "邊緣閾值",
"export": "匯出",
@@ -589,6 +594,7 @@
"wireframe": "線框"
},
"model": "模型",
"openIn3DViewer": "在 3D 檢視器中開啟",
"previewOutput": "預覽輸出",
"removeBackgroundImage": "移除背景圖片",
"resizeNodeMatchOutput": "調整節點以符合輸出",
@@ -599,8 +605,22 @@
"switchCamera": "切換相機",
"switchingMaterialMode": "正在切換材質模式...",
"upDirection": "上方方向",
"upDirections": {
"original": "原始"
},
"uploadBackgroundImage": "上傳背景圖片",
"uploadTexture": "上傳材質貼圖"
"uploadTexture": "上傳材質貼圖",
"viewer": {
"apply": "套用",
"cameraSettings": "相機設定",
"cameraType": "相機類型",
"cancel": "取消",
"exportSettings": "匯出設定",
"lightSettings": "燈光設定",
"modelSettings": "模型設定",
"sceneSettings": "場景設定",
"title": "3D 檢視器(測試版)"
}
},
"loadWorkflowWarning": {
"coreNodesFromVersion": "需要 ComfyUI {version}",
@@ -647,9 +667,6 @@
"installationQueue": "安裝佇列",
"lastUpdated": "最後更新",
"latestVersion": "最新版本",
"legacyManagerUI": "使用舊版介面",
"legacyManagerUIDescription": "若要使用舊版管理介面,請以 --enable-manager-legacy-ui 啟動 ComfyUI",
"legacyMenuNotAvailable": "舊版管理選單不可用,已預設切換至新版管理選單。",
"license": "授權條款",
"loadingVersions": "正在載入版本...",
"nightlyVersion": "每夜建置版",
@@ -732,6 +749,7 @@
"manageExtensions": "管理擴充功能",
"onChange": "變更時",
"onChangeTooltip": "每當有變更時,工作流程會排入佇列",
"queue": "佇列面板",
"refresh": "重新整理節點定義",
"resetView": "重設畫布視圖",
"run": "執行",
@@ -745,12 +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 Custom Node Updates": "檢查自訂節點更新",
"Check for Updates": "檢查更新",
"Clear Pending Tasks": "清除待處理任務",
"Clear Workflow": "清除工作流程",
@@ -764,27 +781,28 @@
"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",
"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": "在 MaskEditor 中增大筆刷大小",
"Install Missing Custom Nodes": "安裝缺少的自訂節點",
"Interrupt": "中斷",
"Load Default Workflow": "載入預設工作流程",
"Manage group nodes": "管理群組節點",
"Manager": "管理員",
"Manager Menu (Legacy)": "管理員選單(舊版)",
"Minimap": "縮圖地圖",
"Model Library": "模型庫",
"Move Selected Nodes Down": "選取節點下移",
"Move Selected Nodes Left": "選取節點左移",
"Move Selected Nodes Right": "選取節點右移",
@@ -792,7 +810,10 @@
"Mute/Unmute Selected Nodes": "靜音/取消靜音選取節點",
"New": "新增",
"Next Opened Workflow": "下一個已開啟的工作流程",
"Node Library": "節點庫",
"Node Links": "節點連結",
"Open": "開啟",
"Open 3D Viewer (Beta) for Selected Node": "為選取的節點開啟 3D 檢視器Beta 版)",
"Open Custom Nodes Folder": "開啟自訂節點資料夾",
"Open DevTools": "開啟開發者工具",
"Open Inputs Folder": "開啟輸入資料夾",
@@ -805,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": "將選取的輸出節點加入佇列",
@@ -817,26 +839,31 @@
"Restart": "重新啟動",
"Save": "儲存",
"Save As": "另存新檔",
"Show Keybindings Dialog": "顯示快捷鍵對話框",
"Show Settings Dialog": "顯示設定對話框",
"Sign Out": "登出",
"Toggle Bottom Panel": "切換下方面板",
"Toggle Focus Mode": "切換專注模式",
"Toggle Essential Bottom Panel": "切換基本下方面板",
"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 Workflows Sidebar": "切換工作流程側邊欄",
"Toggle View Controls Bottom Panel": "切換檢視控制下方面板",
"Toggle the Custom Nodes Manager": "切換自訂節點管理器",
"Toggle the Custom Nodes Manager Progress Bar": "切換自訂節點管理器進度條",
"Undo": "復原",
"Ungroup selected group nodes": "取消群組選取的群組節點",
"Unload Models": "卸載模型",
"Unload Models and Execution Cache": "卸載模型與執行快取",
"Workflow": "工作流程",
"Unpack the selected Subgraph": "解包所選子圖",
"Workflows": "工作流程",
"Zoom In": "放大",
"Zoom Out": "縮小"
"Zoom Out": "縮小",
"Zoom to fit": "縮放至適合大小"
},
"minimap": {
"nodeColors": "節點顏色",
"renderBypassState": "顯示繞過狀態",
"renderErrorState": "顯示錯誤狀態",
"showGroups": "顯示框架/群組",
"showLinks": "顯示連結"
},
"missingModelsDialog": {
"doNotAskAgain": "不要再顯示此訊息",
@@ -1104,6 +1131,7 @@
},
"settingsCategories": {
"3D": "3D",
"3DViewer": "3D 檢視器",
"API Nodes": "API 節點",
"About": "關於",
"Appearance": "外觀",
@@ -1155,10 +1183,31 @@
"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": "建立新的空白工作流程",
@@ -1196,6 +1245,7 @@
},
"showFlatList": "顯示平面清單"
},
"templates": "範本",
"workflowTab": {
"confirmDelete": "您確定要刪除這個工作流程嗎?",
"confirmDeleteTitle": "刪除工作流程?",
@@ -1242,6 +1292,8 @@
"Video": "影片",
"Video API": "影片 API"
},
"loadingMore": "正在載入更多範本...",
"searchPlaceholder": "搜尋範本...",
"template": {
"3D": {
"3d_hunyuan3d_image_to_model": "Hunyuan3D 2.0",
@@ -1564,6 +1616,7 @@
"failedToExportModel": "無法將模型匯出為 {format}",
"failedToFetchBalance": "取得餘額失敗:{error}",
"failedToFetchLogs": "無法取得伺服器日誌",
"failedToInitializeLoad3dViewer": "初始化 3D 檢視器失敗",
"failedToInitiateCreditPurchase": "啟動點數購買失敗:{error}",
"failedToPurchaseCredits": "購買點數失敗:{error}",
"fileLoadError": "無法在 {fileName} 中找到工作流程",

View File

@@ -164,20 +164,8 @@
"Comfy_LoadDefaultWorkflow": {
"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_CustomNodesManager": {
"label": "自定义节点管理器"
},
"Comfy_Manager_ToggleManagerProgressDialog": {
"label": "切换进度对话框"
@@ -191,12 +179,6 @@
"Comfy_MaskEditor_OpenMaskEditor": {
"label": "打开选中节点的遮罩编辑器"
},
"Comfy_Memory_UnloadModels": {
"label": "卸载模型"
},
"Comfy_Memory_UnloadModelsAndExecutionCache": {
"label": "卸载模型和执行缓存"
},
"Comfy_NewBlankWorkflow": {
"label": "新建空白工作流"
},

View File

@@ -83,10 +83,10 @@
}
},
"breadcrumbsMenu": {
"clearWorkflow": "清工作流",
"deleteWorkflow": "除工作流",
"duplicate": "複製",
"enterNewName": "入新名"
"clearWorkflow": "清工作流",
"deleteWorkflow": "除工作流",
"duplicate": "复制",
"enterNewName": "入新名"
},
"chatHistory": {
"cancelEdit": "取消",
@@ -218,7 +218,7 @@
"WEBCAM": "摄像头"
},
"desktopMenu": {
"confirmQuit": "未保存的工作流程开启;任何未保存的更改都将丢失。忽略此警告并退出?",
"confirmQuit": "存在未保存的工作流;任何未保存的更改都将丢失。忽略此警告并退出?",
"confirmReinstall": "这将清除您的 extra_models_config.yaml 文件,并重新开始安装。您确定吗?",
"quit": "退出",
"reinstall": "重新安装"
@@ -272,11 +272,11 @@
"category": "类别",
"choose_file_to_upload": "选择要上传的文件",
"clear": "清除",
"clearFilters": "清除筛选",
"close": "关闭",
"color": "颜色",
"comingSoon": "即将推出",
"command": "指令",
"commandProhibited": "命令 {command} 被禁止。请联系管理员获取更多信息。",
"community": "社区",
"completed": "已完成",
"confirm": "确认",
@@ -297,8 +297,9 @@
"devices": "设备",
"disableAll": "禁用全部",
"disabling": "禁用中",
"dismiss": "關閉",
"dismiss": "关闭",
"download": "下载",
"duplicate": "复制",
"edit": "编辑",
"empty": "空",
"enableAll": "启用全部",
@@ -312,8 +313,8 @@
"filter": "过滤",
"findIssues": "查找问题",
"firstTimeUIMessage": "这是您第一次使用新界面。选择 \"菜单 > 使用新菜单 > 禁用\" 来恢复旧界面。",
"frontendNewer": "前端版本 {frontendVersion} 可能與後端版本 {backendVersion} 不容。",
"frontendOutdated": "前端版本 {frontendVersion} 已過時。後端需要 {requiredVersion} 或更高版本。",
"frontendNewer": "前端版本 {frontendVersion} 可能与后端版本 {backendVersion} 不容。",
"frontendOutdated": "前端版本 {frontendVersion} 已过时。后端需要 {requiredVersion} 或更高版本。",
"goToNode": "转到节点",
"help": "帮助",
"icon": "图标",
@@ -399,8 +400,8 @@
"upload": "上传",
"usageHint": "使用提示",
"user": "用户",
"versionMismatchWarning": "版本容性警告",
"versionMismatchWarningMessage": "{warning}{detail} 請參閱 https://docs.comfy.org/installation/update_comfyui#common-update-issues 以取得更新明。",
"versionMismatchWarning": "版本容性警告",
"versionMismatchWarningMessage": "{warning}{detail} 请参考 https://docs.comfy.org/installation/update_comfyui#common-update-issues 以取得更新明。",
"videoFailedToLoad": "视频加载失败",
"workflow": "工作流"
},
@@ -410,7 +411,7 @@
"resetView": "重置视图",
"selectMode": "选择模式",
"toggleLinkVisibility": "切换连线可见性",
"toggleMinimap": "切小地",
"toggleMinimap": "切小地",
"zoomIn": "放大",
"zoomOut": "缩小"
},
@@ -569,6 +570,10 @@
"applyingTexture": "应用纹理中...",
"backgroundColor": "背景颜色",
"camera": "摄影机",
"cameraType": {
"orthographic": "正交",
"perspective": "透视"
},
"clearRecording": "清除录制",
"edgeThreshold": "边缘阈值",
"export": "导出",
@@ -589,6 +594,7 @@
"wireframe": "线框"
},
"model": "模型",
"openIn3DViewer": "在 3D 浏览器中打开",
"previewOutput": "预览输出",
"removeBackgroundImage": "移除背景图片",
"resizeNodeMatchOutput": "调整节点以匹配输出",
@@ -599,8 +605,22 @@
"switchCamera": "切换摄影机类型",
"switchingMaterialMode": "切换材质模式中...",
"upDirection": "上方向",
"upDirections": {
"original": "原始"
},
"uploadBackgroundImage": "上传背景图片",
"uploadTexture": "上传纹理"
"uploadTexture": "上传纹理",
"viewer": {
"apply": "套用",
"cameraSettings": "相机设置",
"cameraType": "相机类型",
"cancel": "取消",
"exportSettings": "导出设置",
"lightSettings": "灯光设置",
"modelSettings": "模型设置",
"sceneSettings": "场景设置",
"title": "3D 浏览器(测试版)"
}
},
"loadWorkflowWarning": {
"coreNodesFromVersion": "需要 ComfyUI {version}",
@@ -647,9 +667,6 @@
"installationQueue": "安装队列",
"lastUpdated": "最后更新",
"latestVersion": "最新",
"legacyManagerUI": "使用旧版UI",
"legacyManagerUIDescription": "要使用旧版的管理器UI请启动ComfyUI并使用 --enable-manager-legacy-ui",
"legacyMenuNotAvailable": "在此版本的ComfyUI中不提供旧版的管理器菜单。请使用新的管理器菜单。",
"license": "许可证",
"loadingVersions": "正在加载版本...",
"nightlyVersion": "每夜",
@@ -723,21 +740,22 @@
"disabled": "禁用",
"disabledTooltip": "工作流将不会自动执行",
"execute": "执行",
"help": "說明",
"help": "帮助",
"hideMenu": "隐藏菜单",
"instant": "实时",
"instantTooltip": "工作流将会在生成完成后立即执行",
"interrupt": "取消当前任务",
"light": "淺色",
"manageExtensions": "管理擴充功能",
"manageExtensions": "管理扩展功能",
"onChange": "更改时",
"onChangeTooltip": "一旦进行更改,工作流将添加到执行队列",
"queue": "队列面板",
"refresh": "刷新节点",
"resetView": "重置视图",
"run": "运行",
"runWorkflow": "运行工作流Shift排在前面",
"runWorkflowFront": "运行工作流程(排在前面",
"settings": "設定",
"runWorkflow": "运行工作流Shift插队",
"runWorkflowFront": "运行工作流(插队",
"settings": "设置",
"showMenu": "显示菜单",
"theme": "主題",
"toggleBottomPanel": "底部面板"
@@ -745,12 +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 Custom Node Updates": "檢查自訂節點更新",
"Check for Updates": "检查更新",
"Clear Pending Tasks": "清除待处理任务",
"Clear Workflow": "清除工作流",
@@ -764,27 +781,28 @@
"Contact Support": "联系支持",
"Convert Selection to Subgraph": "将选中内容转换为子图",
"Convert selected nodes to group node": "将选中节点转换为组节点",
"Custom Nodes (Legacy)": "自訂節點(舊版)",
"Custom Nodes Manager": "自定义节点管理器",
"Decrease Brush Size in MaskEditor": "在 MaskEditor 中減小筆刷大小",
"Decrease Brush Size in MaskEditor": "在 MaskEditor 中减小笔刷大小",
"Delete Selected Items": "删除选定的项目",
"Desktop User Guide": "桌面端用户指南",
"Duplicate Current Workflow": "复制当前工作流",
"Edit": "编辑",
"Exit Subgraph": "退出子圖",
"Export": "导出",
"Export (API)": "导出 (API)",
"File": "文件",
"Fit Group To Contents": "适应组内容",
"Fit view to selected nodes": "适应视图到选中节点",
"Focus Mode": "专注模式",
"Give Feedback": "提供反馈",
"Group Selected Nodes": "将选中节点转换为组节点",
"Help": "帮助",
"Increase Brush Size in MaskEditor": "在 MaskEditor 中增大筆刷大小",
"Install Missing Custom Nodes": "安裝缺少的自訂節點",
"Help Center": "帮助中心",
"Increase Brush Size in MaskEditor": "在 MaskEditor 中增大笔刷大小",
"Interrupt": "中断",
"Load Default Workflow": "加载默认工作流",
"Manage group nodes": "管理组节点",
"Manager": "管理器",
"Manager Menu (Legacy)": "管理選單(舊版)",
"Minimap": "缩略地图",
"Model Library": "模型库",
"Move Selected Nodes Down": "下移所选节点",
"Move Selected Nodes Left": "左移所选节点",
"Move Selected Nodes Right": "右移所选节点",
@@ -792,7 +810,10 @@
"Mute/Unmute Selected Nodes": "静音/取消静音选定节点",
"New": "新建",
"Next Opened Workflow": "下一个打开的工作流",
"Node Library": "节点库",
"Node Links": "节点连线",
"Open": "打开",
"Open 3D Viewer (Beta) for Selected Node": "为所选节点开启 3D 浏览器Beta 版)",
"Open Custom Nodes Folder": "打开自定义节点文件夹",
"Open DevTools": "打开开发者工具",
"Open Inputs Folder": "打开输入文件夹",
@@ -805,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": "将所选输出节点加入队列",
@@ -817,26 +839,31 @@
"Restart": "重启",
"Save": "保存",
"Save As": "另存为",
"Show Keybindings Dialog": "显示快捷键对话框",
"Show Settings Dialog": "显示设置对话框",
"Sign Out": "退出登录",
"Toggle Bottom Panel": "切换底部面板",
"Toggle Focus Mode": "切换专注模式",
"Toggle Essential Bottom Panel": "切换基本下方面板",
"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 Workflows Sidebar": "切換工作流程側邊欄",
"Toggle View Controls Bottom Panel": "切换视图控制下方面板",
"Toggle the Custom Nodes Manager": "切换自定义节点管理器",
"Toggle the Custom Nodes Manager Progress Bar": "切换自定义节点管理器进度条",
"Undo": "撤销",
"Ungroup selected group nodes": "解散选中组节点",
"Unload Models": "卸载模型",
"Unload Models and Execution Cache": "卸载模型和执行缓存",
"Workflow": "工作流",
"Unpack the selected Subgraph": "解開所選子圖",
"Workflows": "工作流程",
"Zoom In": "放大画面",
"Zoom Out": "缩小画面"
"Zoom Out": "缩小画面",
"Zoom to fit": "缩放至适合大小"
},
"minimap": {
"nodeColors": "节点颜色",
"renderBypassState": "显示绕过状态",
"renderErrorState": "显示错误状态",
"showGroups": "显示框架/群组",
"showLinks": "显示连线"
},
"missingModelsDialog": {
"doNotAskAgain": "不再显示此消息",
@@ -1104,6 +1131,7 @@
},
"settingsCategories": {
"3D": "3D",
"3DViewer": "3D 浏览器",
"API Nodes": "API 节点",
"About": "关于",
"Appearance": "外观",
@@ -1155,10 +1183,31 @@
"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": "创建空白工作流",
@@ -1196,6 +1245,7 @@
},
"showFlatList": "平铺结果"
},
"templates": "模板",
"workflowTab": {
"confirmDelete": "您确定要删除此工作流吗?",
"confirmDeleteTitle": "删除工作流?",
@@ -1242,6 +1292,8 @@
"Video": "视频生成",
"Video API": "视频 API"
},
"loadingMore": "正在加载更多模板...",
"searchPlaceholder": "搜索模板...",
"template": {
"3D": {
"3d_hunyuan3d_image_to_model": "混元3D 2.0 图生模型",
@@ -1564,6 +1616,7 @@
"failedToExportModel": "无法将模型导出为 {format}",
"failedToFetchBalance": "获取余额失败:{error}",
"failedToFetchLogs": "无法获取服务器日志",
"failedToInitializeLoad3dViewer": "初始化 3D 浏览器失败",
"failedToInitiateCreditPurchase": "发起积分购买失败:{error}",
"failedToPurchaseCredits": "购买积分失败:{error}",
"fileLoadError": "无法在 {fileName} 中找到工作流",
@@ -1620,7 +1673,7 @@
"required": "必填"
},
"versionMismatchWarning": {
"dismiss": "關閉",
"dismiss": "关闭",
"frontendNewer": "前端版本 {frontendVersion} 可能與後端版本 {backendVersion} 不相容。",
"frontendOutdated": "前端版本 {frontendVersion} 已過時。後端需要 {requiredVersion} 版或更高版本。",
"title": "版本相容性警告",

View File

@@ -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": "校验工作流"

View File

@@ -495,7 +495,6 @@ 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(),

View File

@@ -1,5 +1,4 @@
import axios from 'axios'
import get from 'lodash/get'
import defaultClientFeatureFlags from '@/config/clientFeatureFlags.json'
import type {
@@ -36,7 +35,6 @@ 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'
@@ -1022,56 +1020,6 @@ 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.
*
@@ -1083,21 +1031,21 @@ export class ComfyApi extends EventTarget {
/**
* Checks if the server supports a specific feature.
* @param featureName The name of the feature to check (supports dot notation for nested values)
* @param featureName The name of the feature to check
* @returns true if the feature is supported, false otherwise
*/
serverSupportsFeature(featureName: string): boolean {
return get(this.serverFeatureFlags, featureName) === true
return this.serverFeatureFlags[featureName] === true
}
/**
* Gets a server feature flag value.
* @param featureName The name of the feature to get (supports dot notation for nested values)
* @param featureName The name of the feature to get
* @param defaultValue The default value if the feature is not found
* @returns The feature value or default
*/
getServerFeature<T = unknown>(featureName: string, defaultValue?: T): T {
return get(this.serverFeatureFlags, featureName, defaultValue) as T
return (this.serverFeatureFlags[featureName] ?? defaultValue) as T
}
/**

View File

@@ -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',
IS_LEGACY_MANAGER_UI = 'manager/is_legacy_manager_ui'
REBOOT = 'manager/reboot'
}
const managerApiClient = axios.create({
baseURL: api.apiURL('/v2/'),
baseURL: api.apiURL(''),
headers: {
'Content-Type': 'application/json'
}
@@ -247,15 +247,6 @@ 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,
@@ -277,7 +268,6 @@ export const useComfyManagerService = () => {
updateAllPacks,
// System operations
rebootComfyUI,
isLegacyManagerUI
rebootComfyUI
}
}

View File

@@ -29,7 +29,6 @@ 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[]>([])
@@ -50,9 +49,6 @@ 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
@@ -121,11 +117,7 @@ export const useComfyManagerStore = defineStore('comfyManager', () => {
whenever(isStale, refreshInstalledList, { immediate: true })
whenever(uncompletedCount, () => showManagerProgressDialog())
const withLogs = (
task: () => Promise<null>,
taskName: string,
packId?: string
) => {
const withLogs = (task: () => Promise<null>, taskName: string) => {
const { startListening, stopListening, logs } = useServerLogs()
const loggedTask = async () => {
@@ -136,9 +128,6 @@ export const useComfyManagerStore = defineStore('comfyManager', () => {
const onComplete = async () => {
await stopListening()
if (packId) {
installingPacksIds.value.delete(packId)
}
setStale()
}
@@ -163,11 +152,8 @@ export const useComfyManagerStore = defineStore('comfyManager', () => {
}
}
installingPacksIds.value.add(params.id)
const task = () => managerService.installPack(params, signal)
enqueueTask(
withLogs(task, `${actionDescription} ${params.id}`, params.id)
)
enqueueTask(withLogs(task, `${actionDescription} ${params.id}`))
},
{ maxSize: 1 }
)
@@ -176,16 +162,14 @@ export const useComfyManagerStore = defineStore('comfyManager', () => {
installPack.clear()
installPack.cancel()
const task = () => managerService.uninstallPack(params, signal)
enqueueTask(
withLogs(task, t('manager.uninstalling', { id: params.id }), params.id)
)
enqueueTask(withLogs(task, t('manager.uninstalling', { 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 }), params.id))
enqueueTask(withLogs(task, t('g.updating', { id: params.id })))
},
{ maxSize: 1 }
)
@@ -200,7 +184,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 }), params.id))
enqueueTask(withLogs(task, t('g.disabling', { id: params.id })))
}
const getInstalledPackVersion = (packId: string) => {
@@ -228,7 +212,6 @@ export const useComfyManagerStore = defineStore('comfyManager', () => {
installedPacksIds,
isPackInstalled: isInstalledPackId,
isPackEnabled: isEnabledPackId,
isPackInstalling: isInstallingPackId,
getInstalledPackVersion,
refreshInstalledList,

View File

@@ -1,3 +1,4 @@
import axios from 'axios'
import {
type Auth,
GithubAuthProvider,
@@ -44,6 +45,14 @@ export class FirebaseAuthStoreError extends Error {
}
}
// Customer API client - follows the same pattern as other services
const customerApiClient = axios.create({
baseURL: COMFY_API_BASE_URL,
headers: {
'Content-Type': 'application/json'
}
})
export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
// State
const loading = ref(false)
@@ -129,27 +138,27 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
)
}
const response = await fetch(`${COMFY_API_BASE_URL}/customers/balance`, {
headers: {
...authHeader,
'Content-Type': 'application/json'
let balanceData
try {
const response = await customerApiClient.get('/customers/balance', {
headers: authHeader
})
balanceData = response.data
} catch (error) {
if (axios.isAxiosError(error) && error.response) {
if (error.response.status === 404) {
// Customer not found is expected for new users
return null
}
const errorData = error.response.data
throw new FirebaseAuthStoreError(
t('toastMessages.failedToFetchBalance', {
error: errorData.message
})
)
}
})
if (!response.ok) {
if (response.status === 404) {
// Customer not found is expected for new users
return null
}
const errorData = await response.json()
throw new FirebaseAuthStoreError(
t('toastMessages.failedToFetchBalance', {
error: errorData.message
})
)
throw error
}
const balanceData = await response.json()
// Update the last balance update time
lastBalanceUpdateTime.value = new Date()
balance.value = balanceData
@@ -165,23 +174,26 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
throw new FirebaseAuthStoreError(t('toastMessages.userNotAuthenticated'))
}
const createCustomerRes = await fetch(`${COMFY_API_BASE_URL}/customers`, {
method: 'POST',
headers: {
...authHeader,
'Content-Type': 'application/json'
}
})
if (!createCustomerRes.ok) {
throw new FirebaseAuthStoreError(
t('toastMessages.failedToCreateCustomer', {
error: createCustomerRes.statusText
})
let createCustomerResJson: CreateCustomerResponse
try {
const createCustomerRes = await customerApiClient.post(
'/customers',
{},
{
headers: authHeader
}
)
createCustomerResJson = createCustomerRes.data
} catch (error) {
if (axios.isAxiosError(error)) {
throw new FirebaseAuthStoreError(
t('toastMessages.failedToCreateCustomer', {
error: error.response?.statusText || error.message
})
)
}
throw error
}
const createCustomerResJson: CreateCustomerResponse =
await createCustomerRes.json()
if (!createCustomerResJson?.id) {
throw new FirebaseAuthStoreError(
t('toastMessages.failedToCreateCustomer', {
@@ -282,25 +294,26 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
customerCreated.value = true
}
const response = await fetch(`${COMFY_API_BASE_URL}/customers/credit`, {
method: 'POST',
headers: {
...authHeader,
'Content-Type': 'application/json'
},
body: JSON.stringify(requestBodyContent)
})
if (!response.ok) {
const errorData = await response.json()
throw new FirebaseAuthStoreError(
t('toastMessages.failedToInitiateCreditPurchase', {
error: errorData.message
})
try {
const response = await customerApiClient.post(
'/customers/credit',
requestBodyContent,
{
headers: authHeader
}
)
return response.data
} catch (error) {
if (axios.isAxiosError(error) && error.response) {
const errorData = error.response.data
throw new FirebaseAuthStoreError(
t('toastMessages.failedToInitiateCreditPurchase', {
error: errorData.message
})
)
}
throw error
}
return response.json()
}
const initiateCreditPurchase = async (
@@ -316,27 +329,26 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
throw new FirebaseAuthStoreError(t('toastMessages.userNotAuthenticated'))
}
const response = await fetch(`${COMFY_API_BASE_URL}/customers/billing`, {
method: 'POST',
headers: {
...authHeader,
'Content-Type': 'application/json'
},
...(requestBody && {
body: JSON.stringify(requestBody)
})
})
if (!response.ok) {
const errorData = await response.json()
throw new FirebaseAuthStoreError(
t('toastMessages.failedToAccessBillingPortal', {
error: errorData.message
})
try {
const response = await customerApiClient.post(
'/customers/billing',
requestBody,
{
headers: authHeader
}
)
return response.data
} catch (error) {
if (axios.isAxiosError(error) && error.response) {
const errorData = error.response.data
throw new FirebaseAuthStoreError(
t('toastMessages.failedToAccessBillingPortal', {
error: errorData.message
})
)
}
throw error
}
return response.json()
}
return {

View File

@@ -19,7 +19,7 @@ export const IsInstallingKey: InjectionKey<Ref<boolean>> =
Symbol('isInstalling')
export enum ManagerWsQueueStatus {
DONE = 'all-done',
DONE = 'done',
IN_PROGRESS = 'in_progress'
}

View File

@@ -11,8 +11,7 @@ const mockT = vi.fn((key: string) => {
'shortcuts.subcategories.node': 'Node',
'shortcuts.subcategories.queue': 'Queue',
'shortcuts.subcategories.view': 'View',
'shortcuts.subcategories.panelControls': 'Panel Controls',
'commands.Workflow_New.label': 'New Blank Workflow'
'shortcuts.subcategories.panelControls': 'Panel Controls'
}
return translations[key] || key
})
@@ -77,7 +76,9 @@ describe('ShortcutsList', () => {
expect(wrapper.text()).toContain('Queue')
// Check that commands are rendered
expect(wrapper.text()).toContain('New Blank Workflow')
expect(wrapper.text()).toContain('New Workflow')
expect(wrapper.text()).toContain('Add Node')
expect(wrapper.text()).toContain('Clear Queue')
})
it('should format keyboard shortcuts correctly', () => {

View File

@@ -1,440 +0,0 @@
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()
})
})
})

View File

@@ -1512,10 +1512,7 @@ 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-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' }
{ model: 'gpt-4.1', expected: '$0.002/$0.008 per 1K tokens' }
]
testCases.forEach(({ model, expected }) => {

View File

@@ -1,121 +0,0 @@
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)
})
})
})

View File

@@ -28,7 +28,7 @@ describe('useManagerQueue', () => {
const getEventListenerCallback = () =>
vi.mocked(api.addEventListener).mock.calls[0][1]
const simulateServerStatus = async (status: 'all-done' | 'in_progress') => {
const simulateServerStatus = async (status: '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('all-done')
expect(queue.statusMessage.value).toBe('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('all-done')
expect(queue.statusMessage.value).toBe('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('all-done')
expect(queue.statusMessage.value).toBe('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('all-done')
await simulateServerStatus('done')
// Task should have been started
expect(mockTask.task).toHaveBeenCalled()
@@ -138,7 +138,7 @@ describe('useManagerQueue', () => {
const { mockTask } = createQueueWithMockTask()
// Start the task
await simulateServerStatus('all-done')
await simulateServerStatus('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('all-done')
await simulateServerStatus('done')
expect(mockTask.onComplete).toHaveBeenCalled()
})
@@ -159,7 +159,7 @@ describe('useManagerQueue', () => {
queue.enqueueTask(mockTask)
// Start the task
await simulateServerStatus('all-done')
await simulateServerStatus('done')
expect(mockTask.task).toHaveBeenCalled()
// Simulate task completion
@@ -167,7 +167,7 @@ describe('useManagerQueue', () => {
// Simulate server cycle
await simulateServerStatus('in_progress')
await simulateServerStatus('all-done')
await simulateServerStatus('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('all-done')
await simulateServerStatus('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('all-done')
await simulateServerStatus('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('all-done')
await simulateServerStatus('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('all-done')
await simulateServerStatus('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('all-done')
await simulateServerStatus('done')
// onComplete should still be called for failed tasks
expect(mockTask.onComplete).toHaveBeenCalled()
@@ -252,7 +252,7 @@ describe('useManagerQueue', () => {
])
// Task 1
await simulateServerStatus('all-done')
await simulateServerStatus('done')
expect(mockTask1.task).toHaveBeenCalled()
// Verify state of onComplete callbacks
@@ -266,7 +266,7 @@ describe('useManagerQueue', () => {
// Task 2
await simulateServerStatus('in_progress')
await simulateServerStatus('all-done')
await simulateServerStatus('done')
expect(mockTask2.task).toHaveBeenCalled()
// Verify state of onComplete callbacks
@@ -279,7 +279,7 @@ describe('useManagerQueue', () => {
// Task 3
await simulateServerStatus('in_progress')
await simulateServerStatus('all-done')
await simulateServerStatus('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('all-done')
await simulateServerStatus('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('all-done')
await simulateServerStatus('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('all-done')
await simulateServerStatus('done')
await simulateServerStatus('in_progress')
await simulateServerStatus('all-done')
await simulateServerStatus('done')
// Should not cause any errors
expect(queue.allTasksDone.value).toBe(true)

View File

@@ -6,8 +6,6 @@ import { useComfyManagerService } from '@/services/comfyManagerService'
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
import {
InstalledPacksResponse,
ManagerChannel,
ManagerDatabaseSource,
ManagerPackInstalled
} from '@/types/comfyManagerTypes'
@@ -15,34 +13,6 @@ 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)
@@ -63,7 +33,11 @@ interface EnabledDisabledTestCase {
}
describe('useComfyManagerStore', () => {
let mockManagerService: ReturnType<typeof useComfyManagerService>
let mockManagerService: {
isLoading: ReturnType<typeof ref<boolean>>
error: ReturnType<typeof ref<string | null>>
listInstalledPacks: ReturnType<typeof vi.fn>
}
const triggerPacksChange = async (
installedPacks: InstalledPacksResponse,
@@ -81,21 +55,10 @@ describe('useComfyManagerStore', () => {
mockManagerService = {
isLoading: ref(false),
error: ref(null),
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)
listInstalledPacks: vi.fn().mockResolvedValue({})
}
// @ts-expect-error Mocking the return type of useComfyManagerService
vi.mocked(useComfyManagerService).mockReturnValue(mockManagerService)
})
@@ -350,90 +313,4 @@ 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)
})
})
})

View File

@@ -1,3 +1,4 @@
import axios from 'axios'
import * as firebaseAuth from 'firebase/auth'
import { createPinia, setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it, vi } from 'vitest'
@@ -5,6 +6,30 @@ import * as vuefire from 'vuefire'
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
// Mock axios before any imports that use it
vi.mock('axios', () => {
const mockAxiosInstance = {
get: vi.fn().mockResolvedValue({
data: { balance: { credits: 0 } },
status: 200
}),
post: vi.fn().mockResolvedValue({
data: { id: 'test-customer-id' },
status: 201
})
}
return {
default: {
create: vi.fn().mockReturnValue(mockAxiosInstance),
isAxiosError: vi.fn().mockImplementation(() => false)
}
}
})
const mockedAxios = vi.mocked(axios)
const mockAxiosInstance = mockedAxios.create() as any
// Mock fetch
const mockFetch = vi.fn()
vi.stubGlobal('fetch', mockFetch)
@@ -91,7 +116,18 @@ describe('useFirebaseAuthStore', () => {
}
beforeEach(() => {
vi.resetAllMocks()
vi.clearAllMocks()
// Reset axios mock responses to defaults
mockAxiosInstance.get.mockResolvedValue({
data: { balance: { credits: 0 } },
status: 200
})
mockAxiosInstance.post.mockResolvedValue({
data: { id: 'test-customer-id' },
status: 201
})
;(mockedAxios.isAxiosError as any).mockReturnValue(false)
// Mock useFirebaseAuth to return our mock auth object
vi.mocked(vuefire.useFirebaseAuth).mockReturnValue(mockAuth as any)