Merge branch 'main' into sno-storybook--settings-panel

This commit is contained in:
snomiao
2025-10-02 23:31:34 +09:00
committed by GitHub
70 changed files with 923 additions and 847 deletions

View File

@@ -0,0 +1,31 @@
name: Setup Playwright
description: Cache and install Playwright browsers with dependencies
runs:
using: composite
steps:
- name: Detect Playwright version
id: detect-version
shell: bash
working-directory: ComfyUI_frontend
run: |
PLAYWRIGHT_VERSION=$(pnpm ls @playwright/test --json | jq --raw-output '.[0].devDependencies["@playwright/test"].version')
echo "playwright-version=$PLAYWRIGHT_VERSION" >> $GITHUB_OUTPUT
- name: Cache Playwright Browsers
uses: actions/cache@v4
id: cache-playwright-browsers
with:
path: '~/.cache/ms-playwright'
key: ${{ runner.os }}-playwright-browsers-${{ steps.detect-version.outputs.playwright-version }}
- name: Install Playwright Browsers
if: steps.cache-playwright-browsers.outputs.cache-hit != 'true'
shell: bash
run: pnpm exec playwright install chromium --with-deps
working-directory: ComfyUI_frontend
- name: Install Playwright Browsers (operating system dependencies)
if: steps.cache-playwright-browsers.outputs.cache-hit == 'true'
shell: bash
run: pnpm exec playwright install-deps
working-directory: ComfyUI_frontend

View File

@@ -15,9 +15,7 @@ jobs:
- name: Checkout PR - name: Checkout PR
uses: actions/checkout@v5 uses: actions/checkout@v5
with: with:
token: ${{ secrets.GITHUB_TOKEN }} ref: ${{ !github.event.pull_request.head.repo.fork && github.head_ref || github.ref }}
ref: ${{ github.event.pull_request.head.ref }}
fetch-depth: 0
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
@@ -102,4 +100,4 @@ jobs:
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
body: '## ⚠️ Linting/Formatting Issues Found\n\nThis PR has linting or formatting issues that need to be fixed.\n\n**Since this PR is from a fork, auto-fix cannot be applied automatically.**\n\n### Option 1: Set up pre-commit hooks (recommended)\nRun this once to automatically format code on every commit:\n```bash\npnpm prepare\n```\n\n### Option 2: Fix manually\nRun these commands and push the changes:\n```bash\npnpm lint:fix\npnpm format\n```\n\nSee [CONTRIBUTING.md](https://github.com/Comfy-Org/ComfyUI_frontend/blob/main/CONTRIBUTING.md#git-pre-commit-hooks) for more details.' body: '## ⚠️ Linting/Formatting Issues Found\n\nThis PR has linting or formatting issues that need to be fixed.\n\n**Since this PR is from a fork, auto-fix cannot be applied automatically.**\n\n### Option 1: Set up pre-commit hooks (recommended)\nRun this once to automatically format code on every commit:\n```bash\npnpm prepare\n```\n\n### Option 2: Fix manually\nRun these commands and push the changes:\n```bash\npnpm lint:fix\npnpm format\n```\n\nSee [CONTRIBUTING.md](https://github.com/Comfy-Org/ComfyUI_frontend/blob/main/CONTRIBUTING.md#git-pre-commit-hooks) for more details.'
}) })

View File

@@ -12,7 +12,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs: outputs:
cache-key: ${{ steps.cache-key.outputs.key }} cache-key: ${{ steps.cache-key.outputs.key }}
playwright-version: ${{ steps.playwright-version.outputs.PLAYWRIGHT_VERSION }}
steps: steps:
- name: Checkout ComfyUI - name: Checkout ComfyUI
uses: actions/checkout@v5 uses: actions/checkout@v5
@@ -65,12 +64,6 @@ jobs:
id: cache-key id: cache-key
run: echo "key=$(date +%s)" >> $GITHUB_OUTPUT run: echo "key=$(date +%s)" >> $GITHUB_OUTPUT
- name: Playwright Version
id: playwright-version
run: |
PLAYWRIGHT_VERSION=$(pnpm ls @playwright/test --json | jq --raw-output '.[0].devDependencies["@playwright/test"].version')
echo "PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION" >> $GITHUB_OUTPUT
working-directory: ComfyUI_frontend
- name: Save cache - name: Save cache
uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684
@@ -123,22 +116,8 @@ jobs:
working-directory: ComfyUI working-directory: ComfyUI
- name: Cache Playwright Browsers - name: Setup Playwright
uses: actions/cache@v4 uses: ./ComfyUI_frontend/.github/actions/setup-playwright
id: cache-playwright-browsers
with:
path: '~/.cache/ms-playwright'
key: '${{ runner.os }}-playwright-browsers-${{ needs.setup.outputs.playwright-version }}'
- name: Install Playwright Browsers
if: steps.cache-playwright-browsers.outputs.cache-hit != 'true'
run: pnpm exec playwright install chromium --with-deps
working-directory: ComfyUI_frontend
- name: Install Playwright Browsers (operating system dependencies)
if: steps.cache-playwright-browsers.outputs.cache-hit == 'true'
run: pnpm exec playwright install-deps
working-directory: ComfyUI_frontend
- name: Start ComfyUI server - name: Start ComfyUI server
run: | run: |
@@ -202,22 +181,8 @@ jobs:
pip install wait-for-it pip install wait-for-it
working-directory: ComfyUI working-directory: ComfyUI
- name: Cache Playwright Browsers - name: Setup Playwright
uses: actions/cache@v4 uses: ./ComfyUI_frontend/.github/actions/setup-playwright
id: cache-playwright-browsers
with:
path: '~/.cache/ms-playwright'
key: '${{ runner.os }}-playwright-browsers-${{ needs.setup.outputs.playwright-version }}'
- name: Install Playwright Browsers
if: steps.cache-playwright-browsers.outputs.cache-hit != 'true'
run: pnpm exec playwright install chromium --with-deps
working-directory: ComfyUI_frontend
- name: Install Playwright Browsers (operating system dependencies)
if: steps.cache-playwright-browsers.outputs.cache-hit == 'true'
run: pnpm exec playwright install-deps
working-directory: ComfyUI_frontend
- name: Start ComfyUI server - name: Start ComfyUI server
run: | run: |

View File

@@ -77,9 +77,8 @@ jobs:
python main.py --cpu --multi-user & python main.py --cpu --multi-user &
wait-for-it --service 127.0.0.1:8188 -t 600 wait-for-it --service 127.0.0.1:8188 -t 600
working-directory: ComfyUI working-directory: ComfyUI
- name: Install Playwright Browsers - name: Setup Playwright
run: pnpm exec playwright install chromium --with-deps uses: ./ComfyUI_frontend/.github/actions/setup-playwright
working-directory: ComfyUI_frontend
- name: Start dev server - name: Start dev server
# Run electron dev server as it is a superset of the web dev server # Run electron dev server as it is a superset of the web dev server
# We do want electron specific UIs to be translated. # We do want electron specific UIs to be translated.

View File

@@ -26,16 +26,8 @@ jobs:
key: i18n-tools-cache-${{ runner.os }}-${{ hashFiles('ComfyUI_frontend/pnpm-lock.yaml') }} key: i18n-tools-cache-${{ runner.os }}-${{ hashFiles('ComfyUI_frontend/pnpm-lock.yaml') }}
restore-keys: | restore-keys: |
i18n-tools-cache-${{ runner.os }}- i18n-tools-cache-${{ runner.os }}-
- name: Cache Playwright browsers - name: Setup Playwright
uses: actions/cache@v4 uses: ./.github/actions/setup-playwright
with:
path: ~/.cache/ms-playwright
key: playwright-browsers-${{ runner.os }}-${{ hashFiles('ComfyUI_frontend/pnpm-lock.yaml') }}
restore-keys: |
playwright-browsers-${{ runner.os }}-
- name: Install Playwright Browsers
run: pnpm exec playwright install chromium --with-deps
working-directory: ComfyUI_frontend
- name: Start dev server - name: Start dev server
# Run electron dev server as it is a superset of the web dev server # Run electron dev server as it is a superset of the web dev server
# We do want electron specific UIs to be translated. # We do want electron specific UIs to be translated.

View File

@@ -13,11 +13,9 @@ jobs:
update-locales: update-locales:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Setup Frontend - uses: Comfy-Org/ComfyUI_frontend_setup_action@v3
uses: ./.github/actions/setup-frontend - name: Setup Playwright
- name: Install Playwright Browsers uses: ./.github/actions/setup-playwright
run: pnpm exec playwright install chromium --with-deps
working-directory: ComfyUI_frontend
- name: Start dev server - name: Start dev server
# Run electron dev server as it is a superset of the web dev server # Run electron dev server as it is a superset of the web dev server
# We do want electron specific UIs to be translated. # We do want electron specific UIs to be translated.

View File

@@ -14,16 +14,8 @@ jobs:
uses: actions/checkout@v5 uses: actions/checkout@v5
- name: Setup Frontend - name: Setup Frontend
uses: ./.github/actions/setup-frontend uses: ./.github/actions/setup-frontend
- name: Cache Playwright browsers - name: Setup Playwright
uses: actions/cache@v4 uses: ./.github/actions/setup-playwright
with:
path: ~/.cache/ms-playwright
key: playwright-browsers-${{ runner.os }}-${{ hashFiles('ComfyUI_frontend/pnpm-lock.yaml') }}
restore-keys: |
playwright-browsers-${{ runner.os }}-
- name: Install Playwright Browsers
run: pnpm exec playwright install chromium --with-deps
working-directory: ComfyUI_frontend
- name: Run Playwright tests and update snapshots - name: Run Playwright tests and update snapshots
id: playwright-tests id: playwright-tests
run: pnpm exec playwright test --update-snapshots run: pnpm exec playwright test --update-snapshots

View File

@@ -90,6 +90,7 @@ export default defineConfig([
} }
], ],
'unused-imports/no-unused-imports': 'error', 'unused-imports/no-unused-imports': 'error',
'no-console': ['error', { allow: ['warn', 'error'] }],
'vue/no-v-html': 'off', 'vue/no-v-html': 'off',
// Enforce dark-theme: instead of dark: prefix // Enforce dark-theme: instead of dark: prefix
'vue/no-restricted-class': ['error', '/^dark:/'], 'vue/no-restricted-class': ['error', '/^dark:/'],
@@ -207,5 +208,11 @@ export default defineConfig([
} }
] ]
} }
},
{
files: ['**/*.{test,spec,stories}.ts', '**/*.stories.vue'],
rules: {
'no-console': 'off'
}
} }
]) ])

View File

@@ -20,7 +20,6 @@ const config: KnipConfig = {
project: ['src/**/*.{js,ts}', '*.{js,ts,mts}'] project: ['src/**/*.{js,ts}', '*.{js,ts,mts}']
}, },
'packages/registry-types': { 'packages/registry-types': {
entry: ['src/comfyRegistryTypes.ts'],
project: ['src/**/*.{js,ts}'] project: ['src/**/*.{js,ts}']
} }
}, },

View File

@@ -42,82 +42,82 @@
"devtools:pycheck": "python3 -m compileall -q tools/devtools" "devtools:pycheck": "python3 -m compileall -q tools/devtools"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.35.0", "@eslint/js": "catalog:",
"@intlify/eslint-plugin-vue-i18n": "^4.1.0", "@intlify/eslint-plugin-vue-i18n": "catalog:",
"@lobehub/i18n-cli": "^1.25.1", "@lobehub/i18n-cli": "catalog:",
"@nx/eslint": "21.4.1", "@nx/eslint": "catalog:",
"@nx/playwright": "21.4.1", "@nx/playwright": "catalog:",
"@nx/storybook": "21.4.1", "@nx/storybook": "catalog:",
"@nx/vite": "21.4.1", "@nx/vite": "catalog:",
"@pinia/testing": "^0.1.5", "@pinia/testing": "catalog:",
"@playwright/test": "^1.52.0", "@playwright/test": "catalog:",
"@storybook/addon-docs": "^9.1.1", "@storybook/addon-docs": "catalog:",
"@storybook/vue3": "^9.1.1", "@storybook/vue3": "catalog:",
"@storybook/vue3-vite": "^9.1.1", "@storybook/vue3-vite": "catalog:",
"@tailwindcss/vite": "^4.1.12", "@tailwindcss/vite": "catalog:",
"@trivago/prettier-plugin-sort-imports": "^5.2.0", "@trivago/prettier-plugin-sort-imports": "catalog:",
"@types/fs-extra": "^11.0.4", "@types/fs-extra": "catalog:",
"@types/jsdom": "^21.1.7", "@types/jsdom": "catalog:",
"@types/node": "^20.14.8", "@types/node": "catalog:",
"@types/semver": "^7.7.0", "@types/semver": "catalog:",
"@types/three": "^0.169.0", "@types/three": "catalog:",
"@vitejs/plugin-vue": "^5.1.4", "@vitejs/plugin-vue": "catalog:",
"@vitest/coverage-v8": "^3.2.4", "@vitest/coverage-v8": "catalog:",
"@vitest/ui": "^3.0.0", "@vitest/ui": "catalog:",
"@vue/test-utils": "^2.4.6", "@vue/test-utils": "catalog:",
"eslint": "^9.34.0", "eslint": "catalog:",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "catalog:",
"eslint-plugin-prettier": "^5.5.4", "eslint-plugin-prettier": "catalog:",
"eslint-plugin-storybook": "^9.1.6", "eslint-plugin-storybook": "catalog:",
"eslint-plugin-unused-imports": "^4.2.0", "eslint-plugin-unused-imports": "catalog:",
"eslint-plugin-vue": "^10.4.0", "eslint-plugin-vue": "catalog:",
"fs-extra": "^11.2.0", "fs-extra": "^11.2.0",
"globals": "^15.9.0", "globals": "catalog:",
"happy-dom": "^15.11.0", "happy-dom": "catalog:",
"husky": "^9.0.11", "husky": "catalog:",
"jiti": "2.4.2", "jiti": "catalog:",
"jsdom": "^26.1.0", "jsdom": "catalog:",
"knip": "^5.62.0", "knip": "catalog:",
"lint-staged": "^15.2.7", "lint-staged": "catalog:",
"nx": "21.4.1", "nx": "catalog:",
"prettier": "^3.3.2", "prettier": "catalog:",
"storybook": "^9.1.6", "storybook": "catalog:",
"tailwindcss": "^4.1.12", "tailwindcss": "catalog:",
"tailwindcss-primeui": "^0.6.1", "tailwindcss-primeui": "catalog:",
"tsx": "^4.15.6", "tsx": "catalog:",
"tw-animate-css": "^1.3.8", "tw-animate-css": "catalog:",
"typescript": "^5.4.5", "typescript": "catalog:",
"typescript-eslint": "^8.44.0", "typescript-eslint": "catalog:",
"unplugin-icons": "^0.22.0", "unplugin-icons": "catalog:",
"unplugin-vue-components": "^0.28.0", "unplugin-vue-components": "catalog:",
"uuid": "^11.1.0", "uuid": "^11.1.0",
"vite": "^5.4.19", "vite": "catalog:",
"vite-plugin-dts": "^4.5.4", "vite-plugin-dts": "catalog:",
"vite-plugin-html": "^3.2.2", "vite-plugin-html": "catalog:",
"vite-plugin-vue-devtools": "^7.7.6", "vite-plugin-vue-devtools": "catalog:",
"vitest": "^3.2.4", "vitest": "catalog:",
"vue-component-type-helpers": "^3.0.7", "vue-component-type-helpers": "catalog:",
"vue-eslint-parser": "^10.2.0", "vue-eslint-parser": "catalog:",
"vue-tsc": "^3.0.7", "vue-tsc": "catalog:",
"zip-dir": "^2.0.0", "zip-dir": "^2.0.0",
"zod-to-json-schema": "^3.24.1" "zod-to-json-schema": "catalog:"
}, },
"dependencies": { "dependencies": {
"@alloc/quick-lru": "^5.2.0", "@alloc/quick-lru": "catalog:",
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1", "@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
"@comfyorg/comfyui-electron-types": "0.4.73-0", "@comfyorg/comfyui-electron-types": "0.4.73-0",
"@comfyorg/design-system": "workspace:*", "@comfyorg/design-system": "workspace:*",
"@comfyorg/registry-types": "workspace:*", "@comfyorg/registry-types": "workspace:*",
"@comfyorg/tailwind-utils": "workspace:*", "@comfyorg/tailwind-utils": "workspace:*",
"@iconify/json": "^2.2.380", "@iconify/json": "catalog:",
"@primeuix/forms": "0.0.2", "@primeuix/forms": "catalog:",
"@primeuix/styled": "0.3.2", "@primeuix/styled": "catalog:",
"@primeuix/utils": "^0.3.2", "@primeuix/utils": "catalog:",
"@primevue/core": "^4.2.5", "@primevue/core": "catalog:",
"@primevue/forms": "^4.2.5", "@primevue/forms": "catalog:",
"@primevue/icons": "4.2.5", "@primevue/icons": "catalog:",
"@primevue/themes": "^4.2.5", "@primevue/themes": "catalog:",
"@sentry/vue": "^8.48.0", "@sentry/vue": "catalog:",
"@tiptap/core": "^2.10.4", "@tiptap/core": "^2.10.4",
"@tiptap/extension-link": "^2.10.4", "@tiptap/extension-link": "^2.10.4",
"@tiptap/extension-table": "^2.10.4", "@tiptap/extension-table": "^2.10.4",
@@ -125,39 +125,39 @@
"@tiptap/extension-table-header": "^2.10.4", "@tiptap/extension-table-header": "^2.10.4",
"@tiptap/extension-table-row": "^2.10.4", "@tiptap/extension-table-row": "^2.10.4",
"@tiptap/starter-kit": "^2.10.4", "@tiptap/starter-kit": "^2.10.4",
"@vueuse/core": "^11.0.0", "@vueuse/core": "catalog:",
"@vueuse/integrations": "^13.9.0", "@vueuse/integrations": "catalog:",
"@xterm/addon-fit": "^0.10.0", "@xterm/addon-fit": "^0.10.0",
"@xterm/addon-serialize": "^0.13.0", "@xterm/addon-serialize": "^0.13.0",
"@xterm/xterm": "^5.5.0", "@xterm/xterm": "^5.5.0",
"algoliasearch": "^5.21.0", "algoliasearch": "catalog:",
"axios": "^1.8.2", "axios": "catalog:",
"chart.js": "^4.5.0", "chart.js": "^4.5.0",
"dompurify": "^3.2.5", "dompurify": "^3.2.5",
"dotenv": "^16.4.5", "dotenv": "catalog:",
"es-toolkit": "^1.39.9", "es-toolkit": "^1.39.9",
"extendable-media-recorder": "^9.2.27", "extendable-media-recorder": "^9.2.27",
"extendable-media-recorder-wav-encoder": "^7.0.129", "extendable-media-recorder-wav-encoder": "^7.0.129",
"fast-glob": "^3.3.3", "fast-glob": "^3.3.3",
"firebase": "^11.6.0", "firebase": "catalog:",
"fuse.js": "^7.0.0", "fuse.js": "^7.0.0",
"glob": "^11.0.3", "glob": "^11.0.3",
"jsondiffpatch": "^0.6.0", "jsondiffpatch": "^0.6.0",
"loglevel": "^1.9.2", "loglevel": "^1.9.2",
"marked": "^15.0.11", "marked": "^15.0.11",
"pinia": "^2.1.7", "pinia": "catalog:",
"primeicons": "^7.0.0", "primeicons": "catalog:",
"primevue": "^4.2.5", "primevue": "catalog:",
"reka-ui": "^2.5.0", "reka-ui": "^2.5.0",
"semver": "^7.7.2", "semver": "^7.7.2",
"three": "^0.170.0", "three": "^0.170.0",
"tiptap-markdown": "^0.8.10", "tiptap-markdown": "^0.8.10",
"vue": "^3.5.13", "vue": "catalog:",
"vue-i18n": "^9.14.3", "vue-i18n": "catalog:",
"vue-router": "^4.4.3", "vue-router": "catalog:",
"vuefire": "^3.2.1", "vuefire": "catalog:",
"yjs": "^13.6.27", "yjs": "catalog:",
"zod": "^3.23.8", "zod": "catalog:",
"zod-validation-error": "^3.3.0" "zod-validation-error": "catalog:"
} }
} }

View File

@@ -4,10 +4,7 @@
"description": "Shared design system for ComfyUI Frontend", "description": "Shared design system for ComfyUI Frontend",
"type": "module", "type": "module",
"exports": { "exports": {
"./tailwind-config": { "./tailwind-config": "./tailwind.config.ts",
"import": "./tailwind.config.ts",
"types": "./tailwind.config.ts"
},
"./css/*": "./src/css/*" "./css/*": "./src/css/*"
}, },
"scripts": { "scripts": {
@@ -20,12 +17,12 @@
] ]
}, },
"dependencies": { "dependencies": {
"@iconify-json/lucide": "^1.1.178", "@iconify-json/lucide": "catalog:",
"@iconify/tailwind": "^1.1.3" "@iconify/tailwind": "catalog:"
}, },
"devDependencies": { "devDependencies": {
"tailwindcss": "^3.4.17", "tailwindcss": "catalog:",
"typescript": "^5.4.5" "typescript": "catalog:"
}, },
"packageManager": "pnpm@10.17.1" "packageManager": "pnpm@10.17.1"
} }

View File

@@ -152,7 +152,7 @@
} }
} }
/* Everthing below here to be cleaned up over time. */ /* Everything below here to be cleaned up over time. */
body { body {
width: 100vw; width: 100vw;

View File

@@ -1125,7 +1125,7 @@ export interface paths {
} }
get?: never get?: never
put?: never put?: never
/** Create a new custom node using admin priviledge */ /** Create a new custom node using admin privilege */
post: operations['adminCreateNode'] post: operations['adminCreateNode']
delete?: never delete?: never
options?: never options?: never
@@ -16383,7 +16383,7 @@ export interface operations {
} }
} }
responses: { responses: {
/** @description Webhook processed succesfully */ /** @description Webhook processed successfully */
200: { 200: {
headers: { headers: {
[name: string]: unknown [name: string]: unknown

View File

@@ -14,9 +14,9 @@
"./networkUtil": "./src/networkUtil.ts" "./networkUtil": "./src/networkUtil.ts"
}, },
"dependencies": { "dependencies": {
"axios": "^1.11.0" "axios": "catalog:"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5.9.2" "typescript": "catalog:"
} }
} }

View File

@@ -6,10 +6,7 @@
"main": "./src/index.ts", "main": "./src/index.ts",
"types": "./src/index.ts", "types": "./src/index.ts",
"exports": { "exports": {
".": { ".": "./src/index.ts"
"import": "./src/index.ts",
"types": "./src/index.ts"
}
}, },
"scripts": { "scripts": {
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
@@ -25,6 +22,6 @@
"tailwind-merge": "^2.2.0" "tailwind-merge": "^2.2.0"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5.4.5" "typescript": "catalog:"
} }
} }

887
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -84,6 +84,7 @@ catalog:
# Data and validation # Data and validation
algoliasearch: ^5.21.0 algoliasearch: ^5.21.0
axios: ^1.8.2
firebase: ^11.6.0 firebase: ^11.6.0
yjs: ^13.6.27 yjs: ^13.6.27
zod: ^3.23.8 zod: ^3.23.8

View File

@@ -44,7 +44,6 @@ const showContextMenu = (event: MouseEvent) => {
onMounted(() => { onMounted(() => {
// @ts-expect-error fixme ts strict error // @ts-expect-error fixme ts strict error
window['__COMFYUI_FRONTEND_VERSION__'] = config.app_version window['__COMFYUI_FRONTEND_VERSION__'] = config.app_version
console.log('ComfyUI Front-end version:', config.app_version)
if (isElectron()) { if (isElectron()) {
document.addEventListener('contextmenu', showContextMenu) document.addEventListener('contextmenu', showContextMenu)

View File

@@ -69,7 +69,7 @@ const terminalCreated = (
await loadLogEntries() await loadLogEntries()
} catch (err) { } catch (err) {
console.error('Error loading logs', err) console.error('Error loading logs', err)
// On older backends the endpoints wont exist // On older backends the endpoints won't exist
errorMessage.value = errorMessage.value =
'Unable to load logs, please ensure you have updated your ComfyUI backend.' 'Unable to load logs, please ensure you have updated your ComfyUI backend.'
return return

View File

@@ -45,37 +45,39 @@
<span class="text-muted">{{ t('auth.login.orContinueWith') }}</span> <span class="text-muted">{{ t('auth.login.orContinueWith') }}</span>
</Divider> </Divider>
<!-- Social Login Buttons --> <!-- Social Login Buttons (hidden if host not whitelisted) -->
<div class="flex flex-col gap-6"> <div class="flex flex-col gap-6">
<Button <template v-if="ssoAllowed">
type="button" <Button
class="h-10" type="button"
severity="secondary" class="h-10"
outlined severity="secondary"
@click="signInWithGoogle" outlined
> @click="signInWithGoogle"
<i class="pi pi-google mr-2"></i> >
{{ <i class="pi pi-google mr-2"></i>
isSignIn {{
? t('auth.login.loginWithGoogle') isSignIn
: t('auth.signup.signUpWithGoogle') ? t('auth.login.loginWithGoogle')
}} : t('auth.signup.signUpWithGoogle')
</Button> }}
</Button>
<Button <Button
type="button" type="button"
class="h-10" class="h-10"
severity="secondary" severity="secondary"
outlined outlined
@click="signInWithGithub" @click="signInWithGithub"
> >
<i class="pi pi-github mr-2"></i> <i class="pi pi-github mr-2"></i>
{{ {{
isSignIn isSignIn
? t('auth.login.loginWithGithub') ? t('auth.login.loginWithGithub')
: t('auth.signup.signUpWithGithub') : t('auth.signup.signUpWithGithub')
}} }}
</Button> </Button>
</template>
<Button <Button
type="button" type="button"
@@ -149,6 +151,7 @@ import { useI18n } from 'vue-i18n'
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions' import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
import { COMFY_PLATFORM_BASE_URL } from '@/config/comfyApi' import { COMFY_PLATFORM_BASE_URL } from '@/config/comfyApi'
import type { SignInData, SignUpData } from '@/schemas/signInSchema' import type { SignInData, SignUpData } from '@/schemas/signInSchema'
import { isHostWhitelisted, normalizeHost } from '@/utils/hostWhitelist'
import { isInChina } from '@/utils/networkUtil' import { isInChina } from '@/utils/networkUtil'
import ApiKeyForm from './signin/ApiKeyForm.vue' import ApiKeyForm from './signin/ApiKeyForm.vue'
@@ -164,6 +167,7 @@ const authActions = useFirebaseAuthActions()
const isSecureContext = window.isSecureContext const isSecureContext = window.isSecureContext
const isSignIn = ref(true) const isSignIn = ref(true)
const showApiKeyForm = ref(false) const showApiKeyForm = ref(false)
const ssoAllowed = isHostWhitelisted(normalizeHost(window.location.hostname))
const toggleState = () => { const toggleState = () => {
isSignIn.value = !isSignIn.value isSignIn.value = !isSignIn.value

View File

@@ -127,7 +127,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, provide, ref, watch } from 'vue' import { computed, provide, ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import IconButton from '@/components/button/IconButton.vue' import IconButton from '@/components/button/IconButton.vue'
@@ -202,12 +202,4 @@ const selectedSort = ref<string>('popular')
const selectedNavItem = ref<string | null>('installed') const selectedNavItem = ref<string | null>('installed')
const gridStyle = computed(() => createGridStyle()) const gridStyle = computed(() => createGridStyle())
watch(searchText, (newQuery) => {
console.log('searchText:', searchText.value, newQuery)
})
watch(searchQuery, (newQuery) => {
console.log('searchQuery:', searchQuery.value, newQuery)
})
</script> </script>

View File

@@ -86,7 +86,7 @@ const createStoryTemplate = (args: StoryArgs) => ({
const t = (k: string) => k const t = (k: string) => k
const onClose = () => { const onClose = () => {
console.log('OnClose invoked') // OnClose handler for story
} }
provide(OnCloseKey, onClose) provide(OnCloseKey, onClose)

View File

@@ -300,9 +300,6 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
const modeValue = String(modeWidget.value) const modeValue = String(modeWidget.value)
const durationValue = String(durationWidget.value) const durationValue = String(durationWidget.value)
const modelValue = String(modelWidget.value) const modelValue = String(modelWidget.value)
console.log('modelValue', modelValue)
console.log('modeValue', modeValue)
console.log('durationValue', durationValue)
// Same pricing matrix as KlingTextToVideoNode // Same pricing matrix as KlingTextToVideoNode
if (modelValue.includes('v1-6') || modelValue.includes('v1-5')) { if (modelValue.includes('v1-6') || modelValue.includes('v1-5')) {
@@ -356,9 +353,6 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
const modeValue = String(modeWidget.value) const modeValue = String(modeWidget.value)
const durationValue = String(durationWidget.value) const durationValue = String(durationWidget.value)
const modelValue = String(modelWidget.value) const modelValue = String(modelWidget.value)
console.log('modelValue', modelValue)
console.log('modeValue', modeValue)
console.log('durationValue', durationValue)
// Same pricing matrix as KlingTextToVideoNode // Same pricing matrix as KlingTextToVideoNode
if ( if (
@@ -564,9 +558,6 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
const model = String(modelWidget.value) const model = String(modelWidget.value)
const resolution = String(resolutionWidget.value).toLowerCase() const resolution = String(resolutionWidget.value).toLowerCase()
const duration = String(durationWidget.value) const duration = String(durationWidget.value)
console.log('model', model)
console.log('resolution', resolution)
console.log('duration', duration)
if (model.includes('ray-flash-2')) { if (model.includes('ray-flash-2')) {
if (duration.includes('5s')) { if (duration.includes('5s')) {

View File

@@ -344,7 +344,7 @@ export const SERVER_CONFIG_ITEMS: ServerConfig<any>[] = [
type: 'number', type: 'number',
defaultValue: null, defaultValue: null,
tooltip: tooltip:
'Set the amount of vram in GB you want to reserve for use by your OS/other software. By default some amount is reverved depending on your OS.' 'Set the amount of vram in GB you want to reserve for use by your OS/other software. By default some amount is reserved depending on your OS.'
}, },
// Misc settings // Misc settings

View File

@@ -135,7 +135,7 @@ function addProxyFromOverlay(subgraphNode: SubgraphNode, overlay: Overlay) {
* @param {string} property - The name of the accessed value. * @param {string} property - The name of the accessed value.
* Checked for conditional logic, but never changed * Checked for conditional logic, but never changed
* @param {object} receiver - The object the result is set to * @param {object} receiver - The object the result is set to
* and the vlaue used as 'this' if property is a get/set method * and the value used as 'this' if property is a get/set method
* @param {unknown} value - only used on set calls. The thing being assigned * @param {unknown} value - only used on set calls. The thing being assigned
*/ */
const handler = { const handler = {

View File

@@ -147,7 +147,7 @@ app.registerExtension({
// @ts-expect-error fixme ts strict error // @ts-expect-error fixme ts strict error
node[WEBCAM_READY].then((v) => { node[WEBCAM_READY].then((v) => {
video = v video = v
// If width isnt specified then use video output resolution // If width isn't specified then use video output resolution
// @ts-expect-error fixme ts strict error // @ts-expect-error fixme ts strict error
if (!w.value) { if (!w.value) {
// @ts-expect-error fixme ts strict error // @ts-expect-error fixme ts strict error

View File

@@ -149,7 +149,7 @@ export class PrimitiveNode extends LGraphNode {
target_slot: number target_slot: number
) { ) {
// Fires before the link is made allowing us to reject it if it isn't valid // Fires before the link is made allowing us to reject it if it isn't valid
// No widget, we cant connect // No widget, we can't connect
if (!input.widget && !(input.type in ComfyWidgets)) { if (!input.widget && !(input.type in ComfyWidgets)) {
return false return false
} }
@@ -388,7 +388,7 @@ export class PrimitiveNode extends LGraphNode {
} }
onLastDisconnect() { onLastDisconnect() {
// We cant remove + re-add the output here as if you drag a link over the same link // We can't remove + re-add the output here as if you drag a link over the same link
// it removes, then re-adds, causing it to break // it removes, then re-adds, causing it to break
this.outputs[0].type = '*' this.outputs[0].type = '*'
this.outputs[0].name = 'connect to widget input' this.outputs[0].name = 'connect to widget input'
@@ -595,7 +595,7 @@ app.registerExtension({
this.graph?.add(node) this.graph?.add(node)
// Calculate a position that wont directly overlap another node // Calculate a position that won't directly overlap another node
const pos: [number, number] = [ const pos: [number, number] = [
this.pos[0] - node.size[0] - 30, this.pos[0] - node.size[0] - 30,
this.pos[1] this.pos[1]

View File

@@ -146,8 +146,8 @@ Litegraph has no runtime dependencies. The build tooling has been tested on Node
Use GitHub actions to release normal versions. Use GitHub actions to release normal versions.
1. Run the `Release a New Version` action, selecting the version incrment type 1. Run the `Release a New Version` action, selecting the version increment type
1. Merge the resultion PR 1. Merge the resolution PR
1. A GitHub release is automatically published on merge 1. A GitHub release is automatically published on merge
### Pre-release ### Pre-release

View File

@@ -274,8 +274,6 @@ export class LGraph
* @param o data from previous serialization [optional] * @param o data from previous serialization [optional]
*/ */
constructor(o?: ISerialisedGraph | SerialisableGraph) { constructor(o?: ISerialisedGraph | SerialisableGraph) {
if (LiteGraph.debug) console.log('Graph created')
/** @see MapProxyHandler */ /** @see MapProxyHandler */
const links = this._links const links = this._links
MapProxyHandler.bindAllMethods(links) MapProxyHandler.bindAllMethods(links)
@@ -532,7 +530,7 @@ export class LGraph
this.errors_in_execution = true this.errors_in_execution = true
if (LiteGraph.throw_errors) throw error if (LiteGraph.throw_errors) throw error
if (LiteGraph.debug) console.log('Error during execution:', error) if (LiteGraph.debug) console.error('Error during execution:', error)
this.stop() this.stop()
} }
} }
@@ -1128,7 +1126,7 @@ export class LGraph
/** /**
* Snaps the provided items to a grid. * Snaps the provided items to a grid.
* *
* Item positions are reounded to the nearest multiple of {@link LiteGraph.CANVAS_GRID_SIZE}. * Item positions are rounded to the nearest multiple of {@link LiteGraph.CANVAS_GRID_SIZE}.
* *
* When {@link LiteGraph.alwaysSnapToGrid} is enabled * When {@link LiteGraph.alwaysSnapToGrid} is enabled
* and the grid size is falsy, a default of 1 is used. * and the grid size is falsy, a default of 1 is used.
@@ -1167,7 +1165,7 @@ export class LGraph
const ctor = LiteGraph.registered_node_types[node.type] const ctor = LiteGraph.registered_node_types[node.type]
if (node.constructor == ctor) continue if (node.constructor == ctor) continue
console.log('node being replaced by newer version:', node.type) console.warn('node being replaced by newer version:', node.type)
const newnode = LiteGraph.createNode(node.type) const newnode = LiteGraph.createNode(node.type)
if (!newnode) continue if (!newnode) continue
_nodes[i] = newnode _nodes[i] = newnode
@@ -1229,9 +1227,6 @@ export class LGraph
/* Called when something visually changed (not the graph!) */ /* Called when something visually changed (not the graph!) */
change(): void { change(): void {
if (LiteGraph.debug) {
console.log('Graph changed')
}
this.canvasAction((c) => c.setDirty(true, true)) this.canvasAction((c) => c.setDirty(true, true))
this.on_change?.(this) this.on_change?.(this)
} }
@@ -1626,12 +1621,6 @@ export class LGraph
} else { } else {
throw new TypeError('Subgraph input node is not a SubgraphInput') throw new TypeError('Subgraph input node is not a SubgraphInput')
} }
console.debug(
'Reconnect input links in parent graph',
{ ...link },
this.links.get(link.id),
this.links.get(link.id) === link
)
for (const resolved of others) { for (const resolved of others) {
resolved.link.disconnect(this) resolved.link.disconnect(this)
@@ -2233,7 +2222,7 @@ export class LGraph
let node = LiteGraph.createNode(String(n_info.type), n_info.title) let node = LiteGraph.createNode(String(n_info.type), n_info.title)
if (!node) { if (!node) {
if (LiteGraph.debug) if (LiteGraph.debug)
console.log('Node not found or has errors:', n_info.type) console.warn('Node not found or has errors:', n_info.type)
// in case of error we create a replacement node to avoid losing info // in case of error we create a replacement node to avoid losing info
node = new LGraphNode('') node = new LGraphNode('')

View File

@@ -461,7 +461,7 @@ export class LGraphCanvas
} }
const baseFontSize = LiteGraph.NODE_TEXT_SIZE // 14px const baseFontSize = LiteGraph.NODE_TEXT_SIZE // 14px
const dprAdjustment = Math.sqrt(window.devicePixelRatio || 1) //Using sqrt here because higher DPR monitors do not linearily scale the readability of the font, instead they increase the font by some heurisitc, and to approximate we use sqrt to say bascially a DPR of 2 increases the readibility by 40%, 3 by 70% const dprAdjustment = Math.sqrt(window.devicePixelRatio || 1) //Using sqrt here because higher DPR monitors do not linearily scale the readability of the font, instead they increase the font by some heurisitc, and to approximate we use sqrt to say basically a DPR of 2 increases the readability by 40%, 3 by 70%
// Calculate the zoom level where text becomes unreadable // Calculate the zoom level where text becomes unreadable
this._lowQualityZoomThreshold = this._lowQualityZoomThreshold =
@@ -547,7 +547,7 @@ export class LGraphCanvas
linkMarkerShape: LinkMarkerShape = LinkMarkerShape.Circle linkMarkerShape: LinkMarkerShape = LinkMarkerShape.Circle
links_render_mode: number links_render_mode: number
/** Minimum font size in pixels before switching to low quality rendering. /** Minimum font size in pixels before switching to low quality rendering.
* This intializes first and if we cant get the value from the settings we default to 8px * This initializes first and if we can't get the value from the settings we default to 8px
*/ */
private _min_font_size_for_lod: number = 8 private _min_font_size_for_lod: number = 8
@@ -1228,7 +1228,7 @@ export class LGraphCanvas
className: 'event' className: 'event'
}) })
} }
// add callback for modifing the menu elements onMenuNodeOutputs // add callback for modifying the menu elements onMenuNodeOutputs
const retEntries = node.onMenuNodeOutputs?.(entries) const retEntries = node.onMenuNodeOutputs?.(entries)
if (retEntries) entries = retEntries if (retEntries) entries = retEntries
@@ -3902,7 +3902,7 @@ export class LGraphCanvas
for (const item of [...parsed.nodes, ...parsed.reroutes]) { for (const item of [...parsed.nodes, ...parsed.reroutes]) {
if (item.pos == null) if (item.pos == null)
throw new TypeError( throw new TypeError(
'Invalid node encounterd on paste. `pos` was null.' 'Invalid node encountered on paste. `pos` was null.'
) )
if (item.pos[0] < offsetX) offsetX = item.pos[0] if (item.pos[0] < offsetX) offsetX = item.pos[0]
@@ -6406,7 +6406,7 @@ export class LGraphCanvas
return true return true
} }
console.log(`failed creating ${nodeNewType}`) console.error(`failed creating ${nodeNewType}`)
} }
} }
return false return false
@@ -6818,7 +6818,7 @@ export class LGraphCanvas
canvas.focus() canvas.focus()
root_document.body.style.overflow = '' root_document.body.style.overflow = ''
// important, if canvas loses focus keys wont be captured // important, if canvas loses focus keys won't be captured
setTimeout(() => canvas.focus(), 20) setTimeout(() => canvas.focus(), 20)
dialog.remove() dialog.remove()
} }
@@ -7095,7 +7095,7 @@ export class LGraphCanvas
) )
} }
} else { } else {
// console.warn("cant find slot " + options.slot_from); // console.warn("can't find slot " + options.slot_from);
} }
} }
if (options.node_to) { if (options.node_to) {
@@ -7140,7 +7140,7 @@ export class LGraphCanvas
) )
} }
} else { } else {
// console.warn("cant find slot_nodeTO " + options.slot_from); // console.warn("can't find slot_nodeTO " + options.slot_from);
} }
} }
@@ -7478,7 +7478,7 @@ export class LGraphCanvas
return dialog return dialog
} }
// TODO refactor, theer are different dialog, some uses createDialog, some dont // TODO refactor, there are different dialog, some uses createDialog, some dont
createDialog(html: string, options: IDialogOptions): IDialog { createDialog(html: string, options: IDialogOptions): IDialog {
const def_options = { const def_options = {
checkForInput: false, checkForInput: false,

View File

@@ -167,8 +167,8 @@ input|output: every connection
general properties: general properties:
+ clip_area: if you render outside the node, it will be clipped + clip_area: if you render outside the node, it will be clipped
+ unsafe_execution: not allowed for safe execution + unsafe_execution: not allowed for safe execution
+ skip_repeated_outputs: when adding new outputs, it wont show if there is one already connected + skip_repeated_outputs: when adding new outputs, it won't show if there is one already connected
+ resizable: if set to false it wont be resizable with the mouse + resizable: if set to false it won't be resizable with the mouse
+ widgets_start_y: widgets start at y distance from the top of the node + widgets_start_y: widgets start at y distance from the top of the node
flags object: flags object:
@@ -902,7 +902,7 @@ export class LGraphNode
if (this.onSerialize?.(o)) if (this.onSerialize?.(o))
console.warn( console.warn(
'node onSerialize shouldnt return anything, data should be stored in the object pass in the first parameter' "node onSerialize shouldn't return anything, data should be stored in the object pass in the first parameter"
) )
return o return o
@@ -1950,7 +1950,7 @@ export class LGraphNode
try { try {
this.removeWidget(widget) this.removeWidget(widget)
} catch (error) { } catch (error) {
console.debug('Failed to remove widget', error) console.error('Failed to remove widget', error)
} }
} }
@@ -2351,7 +2351,7 @@ export class LGraphNode
/** /**
* returns the output (or input) slot with a given type, -1 if not found * returns the output (or input) slot with a given type, -1 if not found
* @param input uise inputs instead of outputs * @param input use inputs instead of outputs
* @param type the type of the slot to find * @param type the type of the slot to find
* @param returnObj if the obj itself wanted * @param returnObj if the obj itself wanted
* @param preferFreeSlot if we want a free slot (if not found, will return the first of the type anyway) * @param preferFreeSlot if we want a free slot (if not found, will return the first of the type anyway)
@@ -2583,12 +2583,7 @@ export class LGraphNode
if (slotIndex !== undefined) if (slotIndex !== undefined)
return this.connect(slot, target_node, slotIndex, optsIn?.afterRerouteId) return this.connect(slot, target_node, slotIndex, optsIn?.afterRerouteId)
console.debug( // No compatible slot found - connection not possible
'[connectByType]: no way to connect type:',
target_slotType,
'to node:',
target_node
)
return null return null
} }
@@ -2621,7 +2616,7 @@ export class LGraphNode
if (slotIndex !== undefined) if (slotIndex !== undefined)
return source_node.connect(slotIndex, this, slot, optsIn?.afterRerouteId) return source_node.connect(slotIndex, this, slot, optsIn?.afterRerouteId)
console.debug( console.error(
'[connectByType]: no way to connect type:', '[connectByType]: no way to connect type:',
source_slotType, source_slotType,
'to node:', 'to node:',
@@ -2661,7 +2656,7 @@ export class LGraphNode
if (!graph) { if (!graph) {
// could be connected before adding it to a graph // could be connected before adding it to a graph
// due to link ids being associated with graphs // due to link ids being associated with graphs
console.log( console.error(
"Connect: Error, node doesn't belong to any graph. Nodes must be added first to a graph before connecting them." "Connect: Error, node doesn't belong to any graph. Nodes must be added first to a graph before connecting them."
) )
return null return null
@@ -2672,11 +2667,12 @@ export class LGraphNode
slot = this.findOutputSlot(slot) slot = this.findOutputSlot(slot)
if (slot == -1) { if (slot == -1) {
if (LiteGraph.debug) if (LiteGraph.debug)
console.log(`Connect: Error, no slot of name ${slot}`) console.error(`Connect: Error, no slot of name ${slot}`)
return null return null
} }
} else if (!outputs || slot >= outputs.length) { } else if (!outputs || slot >= outputs.length) {
if (LiteGraph.debug) console.log('Connect: Error, slot number not found') if (LiteGraph.debug)
console.error('Connect: Error, slot number not found')
return null return null
} }
@@ -2696,7 +2692,7 @@ export class LGraphNode
targetIndex = target_node.findInputSlot(target_slot) targetIndex = target_node.findInputSlot(target_slot)
if (targetIndex == -1) { if (targetIndex == -1) {
if (LiteGraph.debug) if (LiteGraph.debug)
console.log(`Connect: Error, no slot of name ${targetIndex}`) console.error(`Connect: Error, no slot of name ${targetIndex}`)
return null return null
} }
} else if (target_slot === LiteGraph.EVENT) { } else if (target_slot === LiteGraph.EVENT) {
@@ -2728,7 +2724,8 @@ export class LGraphNode
!target_node.inputs || !target_node.inputs ||
targetIndex >= target_node.inputs.length targetIndex >= target_node.inputs.length
) { ) {
if (LiteGraph.debug) console.log('Connect: Error, slot number not found') if (LiteGraph.debug)
console.error('Connect: Error, slot number not found')
return null return null
} }
@@ -2914,7 +2911,7 @@ export class LGraphNode
const fromLastFloatingReroute = const fromLastFloatingReroute =
parentReroute?.floating?.slotType === 'output' parentReroute?.floating?.slotType === 'output'
// Adding from an ouput, or a floating reroute that is NOT the tip of an existing floating chain // Adding from an output, or a floating reroute that is NOT the tip of an existing floating chain
if (afterRerouteId == null || !fromLastFloatingReroute) { if (afterRerouteId == null || !fromLastFloatingReroute) {
const link = new LLink( const link = new LLink(
-1, -1,
@@ -2955,11 +2952,12 @@ export class LGraphNode
slot = this.findOutputSlot(slot) slot = this.findOutputSlot(slot)
if (slot == -1) { if (slot == -1) {
if (LiteGraph.debug) if (LiteGraph.debug)
console.log(`Connect: Error, no slot of name ${slot}`) console.error(`Connect: Error, no slot of name ${slot}`)
return false return false
} }
} else if (!this.outputs || slot >= this.outputs.length) { } else if (!this.outputs || slot >= this.outputs.length) {
if (LiteGraph.debug) console.log('Connect: Error, slot number not found') if (LiteGraph.debug)
console.error('Connect: Error, slot number not found')
return false return false
} }
@@ -3075,19 +3073,19 @@ export class LGraphNode
slot = this.findInputSlot(slot) slot = this.findInputSlot(slot)
if (slot == -1) { if (slot == -1) {
if (LiteGraph.debug) if (LiteGraph.debug)
console.log(`Connect: Error, no slot of name ${slot}`) console.error(`Connect: Error, no slot of name ${slot}`)
return false return false
} }
} else if (!this.inputs || slot >= this.inputs.length) { } else if (!this.inputs || slot >= this.inputs.length) {
if (LiteGraph.debug) { if (LiteGraph.debug) {
console.log('Connect: Error, slot number not found') console.error('Connect: Error, slot number not found')
} }
return false return false
} }
const input = this.inputs[slot] const input = this.inputs[slot]
if (!input) { if (!input) {
console.debug('disconnectInput: input not found', slot, this.inputs) console.error('disconnectInput: input not found', slot, this.inputs)
return false return false
} }
@@ -3116,19 +3114,16 @@ export class LGraphNode
const target_node = graph.getNodeById(link_info.origin_id) const target_node = graph.getNodeById(link_info.origin_id)
if (!target_node) { if (!target_node) {
console.debug( console.error(
'disconnectInput: target node not found', 'disconnectInput: output not found',
link_info.origin_id link_info.origin_slot
) )
return false return false
} }
const output = target_node.outputs[link_info.origin_slot] const output = target_node.outputs[link_info.origin_slot]
if (!output?.links?.length) { if (!output?.links?.length) {
console.debug( // Output not found - may have been removed
'disconnectInput: output not found',
link_info.origin_slot
)
return false return false
} }

View File

@@ -241,10 +241,10 @@ export class LiteGraphGlobal {
*/ */
do_add_triggers_slots = false do_add_triggers_slots = false
/** [false!] being events, it is strongly reccomended to use them sequentially, one by one */ /** [false!] being events, it is strongly recommended to use them sequentially, one by one */
allow_multi_output_for_events = true allow_multi_output_for_events = true
/** [true!] allows to create and connect a ndoe clicking with the third button (wheel) */ /** [true!] allows to create and connect a node clicking with the third button (wheel) */
middle_click_slot_add_default_node = false middle_click_slot_add_default_node = false
/** [true!] dragging a link to empty space will open a menu, add from list, search or defaults */ /** [true!] dragging a link to empty space will open a menu, add from list, search or defaults */
@@ -398,8 +398,6 @@ export class LiteGraphGlobal {
throw 'Cannot register a simple object, it must be a class with a prototype' throw 'Cannot register a simple object, it must be a class with a prototype'
base_class.type = type base_class.type = type
if (this.debug) console.log('Node registered:', type)
const classname = base_class.name const classname = base_class.name
const pos = type.lastIndexOf('/') const pos = type.lastIndexOf('/')
@@ -415,7 +413,7 @@ export class LiteGraphGlobal {
const prev = this.registered_node_types[type] const prev = this.registered_node_types[type]
if (prev && this.debug) { if (prev && this.debug) {
console.log('replacing node type:', type) console.warn('replacing node type:', type)
} }
this.registered_node_types[type] = base_class this.registered_node_types[type] = base_class
@@ -430,7 +428,7 @@ export class LiteGraphGlobal {
`LiteGraph node class ${type} has onPropertyChange method, it must be called onPropertyChanged with d at the end` `LiteGraph node class ${type} has onPropertyChange method, it must be called onPropertyChanged with d at the end`
) )
// TODO one would want to know input and ouput :: this would allow through registerNodeAndSlotType to get all the slots types // TODO one would want to know input and output :: this would allow through registerNodeAndSlotType to get all the slots types
if (this.auto_load_slot_types) new base_class(base_class.title || 'tmpnode') if (this.auto_load_slot_types) new base_class(base_class.title || 'tmpnode')
} }
@@ -524,7 +522,7 @@ export class LiteGraphGlobal {
): LGraphNode | null { ): LGraphNode | null {
const base_class = this.registered_node_types[type] const base_class = this.registered_node_types[type]
if (!base_class) { if (!base_class) {
if (this.debug) console.log(`GraphNode type "${type}" not registered.`) if (this.debug) console.warn(`GraphNode type "${type}" not registered.`)
return null return null
} }
@@ -637,7 +635,6 @@ export class LiteGraphGlobal {
continue continue
try { try {
if (this.debug) console.log('Reloading:', src)
const dynamicScript = document.createElement('script') const dynamicScript = document.createElement('script')
dynamicScript.type = 'text/javascript' dynamicScript.type = 'text/javascript'
dynamicScript.src = src dynamicScript.src = src
@@ -645,11 +642,9 @@ export class LiteGraphGlobal {
script_file.remove() script_file.remove()
} catch (error) { } catch (error) {
if (this.throw_errors) throw error if (this.throw_errors) throw error
if (this.debug) console.log('Error while reloading', src) if (this.debug) console.error('Error while reloading', src)
} }
} }
if (this.debug) console.log('Nodes reloaded')
} }
// separated just to improve if it doesn't work // separated just to improve if it doesn't work
@@ -749,7 +744,7 @@ export class LiteGraphGlobal {
// convert pointerevents to touch event when not available // convert pointerevents to touch event when not available
if (sMethod == 'pointer' && !window.PointerEvent) { if (sMethod == 'pointer' && !window.PointerEvent) {
console.warn("sMethod=='pointer' && !window.PointerEvent") console.warn("sMethod=='pointer' && !window.PointerEvent")
console.log( console.warn(
`Converting pointer[${sEvent}] : down move up cancel enter TO touchstart touchmove touchend, etc ..` `Converting pointer[${sEvent}] : down move up cancel enter TO touchstart touchmove touchend, etc ..`
) )
switch (sEvent) { switch (sEvent) {
@@ -774,7 +769,7 @@ export class LiteGraphGlobal {
break break
} }
case 'enter': { case 'enter': {
console.log('debug: Should I send a move event?') // ??? // TODO: Determine if a move event should be sent
break break
} }
// case "over": case "out": not used at now // case "over": case "out": not used at now

View File

@@ -906,7 +906,6 @@ export class LinkConnector {
if (connectingTo === 'output') { if (connectingTo === 'output') {
// Dropping new output link // Dropping new output link
const output = node.findOutputByType(firstLink.fromSlot.type)?.slot const output = node.findOutputByType(firstLink.fromSlot.type)?.slot
console.debug('out', node, output, firstLink.fromSlot)
if (output === undefined) { if (output === undefined) {
console.warn( console.warn(
`Could not find slot for link type: [${firstLink.fromSlot.type}].` `Could not find slot for link type: [${firstLink.fromSlot.type}].`
@@ -918,7 +917,6 @@ export class LinkConnector {
} else if (connectingTo === 'input') { } else if (connectingTo === 'input') {
// Dropping new input link // Dropping new input link
const input = node.findInputByType(firstLink.fromSlot.type)?.slot const input = node.findInputByType(firstLink.fromSlot.type)?.slot
console.debug('in', node, input, firstLink.fromSlot)
if (input === undefined) { if (input === undefined) {
console.warn( console.warn(
`Could not find slot for link type: [${firstLink.fromSlot.type}].` `Could not find slot for link type: [${firstLink.fromSlot.type}].`

View File

@@ -57,7 +57,7 @@ export class ExecutableNodeDTO implements ExecutableLGraphNode {
#id: ExecutionId #id: ExecutionId
/** /**
* The path to the acutal node through subgraph instances, represented as a list of all subgraph node IDs (instances), * The path to the actual node through subgraph instances, represented as a list of all subgraph node IDs (instances),
* followed by the actual original node ID within the subgraph. Each segment is separated by `:`. * followed by the actual original node ID within the subgraph. Each segment is separated by `:`.
* *
* e.g. `1:2:3`: * e.g. `1:2:3`:
@@ -104,7 +104,7 @@ export class ExecutableNodeDTO implements ExecutableLGraphNode {
readonly subgraphNodePath: readonly NodeId[], readonly subgraphNodePath: readonly NodeId[],
/** A flattened map of all DTOs in this node network. Subgraph instances have been expanded into their inner nodes. */ /** A flattened map of all DTOs in this node network. Subgraph instances have been expanded into their inner nodes. */
readonly nodesByExecutionId: Map<ExecutionId, ExecutableLGraphNode>, readonly nodesByExecutionId: Map<ExecutionId, ExecutableLGraphNode>,
/** The actual subgraph instance that contains this node, otherise undefined. */ /** The actual subgraph instance that contains this node, otherwise undefined. */
readonly subgraphNode?: SubgraphNode readonly subgraphNode?: SubgraphNode
) { ) {
if (!node.graph) throw new NullGraphError() if (!node.graph) throw new NullGraphError()
@@ -271,9 +271,9 @@ export class ExecutableNodeDTO implements ExecutableLGraphNode {
// Bypass nodes by finding first input with matching type // Bypass nodes by finding first input with matching type
const matchingIndex = this.#getBypassSlotIndex(slot, type) const matchingIndex = this.#getBypassSlotIndex(slot, type)
// No input types match // No input types match - bypass not possible
if (matchingIndex === -1) { if (matchingIndex === -1) {
console.debug( console.warn(
`[ExecutableNodeDTO.resolveOutput] No input types match type [${type}] for id [${this.id}] slot [${slot}]`, `[ExecutableNodeDTO.resolveOutput] No input types match type [${type}] for id [${this.id}] slot [${slot}]`,
this this
) )

View File

@@ -175,8 +175,6 @@ export class SubgraphInput extends SubgraphSlot {
} }
widgets.push(widget) widgets.push(widget)
} else {
console.debug('No input found on link id', linkId, link)
} }
} }
return widgets return widgets

View File

@@ -188,7 +188,7 @@ export class SubgraphInputNode
const subgraphInput = this.slots.at(subgraphInputIndex) const subgraphInput = this.slots.at(subgraphInputIndex)
if (!subgraphInput) { if (!subgraphInput) {
console.debug( console.warn(
'disconnectNodeInput: subgraphInput not found', 'disconnectNodeInput: subgraphInput not found',
this, this,
subgraphInputIndex subgraphInputIndex
@@ -201,7 +201,7 @@ export class SubgraphInputNode
if (index !== -1) { if (index !== -1) {
subgraphInput.linkIds.splice(index, 1) subgraphInput.linkIds.splice(index, 1)
} else { } else {
console.debug( console.warn(
'disconnectNodeInput: link ID not found in subgraphInput linkIds', 'disconnectNodeInput: link ID not found in subgraphInput linkIds',
link.id link.id
) )

View File

@@ -430,7 +430,7 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph {
const inputSlot = this.subgraph.inputNode.slots[slot] const inputSlot = this.subgraph.inputNode.slots[slot]
const innerLinks = inputSlot.getLinks() const innerLinks = inputSlot.getLinks()
if (innerLinks.length === 0) { if (innerLinks.length === 0) {
console.debug( console.warn(
`[SubgraphNode.resolveSubgraphInputLinks] No inner links found for input slot [${slot}] ${inputSlot.name}`, `[SubgraphNode.resolveSubgraphInputLinks] No inner links found for input slot [${slot}] ${inputSlot.name}`,
this this
) )
@@ -447,9 +447,10 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph {
resolveSubgraphOutputLink(slot: number): ResolvedConnection | undefined { resolveSubgraphOutputLink(slot: number): ResolvedConnection | undefined {
const outputSlot = this.subgraph.outputNode.slots[slot] const outputSlot = this.subgraph.outputNode.slots[slot]
const innerLink = outputSlot.getLinks().at(0) const innerLink = outputSlot.getLinks().at(0)
if (innerLink) return innerLink.resolve(this.subgraph) if (innerLink) {
return innerLink.resolve(this.subgraph)
console.debug( }
console.warn(
`[SubgraphNode.resolveSubgraphOutputLink] No inner link found for output slot [${slot}] ${outputSlot.name}`, `[SubgraphNode.resolveSubgraphOutputLink] No inner link found for output slot [${slot}] ${outputSlot.name}`,
this this
) )

View File

@@ -116,7 +116,7 @@ export function getBoundaryLinks(
const resolved = LLink.resolve(input.link, graph) const resolved = LLink.resolve(input.link, graph)
if (!resolved) { if (!resolved) {
console.debug(`Failed to resolve link ID [${input.link}]`) console.warn(`Failed to resolve link ID [${input.link}]`)
continue continue
} }

View File

@@ -157,7 +157,7 @@ export interface SubgraphIO extends SubgraphIOShared {
id: UUID id: UUID
/** The data type this slot uses. Unlike nodes, this does not support legacy numeric types. */ /** The data type this slot uses. Unlike nodes, this does not support legacy numeric types. */
type: string type: string
/** Links connected to this slot, or `undefined` if not connected. An ouptut slot should only ever have one link. */ /** Links connected to this slot, or `undefined` if not connected. An output slot should only ever have one link. */
linkIds?: LinkId[] linkIds?: LinkId[]
} }

View File

@@ -426,7 +426,6 @@ describe('ExecutableNodeDTO Integration', () => {
describe('ExecutableNodeDTO Scale Testing', () => { describe('ExecutableNodeDTO Scale Testing', () => {
it('should create DTOs at scale', () => { it('should create DTOs at scale', () => {
const graph = new LGraph() const graph = new LGraph()
const startTime = performance.now()
const dtos: ExecutableNodeDTO[] = [] const dtos: ExecutableNodeDTO[] = []
// Create DTOs to test performance // Create DTOs to test performance
@@ -440,16 +439,11 @@ describe('ExecutableNodeDTO Scale Testing', () => {
dtos.push(dto) dtos.push(dto)
} }
const endTime = performance.now()
const duration = endTime - startTime
expect(dtos).toHaveLength(1000) expect(dtos).toHaveLength(1000)
// Test deterministic properties instead of flaky timing // Test deterministic properties instead of flaky timing
expect(dtos[0].id).toBe('parent:0') expect(dtos[0].id).toBe('parent:0')
expect(dtos[999].id).toBe('parent:999') expect(dtos[999].id).toBe('parent:999')
expect(dtos.every((dto, i) => dto.id === `parent:${i}`)).toBe(true) expect(dtos.every((dto, i) => dto.id === `parent:${i}`)).toBe(true)
console.log(`Created 1000 DTOs in ${duration.toFixed(2)}ms`)
}) })
it('should handle complex path generation correctly', () => { it('should handle complex path generation correctly', () => {

View File

@@ -56,7 +56,7 @@ describe('SubgraphConversion', () => {
expect(graph.nodes.length).toBe(2) expect(graph.nodes.length).toBe(2)
expect(graph.links.size).toBe(1) expect(graph.links.size).toBe(1)
}) })
it('Should merge boundry links', () => { it('Should merge boundary links', () => {
const subgraph = createTestSubgraph({ const subgraph = createTestSubgraph({
inputs: [{ name: 'value', type: 'number' }], inputs: [{ name: 'value', type: 'number' }],
outputs: [{ name: 'value', type: 'number' }] outputs: [{ name: 'value', type: 'number' }]

View File

@@ -134,7 +134,7 @@ describe('SubgraphSlot visual feedback', () => {
expect(globalAlphaValues).toContain(0.4) expect(globalAlphaValues).toContain(0.4)
}) })
// "not implmeneted yet" // "not implemented yet"
// it("should render slots with full opacity when dragging between compatible SubgraphInput and SubgraphOutput", () => { // it("should render slots with full opacity when dragging between compatible SubgraphInput and SubgraphOutput", () => {
// const subgraph = createTestSubgraph() // const subgraph = createTestSubgraph()

View File

@@ -57,12 +57,10 @@ export const Default: Story = {
render: (args) => ({ render: (args) => ({
components: { AssetBrowserModal }, components: { AssetBrowserModal },
setup() { setup() {
const onAssetSelect = (asset: AssetDisplayItem) => { const onAssetSelect = (_asset: AssetDisplayItem) => {
console.log('Selected asset:', asset) // Asset selection handler for story
}
const onClose = () => {
console.log('Modal closed')
} }
const onClose = () => {}
return { return {
...args, ...args,
@@ -97,11 +95,11 @@ export const SingleAssetType: Story = {
render: (args) => ({ render: (args) => ({
components: { AssetBrowserModal }, components: { AssetBrowserModal },
setup() { setup() {
const onAssetSelect = (asset: AssetDisplayItem) => { const onAssetSelect = (_asset: AssetDisplayItem) => {
console.log('Selected asset:', asset) // Asset selection handler for story
} }
const onClose = () => { const onClose = () => {
console.log('Modal closed') // Modal close handler for story
} }
// Create assets with only one type (checkpoints) // Create assets with only one type (checkpoints)
@@ -146,11 +144,11 @@ export const NoLeftPanel: Story = {
render: (args) => ({ render: (args) => ({
components: { AssetBrowserModal }, components: { AssetBrowserModal },
setup() { setup() {
const onAssetSelect = (asset: AssetDisplayItem) => { const onAssetSelect = (_asset: AssetDisplayItem) => {
console.log('Selected asset:', asset) // Asset selection handler for story
} }
const onClose = () => { const onClose = () => {
console.log('Modal closed') // Modal close handler for story
} }
return { ...args, onAssetSelect, onClose, assets: mockAssets } return { ...args, onAssetSelect, onClose, assets: mockAssets }

View File

@@ -198,10 +198,6 @@ export function useAssetBrowser(assets: AssetItem[] = []) {
assetId: string, assetId: string,
onSelect?: (filename: string) => void onSelect?: (filename: string) => void
): Promise<void> { ): Promise<void> {
if (import.meta.env.DEV) {
console.debug('Asset selected:', assetId)
}
if (!onSelect) { if (!onSelect) {
return return
} }

View File

@@ -29,7 +29,6 @@ const DialogDemoComponent = {
} }
const handleAssetSelected = (assetPath: string) => { const handleAssetSelected = (assetPath: string) => {
console.log('Asset selected:', assetPath)
alert(`Selected asset: ${assetPath}`) alert(`Selected asset: ${assetPath}`)
isDialogOpen.value = false // Auto-close like the real composable isDialogOpen.value = false // Auto-close like the real composable
} }

View File

@@ -986,7 +986,7 @@ export const CORE_SETTINGS: SettingParams[] = [
name: 'Auto Save', name: 'Auto Save',
type: 'combo', type: 'combo',
options: ['off', 'after delay'], // Room for other options like on focus change, tab change, window change options: ['off', 'after delay'], // Room for other options like on focus change, tab change, window change
defaultValue: 'off', // Popular requst by users (https://github.com/Comfy-Org/ComfyUI_frontend/issues/1584#issuecomment-2536610154) defaultValue: 'off', // Popular request by users (https://github.com/Comfy-Org/ComfyUI_frontend/issues/1584#issuecomment-2536610154)
versionAdded: '1.16.0' versionAdded: '1.16.0'
}, },
{ {

View File

@@ -195,7 +195,7 @@ defineExpose({
bottom: calc( bottom: calc(
var(--sidebar-width) * 2 + var(--sidebar-icon-size) / 2 - var(--sidebar-width) * 2 + var(--sidebar-icon-size) / 2 -
var(--whats-new-popup-bottom) var(--whats-new-popup-bottom)
); /* Position to center of help center icon (2 icons below + half icon height for center - whats new popup bottom position ) */ ); /* Position to center of help center icon (2 icons below + half icon height for center - what's new popup bottom position ) */
} }
/* Sidebar positioning classes applied by parent */ /* Sidebar positioning classes applied by parent */

View File

@@ -87,7 +87,6 @@ export class ComfyWorkflow extends UserFile {
} }
// Note: originalContent is populated by super.load() // Note: originalContent is populated by super.load()
console.debug('load and start tracking of workflow', this.path)
this.changeTracker = markRaw( this.changeTracker = markRaw(
new ChangeTracker( new ChangeTracker(
this, this,
@@ -98,7 +97,6 @@ export class ComfyWorkflow extends UserFile {
} }
override unload(): void { override unload(): void {
console.debug('unload workflow', this.path)
this.changeTracker = null this.changeTracker = null
super.unload() super.unload()
} }
@@ -302,7 +300,6 @@ export const useWorkflowStore = defineStore('workflow', () => {
const loadedWorkflow = await workflow.load() const loadedWorkflow = await workflow.load()
activeWorkflow.value = loadedWorkflow activeWorkflow.value = loadedWorkflow
comfyApp.canvas.bg_tint = loadedWorkflow.tintCanvasBg comfyApp.canvas.bg_tint = loadedWorkflow.tintCanvasBg
console.debug('[workflowStore] open workflow', workflow.path)
return loadedWorkflow return loadedWorkflow
} }
@@ -379,7 +376,6 @@ export const useWorkflowStore = defineStore('workflow', () => {
} else { } else {
workflow.unload() workflow.unload()
} }
console.debug('[workflowStore] close workflow', workflow.path)
} }
/** /**

View File

@@ -219,7 +219,7 @@ const zSubgraphIO = zNodeInput.extend({
id: z.string().uuid(), id: z.string().uuid(),
/** The data type this slot uses. Unlike nodes, this does not support legacy numeric types. */ /** The data type this slot uses. Unlike nodes, this does not support legacy numeric types. */
type: z.string(), type: z.string(),
/** Links connected to this slot, or `undefined` if not connected. An ouptut slot should only ever have one link. */ /** Links connected to this slot, or `undefined` if not connected. An output slot should only ever have one link. */
linkIds: z.array(z.number()).optional() linkIds: z.array(z.number()).optional()
}) })

View File

@@ -45,6 +45,7 @@
:widget="widget.simplified" :widget="widget.simplified"
:model-value="widget.value" :model-value="widget.value"
:readonly="readonly" :readonly="readonly"
:node-id="nodeData?.id != null ? String(nodeData.id) : ''"
class="flex-1" class="flex-1"
@update:model-value="widget.updateHandler" @update:model-value="widget.updateHandler"
/> />

View File

@@ -13,8 +13,8 @@ import { cn } from '@/utils/tailwindUtil'
* *
* *
* IMPORTANT: this escape is needed for many reason due to primevue's directive tooltip system. * IMPORTANT: this escape is needed for many reason due to primevue's directive tooltip system.
* We cannot use PT to conditonally render the tooltips because the entire PT object only run * We cannot use PT to conditionally render the tooltips because the entire PT object only run
* once during the intialization of the directive not every mount/unmount. * once during the initialization of the directive not every mount/unmount.
* Once the directive is constructed its no longer reactive in the traditional sense. * Once the directive is constructed its no longer reactive in the traditional sense.
* We have to use something non destructive like mouseevents to dismiss the tooltip. * We have to use something non destructive like mouseevents to dismiss the tooltip.
* *

View File

@@ -86,7 +86,6 @@ describe('WidgetInputNumberSlider Value Binding', () => {
it('renders input field', () => { it('renders input field', () => {
const widget = createMockWidget(5) const widget = createMockWidget(5)
const wrapper = mountComponent(widget, 5) const wrapper = mountComponent(widget, 5)
console.log(wrapper.html())
expect(wrapper.find('input[inputmode="numeric"]').exists()).toBe(true) expect(wrapper.find('input[inputmode="numeric"]').exists()).toBe(true)
}) })

View File

@@ -106,7 +106,7 @@ export const useImageUploadWidget = () => {
} }
// On load if we have a value then render the image // On load if we have a value then render the image
// The value isnt set immediately so we need to wait a moment // The value isn't set immediately so we need to wait a moment
// No change callbacks seem to be fired on initial setting of the value // No change callbacks seem to be fired on initial setting of the value
requestAnimationFrame(() => { requestAnimationFrame(() => {
nodeOutputStore.setNodeOutputs(node, fileComboWidget.value, { nodeOutputStore.setNodeOutputs(node, fileComboWidget.value, {

View File

@@ -524,7 +524,7 @@ export class ComfyApi extends EventTarget {
if (msg.data.sid) { if (msg.data.sid) {
const clientId = msg.data.sid const clientId = msg.data.sid
this.clientId = clientId this.clientId = clientId
window.name = clientId // use window name so it isnt reused when duplicating tabs window.name = clientId // use window name so it isn't reused when duplicating tabs
sessionStorage.setItem('clientId', clientId) // store in session storage so duplicate tab can load correct workflow sessionStorage.setItem('clientId', clientId) // store in session storage so duplicate tab can load correct workflow
} }
this.dispatchCustomEvent('status', msg.data.status ?? null) this.dispatchCustomEvent('status', msg.data.status ?? null)

View File

@@ -69,9 +69,6 @@ export const useRegistrySearchGateway = (): NodePackSearchProvider => {
const timeSinceLastAttempt = const timeSinceLastAttempt =
Date.now() - providerState.lastAttempt.getTime() Date.now() - providerState.lastAttempt.getTime()
if (timeSinceLastAttempt > CIRCUIT_BREAKER_TIMEOUT) { if (timeSinceLastAttempt > CIRCUIT_BREAKER_TIMEOUT) {
console.info(
`Retrying ${providerState.name} provider after circuit breaker timeout`
)
return true return true
} }
} }

View File

@@ -14,7 +14,7 @@ export interface ElectronDownload
status?: DownloadStatus status?: DownloadStatus
} }
/** Electron donwloads store handler */ /** Electron downloads store handler */
export const useElectronDownloadStore = defineStore('downloads', () => { export const useElectronDownloadStore = defineStore('downloads', () => {
const downloads = ref<ElectronDownload[]>([]) const downloads = ref<ElectronDownload[]>([])
const { DownloadManager } = electronAPI() const { DownloadManager } = electronAPI()

View File

@@ -404,10 +404,6 @@ export const useExecutionStore = defineStore('execution', () => {
...queuedPrompt.nodes ...queuedPrompt.nodes
} }
queuedPrompt.workflow = workflow queuedPrompt.workflow = workflow
console.debug(
`queued task ${id} with ${Object.values(queuedPrompt.nodes).length} nodes`
)
} }
/** /**

View File

@@ -65,7 +65,7 @@ export const useExtensionStore = defineStore('extension', () => {
} }
if (disabledExtensionNames.value.has(extension.name)) { if (disabledExtensionNames.value.has(extension.name)) {
console.log(`Extension ${extension.name} is disabled.`) console.warn(`Extension ${extension.name} is disabled.`)
} }
extensionByName.value[extension.name] = markRaw(extension) extensionByName.value[extension.name] = markRaw(extension)

View File

@@ -151,7 +151,6 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
/** @todo Refreshes Electron tasks only. */ /** @todo Refreshes Electron tasks only. */
const refreshDesktopTasks = async () => { const refreshDesktopTasks = async () => {
isRefreshing.value = true isRefreshing.value = true
console.log('Refreshing desktop tasks')
await electron.Validation.validateInstallation(processUpdate) await electron.Validation.validateInstallation(processUpdate)
} }

View File

@@ -18,7 +18,7 @@ export class ExecutableGroupNodeChildDTO extends ExecutableNodeDTO {
subgraphNodePath: readonly NodeId[], subgraphNodePath: readonly NodeId[],
/** A flattened map of all DTOs in this node network. Subgraph instances have been expanded into their inner nodes. */ /** A flattened map of all DTOs in this node network. Subgraph instances have been expanded into their inner nodes. */
nodesByExecutionId: Map<ExecutionId, ExecutableLGraphNode>, nodesByExecutionId: Map<ExecutionId, ExecutableLGraphNode>,
/** The actual subgraph instance that contains this node, otherise undefined. */ /** The actual subgraph instance that contains this node, otherwise undefined. */
subgraphNode?: SubgraphNode | undefined, subgraphNode?: SubgraphNode | undefined,
groupNodeHandler?: GroupNodeHandler groupNodeHandler?: GroupNodeHandler
) { ) {

View File

@@ -0,0 +1,91 @@
/**
* Whitelisting helper for enabling SSO on safe, local-only hosts.
*
* Built-ins (always allowed):
* • 'localhost' and any subdomain of '.localhost' (e.g., app.localhost)
* • IPv4 loopback 127.0.0.0/8 (e.g., 127.0.0.1, 127.1.2.3)
* • IPv6 loopback ::1 (supports compressed/expanded textual forms)
*
* No environment variables are used. To add more exact hostnames,
* edit HOST_WHITELIST below.
*/
const HOST_WHITELIST: string[] = ['localhost']
/** Normalize for comparison: lowercase, strip port/brackets, trim trailing dot. */
export function normalizeHost(input: string): string {
let h = (input || '').trim().toLowerCase()
// Trim a trailing dot: 'localhost.' -> 'localhost'
h = h.replace(/\.$/, '')
// Remove ':port' safely.
// Case 1: [IPv6]:port
const mBracket = h.match(/^\[([^\]]+)\]:(\d+)$/)
if (mBracket) {
h = mBracket[1] // keep only the host inside the brackets
} else {
// Case 2: hostname/IPv4:port (exactly one ':')
const mPort = h.match(/^([^:]+):(\d+)$/)
if (mPort) h = mPort[1]
}
// Strip any remaining brackets (e.g., '[::1]' -> '::1')
h = h.replace(/^\[|\]$/g, '')
return h
}
/** Public check used by the UI. */
export function isHostWhitelisted(rawHost: string): boolean {
const host = normalizeHost(rawHost)
if (isLocalhostLabel(host)) return true
if (isIPv4Loopback(host)) return true
if (isIPv6Loopback(host)) return true
const normalizedList = HOST_WHITELIST.map(normalizeHost)
return normalizedList.includes(host)
}
/* -------------------- Helpers -------------------- */
function isLocalhostLabel(h: string): boolean {
// 'localhost' and any subdomain (e.g., 'app.localhost')
return h === 'localhost' || h.endsWith('.localhost')
}
const IPV4_OCTET = '(?:25[0-5]|2[0-4]\\d|1\\d\\d|0?\\d?\\d)'
const V4_LOOPBACK_RE = new RegExp(
'^127\\.' + IPV4_OCTET + '\\.' + IPV4_OCTET + '\\.' + IPV4_OCTET + '$'
)
function isIPv4Loopback(h: string): boolean {
// 127/8 with strict 0255 octets (leading zeros allowed, e.g., 127.000.000.001)
return V4_LOOPBACK_RE.test(h)
}
// Fully expanded IPv6 loopback: 0:0:0:0:0:0:0:1 (allow leading zeros up to 4 chars)
const V6_FULL_LOOPBACK_RE = /^(?:0{1,4}:){7}0{0,3}1$/i
// Compressed IPv6 loopback forms around '::' with only zero groups before the final :1
// - Left side: zero groups separated by ':' (no trailing colon required)
// - Right side: zero groups each followed by ':' (so the final ':1' is provided by the pattern)
// The final group is exactly value 1, with up to 3 leading zeros (e.g., '0001').
const V6_COMPRESSED_LOOPBACK_RE =
/^((?:0{1,4}(?::0{1,4}){0,6})?)::((?:0{1,4}:){0,6})0{0,3}1$/i
function isIPv6Loopback(h: string): boolean {
// Exact full form: 0:0:0:0:0:0:0:1 (with up to 3 leading zeros on the final "1" group)
if (V6_FULL_LOOPBACK_RE.test(h)) return true
// Compressed forms that still equal ::1 (e.g., ::1, ::0001, 0:0::1, ::0:1, etc.)
const m = h.match(V6_COMPRESSED_LOOPBACK_RE)
if (!m) return false
// Count explicit zero groups on each side of '::' to ensure at least one group is compressed.
// (leftCount + rightCount) must be ≤ 6 so that the total expanded groups = 8.
const leftCount = m[1] ? m[1].match(/0{1,4}:/gi)?.length ?? 0 : 0
const rightCount = m[2] ? m[2].match(/0{1,4}:/gi)?.length ?? 0 : 0
// Require that at least one group was actually compressed: i.e., leftCount + rightCount ≤ 6.
return leftCount + rightCount <= 6
}

View File

@@ -70,7 +70,7 @@ function extendLink(link: SerialisedLLinkArray) {
* makes logical sense. Can apply fixes when passed the `fix` argument as true. * makes logical sense. Can apply fixes when passed the `fix` argument as true.
* *
* Note that fixes are a best-effort attempt. Seems to get it correct in most cases, but there is a * Note that fixes are a best-effort attempt. Seems to get it correct in most cases, but there is a
* chance it correct an anomoly that results in placing an incorrect link (say, if there were two * chance it correct an anomaly that results in placing an incorrect link (say, if there were two
* links in the data). Users should take care to not overwrite work until manually checking the * links in the data). Users should take care to not overwrite work until manually checking the
* result. * result.
*/ */

View File

@@ -27,7 +27,7 @@ export function applyTextReplacements(
let nodes = allNodes.filter( let nodes = allNodes.filter(
(n) => n.properties?.['Node name for S&R'] === split[0] (n) => n.properties?.['Node name for S&R'] === split[0]
) )
// If we cant, see if there is a node with that title // If we can't, see if there is a node with that title
if (!nodes.length) { if (!nodes.length) {
nodes = allNodes.filter((n) => n.title === split[0]) nodes = allNodes.filter((n) => n.title === split[0])
} }

View File

@@ -48,16 +48,10 @@ const meta: Meta<typeof InstallView> = {
getDetectedGpu: () => Promise.resolve('mps') getDetectedGpu: () => Promise.resolve('mps')
}, },
Events: { Events: {
trackEvent: (eventName: string, data?: any) => { trackEvent: (_eventName: string, _data?: any) => {}
console.log('Track event:', eventName, data)
}
},
installComfyUI: (options: any) => {
console.log('Install ComfyUI with options:', options)
},
changeTheme: (theme: any) => {
console.log('Change theme:', theme)
}, },
installComfyUI: (_options: any) => {},
changeTheme: (_theme: any) => {},
getSystemPaths: () => getSystemPaths: () =>
Promise.resolve({ Promise.resolve({
defaultInstallPath: '/Users/username/ComfyUI' defaultInstallPath: '/Users/username/ComfyUI'

View File

@@ -61,15 +61,12 @@ const updateAllPacks = async () => {
managerStore.isPackInstalled(pack.id) managerStore.isPackInstalled(pack.id)
) )
if (!updatablePacks.length) { if (!updatablePacks.length) {
console.info('No installed packs available for update')
isUpdating.value = false isUpdating.value = false
return return
} }
console.info(`Starting update of ${updatablePacks.length} packs`)
try { try {
await Promise.all(updatablePacks.map(updatePack)) await Promise.all(updatablePacks.map(updatePack))
managerStore.updatePack.clear() managerStore.updatePack.clear()
console.info('All packs updated successfully')
} catch (error) { } catch (error) {
console.error('Pack update failed:', error) console.error('Pack update failed:', error)
console.error( console.error(

View File

@@ -364,10 +364,6 @@ export function useConflictDetection() {
Object.entries(bulkResult).forEach(([packageId, failInfo]) => { Object.entries(bulkResult).forEach(([packageId, failInfo]) => {
if (failInfo !== null) { if (failInfo !== null) {
importFailures[packageId] = failInfo importFailures[packageId] = failInfo
console.debug(
`[ConflictDetection] Import failure found for ${packageId}:`,
failInfo
)
} }
}) })
@@ -496,11 +492,6 @@ export function useConflictDetection() {
// Merge conflicts for packages with the same name // Merge conflicts for packages with the same name
const mergedConflicts = consolidateConflictsByPackage(conflictedResults) const mergedConflicts = consolidateConflictsByPackage(conflictedResults)
console.debug(
'[ConflictDetection] Conflicts detected (stored for UI):',
mergedConflicts
)
// Store merged conflicts in Pinia store for UI usage // Store merged conflicts in Pinia store for UI usage
conflictStore.setConflictedPackages(mergedConflicts) conflictStore.setConflictedPackages(mergedConflicts)

View File

@@ -58,7 +58,7 @@ describe.skip('SubgraphConversion', () => {
expect(graph.nodes.length).toBe(2) expect(graph.nodes.length).toBe(2)
expect(graph.links.size).toBe(1) expect(graph.links.size).toBe(1)
}) })
it('Should merge boundry links', () => { it('Should merge boundary links', () => {
const subgraph = createTestSubgraph({ const subgraph = createTestSubgraph({
inputs: [{ name: 'value', type: 'number' }], inputs: [{ name: 'value', type: 'number' }],
outputs: [{ name: 'value', type: 'number' }] outputs: [{ name: 'value', type: 'number' }]

View File

@@ -135,7 +135,7 @@ describe.skip('SubgraphSlot visual feedback', () => {
expect(globalAlphaValues).toContain(0.4) expect(globalAlphaValues).toContain(0.4)
}) })
// "not implmeneted yet" // "not implemented yet"
// it("should render slots with full opacity when dragging between compatible SubgraphInput and SubgraphOutput", () => { // it("should render slots with full opacity when dragging between compatible SubgraphInput and SubgraphOutput", () => {
// const subgraph = createTestSubgraph() // const subgraph = createTestSubgraph()

View File

@@ -0,0 +1,123 @@
import { describe, expect, it } from 'vitest'
import { isHostWhitelisted, normalizeHost } from '@/utils/hostWhitelist'
describe('hostWhitelist utils', () => {
describe('normalizeHost', () => {
it.each([
['LOCALHOST', 'localhost'],
['localhost.', 'localhost'], // trims trailing dot
['localhost:5173', 'localhost'], // strips :port
['127.0.0.1:5173', '127.0.0.1'], // strips :port
['[::1]:5173', '::1'], // strips brackets + :port
['[::1]', '::1'], // strips brackets
['::1', '::1'], // leaves plain IPv6
[' [::1] ', '::1'], // trims whitespace
['APP.LOCALHOST', 'app.localhost'], // lowercases
['example.com.', 'example.com'], // trims trailing dot
['[2001:db8::1]:8443', '2001:db8::1'], // IPv6 with brackets+port
['2001:db8::1', '2001:db8::1'] // plain IPv6 stays
])('normalizeHost(%o) -> %o', (input, expected) => {
expect(normalizeHost(input)).toBe(expected)
})
it('does not strip non-numeric suffixes (not a port pattern)', () => {
expect(normalizeHost('example.com:abc')).toBe('example.com:abc')
expect(normalizeHost('127.0.0.1:abc')).toBe('127.0.0.1:abc')
})
})
describe('isHostWhitelisted', () => {
describe('localhost label', () => {
it.each([
'localhost',
'LOCALHOST',
'localhost.',
'localhost:5173',
'foo.localhost',
'Foo.Localhost',
'sub.foo.localhost',
'foo.localhost:5173'
])('should allow %o', (input) => {
expect(isHostWhitelisted(input)).toBe(true)
})
it.each([
'localhost.com',
'evil-localhost',
'notlocalhost',
'foo.localhost.evil'
])('should NOT allow %o', (input) => {
expect(isHostWhitelisted(input)).toBe(false)
})
})
describe('IPv4 127/8 loopback', () => {
it.each([
'127.0.0.1',
'127.1.2.3',
'127.255.255.255',
'127.0.0.1:3000',
'127.000.000.001', // leading zeros are still digits 0-255
'127.0.0.1.' // trailing dot should be tolerated
])('should allow %o', (input) => {
expect(isHostWhitelisted(input)).toBe(true)
})
it.each([
'126.0.0.1',
'127.256.0.1',
'127.-1.0.1',
'127.0.0.1:abc',
'128.0.0.1',
'192.168.1.10',
'10.0.0.2',
'0.0.0.0',
'255.255.255.255',
'127.0.0', // malformed
'127.0.0.1.5' // malformed
])('should NOT allow %o', (input) => {
expect(isHostWhitelisted(input)).toBe(false)
})
})
describe('IPv6 loopback ::1 (all textual forms)', () => {
it.each([
'::1',
'[::1]',
'[::1]:5173',
'::0001',
'0:0:0:0:0:0:0:1',
'0000:0000:0000:0000:0000:0000:0000:0001',
// Compressed equivalents of ::1 (with zeros compressed)
'0:0::1',
'0:0:0:0:0:0::1',
'::0:1' // compressing the initial zeros (still ::1 when expanded)
])('should allow %o', (input) => {
expect(isHostWhitelisted(input)).toBe(true)
})
it.each([
'::2',
'::',
'::0',
'0:0:0:0:0:0:0:2',
'fe80::1', // link-local, not loopback
'2001:db8::1',
'::1:5173', // bracketless "port-like" suffix must not pass
':::1', // invalid (triple colon)
'0:0:0:0:0:0:::1', // invalid compression
'[::1%25lo0]',
'[::1%25lo0]:5173',
'::1%25lo0'
])('should NOT allow %o', (input) => {
expect(isHostWhitelisted(input)).toBe(false)
})
it('should reject empty/whitespace-only input', () => {
expect(isHostWhitelisted('')).toBe(false)
expect(isHostWhitelisted(' ')).toBe(false)
})
})
})
})

View File

@@ -100,7 +100,7 @@ describe('Subgraph proxyWidgets', () => {
expect(innerNodes[0].widgets[0].last_y).toBe(11) expect(innerNodes[0].widgets[0].last_y).toBe(11)
expect(innerNodes[0].widgets[0].computedHeight).toBe(12) expect(innerNodes[0].widgets[0].computedHeight).toBe(12)
}) })
test('Can detatch and re-attach widgets', () => { test('Can detach and re-attach widgets', () => {
const [subgraphNode, innerNodes] = setupSubgraph(1) const [subgraphNode, innerNodes] = setupSubgraph(1)
innerNodes[0].addWidget('text', 'stringWidget', 'value', () => {}) innerNodes[0].addWidget('text', 'stringWidget', 'value', () => {})
subgraphNode.properties.proxyWidgets = JSON.stringify([ subgraphNode.properties.proxyWidgets = JSON.stringify([