Compare commits
19 Commits
sno-fix-pl
...
v1.26.11
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca4352acb4 | ||
|
|
e773816406 | ||
|
|
902dd9f95d | ||
|
|
04b28cb107 | ||
|
|
114cdb592a | ||
|
|
959ede2529 | ||
|
|
132e98b85e | ||
|
|
5d1cbd5612 | ||
|
|
5befd00dfc | ||
|
|
75e5089546 | ||
|
|
b9881fac29 | ||
|
|
a51e228e44 | ||
|
|
4f01333e74 | ||
|
|
2bb158c51c | ||
|
|
fdbf476179 | ||
|
|
a82fcd8ec6 | ||
|
|
9a9f8c72f2 | ||
|
|
065d9e82b9 | ||
|
|
4748378387 |
1
.gitattributes
vendored
@@ -9,6 +9,7 @@
|
||||
*.mts text eol=lf
|
||||
*.ts text eol=lf
|
||||
*.vue text eol=lf
|
||||
*.yaml text eol=lf
|
||||
|
||||
# Generated files
|
||||
src/types/comfyRegistryTypes.ts linguist-generated=true
|
||||
|
||||
2
.github/workflows/update-electron-types.yaml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
electron-types-tools-cache-${{ runner.os }}-
|
||||
|
||||
- name: Update electron types
|
||||
run: pnpm install @comfyorg/comfyui-electron-types@latest
|
||||
run: pnpm install --workspace-root @comfyorg/comfyui-electron-types@latest
|
||||
|
||||
- name: Get new version
|
||||
id: get-version
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { test as base } from '@playwright/test'
|
||||
import { Page } from 'playwright'
|
||||
import { Page, test as base } from '@playwright/test'
|
||||
|
||||
export class UserSelectPage {
|
||||
constructor(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Request, Route } from '@playwright/test'
|
||||
import _ from 'es-toolkit/compat'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import type { Request, Route } from 'playwright'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
import type {
|
||||
|
||||
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 96 KiB |
@@ -2,24 +2,31 @@ import type { KnipConfig } from 'knip'
|
||||
|
||||
const config: KnipConfig = {
|
||||
entry: [
|
||||
'build/**/*.ts',
|
||||
'scripts/**/*.{js,ts}',
|
||||
'src/main.ts',
|
||||
'vite.config.mts',
|
||||
'vite.electron.config.mts',
|
||||
'vite.types.config.mts',
|
||||
'eslint.config.js',
|
||||
'tailwind.config.ts',
|
||||
'postcss.config.js',
|
||||
'playwright.config.ts',
|
||||
'playwright.i18n.config.ts',
|
||||
'vitest.config.ts',
|
||||
'vitest.litegraph.config.ts',
|
||||
'scripts/**/*.{js,ts}'
|
||||
'vite.types.config.mts'
|
||||
],
|
||||
project: [
|
||||
'browser_tests/**/*.{js,ts}',
|
||||
'build/**/*.{js,ts,vue}',
|
||||
'scripts/**/*.{js,ts}',
|
||||
'src/**/*.{js,ts,vue}',
|
||||
'tests-ui/**/*.{js,ts,vue}',
|
||||
'browser_tests/**/*.{js,ts}',
|
||||
'scripts/**/*.{js,ts}'
|
||||
'*.{js,ts,mts}'
|
||||
],
|
||||
ignoreDependencies: [
|
||||
'@primeuix/forms',
|
||||
'@primeuix/styled',
|
||||
'@primeuix/utils',
|
||||
'@primevue/icons',
|
||||
'@iconify/json',
|
||||
'tailwindcss',
|
||||
'tailwindcss-primeui', // Need to figure out why tailwind plugin isn't applying
|
||||
// Dev
|
||||
'@executeautomation/playwright-mcp-server',
|
||||
'@trivago/prettier-plugin-sort-imports'
|
||||
],
|
||||
ignore: [
|
||||
// Generated files
|
||||
@@ -57,29 +64,21 @@ const config: KnipConfig = {
|
||||
ignoreExportsUsedInFile: true,
|
||||
// Vue-specific configuration
|
||||
vue: true,
|
||||
tailwind: true,
|
||||
// Only check for unused files, disable all other rules
|
||||
// TODO: Gradually enable other rules - see https://github.com/Comfy-Org/ComfyUI_frontend/issues/4888
|
||||
rules: {
|
||||
binaries: 'off',
|
||||
classMembers: 'off',
|
||||
dependencies: 'off',
|
||||
devDependencies: 'off',
|
||||
duplicates: 'off',
|
||||
enumMembers: 'off',
|
||||
exports: 'off',
|
||||
nsExports: 'off',
|
||||
nsTypes: 'off',
|
||||
types: 'off',
|
||||
unlisted: 'off'
|
||||
types: 'off'
|
||||
},
|
||||
// Include dependencies analysis
|
||||
includeEntryExports: true,
|
||||
// Workspace configuration for monorepo-like structure
|
||||
workspaces: {
|
||||
'.': {
|
||||
entry: ['src/main.ts', 'playwright.i18n.config.ts']
|
||||
}
|
||||
}
|
||||
includeEntryExports: true
|
||||
}
|
||||
|
||||
export default config
|
||||
|
||||
17
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"private": true,
|
||||
"version": "1.26.9",
|
||||
"version": "1.26.11",
|
||||
"type": "module",
|
||||
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
|
||||
"homepage": "https://comfy.org",
|
||||
@@ -40,7 +40,6 @@
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.8.0",
|
||||
"@executeautomation/playwright-mcp-server": "^1.0.6",
|
||||
"@iconify/json": "^2.2.245",
|
||||
"@iconify/tailwind": "^1.2.0",
|
||||
"@intlify/eslint-plugin-vue-i18n": "^3.2.0",
|
||||
"@lobehub/i18n-cli": "^1.25.1",
|
||||
@@ -48,7 +47,6 @@
|
||||
"@nx/playwright": "21.4.1",
|
||||
"@nx/storybook": "21.4.1",
|
||||
"@nx/vite": "21.4.1",
|
||||
"@nx/web": "21.4.1",
|
||||
"@pinia/testing": "^0.1.5",
|
||||
"@playwright/test": "^1.52.0",
|
||||
"@storybook/addon-docs": "^9.1.1",
|
||||
@@ -56,17 +54,15 @@
|
||||
"@storybook/vue3-vite": "^9.1.1",
|
||||
"@tailwindcss/vite": "^4.1.12",
|
||||
"@trivago/prettier-plugin-sort-imports": "^5.2.0",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/jsdom": "^21.1.7",
|
||||
"@types/node": "^20.14.8",
|
||||
"@types/semver": "^7.7.0",
|
||||
"@types/three": "^0.169.0",
|
||||
"@vitejs/plugin-vue": "^5.1.4",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"@vitest/ui": "^3.0.0",
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
"chalk": "^5.3.0",
|
||||
"commander": "^14.0.0",
|
||||
"eslint": "^9.12.0",
|
||||
"eslint-config-prettier": "^10.1.2",
|
||||
"eslint-plugin-prettier": "^5.2.6",
|
||||
@@ -77,18 +73,13 @@
|
||||
"globals": "^15.9.0",
|
||||
"happy-dom": "^15.11.0",
|
||||
"husky": "^9.0.11",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"ink": "^6.2.2",
|
||||
"jiti": "2.4.2",
|
||||
"jsdom": "^26.1.0",
|
||||
"knip": "^5.62.0",
|
||||
"lint-staged": "^15.2.7",
|
||||
"lucide-vue-next": "^0.540.0",
|
||||
"nx": "21.4.1",
|
||||
"postcss": "^8.4.39",
|
||||
"prettier": "^3.3.2",
|
||||
"react": "^19.1.1",
|
||||
"react-reconciler": "^0.32.0",
|
||||
"storybook": "^9.1.1",
|
||||
"tailwindcss": "^4.1.12",
|
||||
"tailwindcss-primeui": "^0.6.1",
|
||||
@@ -110,7 +101,8 @@
|
||||
"dependencies": {
|
||||
"@alloc/quick-lru": "^5.2.0",
|
||||
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
|
||||
"@comfyorg/comfyui-electron-types": "^0.4.43",
|
||||
"@comfyorg/comfyui-electron-types": "^0.4.69",
|
||||
"@iconify/json": "^2.2.380",
|
||||
"@primeuix/forms": "0.0.2",
|
||||
"@primeuix/styled": "0.3.2",
|
||||
"@primeuix/utils": "^0.3.2",
|
||||
@@ -118,7 +110,6 @@
|
||||
"@primevue/forms": "^4.2.5",
|
||||
"@primevue/icons": "4.2.5",
|
||||
"@primevue/themes": "^4.2.5",
|
||||
"@sentry/core": "^10.5.0",
|
||||
"@sentry/vue": "^8.48.0",
|
||||
"@tiptap/core": "^2.10.4",
|
||||
"@tiptap/extension-link": "^2.10.4",
|
||||
|
||||
449
pnpm-lock.yaml
generated
@@ -15,8 +15,11 @@ importers:
|
||||
specifier: ^1.3.1
|
||||
version: 1.3.1
|
||||
'@comfyorg/comfyui-electron-types':
|
||||
specifier: ^0.4.43
|
||||
version: 0.4.43
|
||||
specifier: ^0.4.69
|
||||
version: 0.4.69
|
||||
'@iconify/json':
|
||||
specifier: ^2.2.380
|
||||
version: 2.2.380
|
||||
'@primeuix/forms':
|
||||
specifier: 0.0.2
|
||||
version: 0.0.2
|
||||
@@ -38,9 +41,6 @@ importers:
|
||||
'@primevue/themes':
|
||||
specifier: ^4.2.5
|
||||
version: 4.2.5
|
||||
'@sentry/core':
|
||||
specifier: ^10.5.0
|
||||
version: 10.5.0
|
||||
'@sentry/vue':
|
||||
specifier: ^8.48.0
|
||||
version: 8.48.0(pinia@2.2.2(typescript@5.9.2)(vue@3.5.13(typescript@5.9.2)))(vue@3.5.13(typescript@5.9.2))
|
||||
@@ -162,9 +162,6 @@ importers:
|
||||
'@executeautomation/playwright-mcp-server':
|
||||
specifier: ^1.0.6
|
||||
version: 1.0.6(react@19.1.1)(ws@8.18.3)(zod@3.24.1)
|
||||
'@iconify/json':
|
||||
specifier: ^2.2.245
|
||||
version: 2.2.245
|
||||
'@iconify/tailwind':
|
||||
specifier: ^1.2.0
|
||||
version: 1.2.0
|
||||
@@ -186,9 +183,6 @@ importers:
|
||||
'@nx/vite':
|
||||
specifier: 21.4.1
|
||||
version: 21.4.1(@babel/traverse@7.28.3)(nx@21.4.1)(typescript@5.9.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))(vitest@3.2.4)
|
||||
'@nx/web':
|
||||
specifier: 21.4.1
|
||||
version: 21.4.1(@babel/traverse@7.28.3)(nx@21.4.1)
|
||||
'@pinia/testing':
|
||||
specifier: ^0.1.5
|
||||
version: 0.1.5(pinia@2.2.2(typescript@5.9.2)(vue@3.5.13(typescript@5.9.2)))(vue@3.5.13(typescript@5.9.2))
|
||||
@@ -209,10 +203,7 @@ importers:
|
||||
version: 4.1.12(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
|
||||
'@trivago/prettier-plugin-sort-imports':
|
||||
specifier: ^5.2.0
|
||||
version: 5.2.0(@vue/compiler-sfc@3.5.13)(prettier@3.3.2)
|
||||
'@types/dompurify':
|
||||
specifier: ^3.0.5
|
||||
version: 3.0.5
|
||||
version: 5.2.2(@vue/compiler-sfc@3.5.13)(prettier@3.3.2)
|
||||
'@types/fs-extra':
|
||||
specifier: ^11.0.4
|
||||
version: 11.0.4
|
||||
@@ -231,18 +222,15 @@ importers:
|
||||
'@vitejs/plugin-vue':
|
||||
specifier: ^5.1.4
|
||||
version: 5.1.4(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))(vue@3.5.13(typescript@5.9.2))
|
||||
'@vitest/coverage-v8':
|
||||
specifier: ^3.2.4
|
||||
version: 3.2.4(vitest@3.2.4)
|
||||
'@vitest/ui':
|
||||
specifier: ^3.0.0
|
||||
version: 3.2.4(vitest@3.2.4)
|
||||
'@vue/test-utils':
|
||||
specifier: ^2.4.6
|
||||
version: 2.4.6
|
||||
chalk:
|
||||
specifier: ^5.3.0
|
||||
version: 5.3.0
|
||||
commander:
|
||||
specifier: ^14.0.0
|
||||
version: 14.0.0
|
||||
eslint:
|
||||
specifier: ^9.12.0
|
||||
version: 9.12.0(jiti@2.4.2)
|
||||
@@ -273,12 +261,6 @@ importers:
|
||||
husky:
|
||||
specifier: ^9.0.11
|
||||
version: 9.0.11
|
||||
identity-obj-proxy:
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0
|
||||
ink:
|
||||
specifier: ^6.2.2
|
||||
version: 6.2.2(@types/react@19.1.9)(react@19.1.1)
|
||||
jiti:
|
||||
specifier: 2.4.2
|
||||
version: 2.4.2
|
||||
@@ -297,18 +279,9 @@ importers:
|
||||
nx:
|
||||
specifier: 21.4.1
|
||||
version: 21.4.1
|
||||
postcss:
|
||||
specifier: ^8.4.39
|
||||
version: 8.5.1
|
||||
prettier:
|
||||
specifier: ^3.3.2
|
||||
version: 3.3.2
|
||||
react:
|
||||
specifier: ^19.1.1
|
||||
version: 19.1.1
|
||||
react-reconciler:
|
||||
specifier: ^0.32.0
|
||||
version: 0.32.0(react@19.1.1)
|
||||
storybook:
|
||||
specifier: ^9.1.1
|
||||
version: 9.1.1(@testing-library/dom@10.4.1)(prettier@3.3.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
|
||||
@@ -507,10 +480,6 @@ packages:
|
||||
resolution: {integrity: sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/generator@7.27.1':
|
||||
resolution: {integrity: sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/generator@7.28.3':
|
||||
resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -602,11 +571,6 @@ packages:
|
||||
resolution: {integrity: sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/parser@7.27.2':
|
||||
resolution: {integrity: sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
'@babel/parser@7.28.3':
|
||||
resolution: {integrity: sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
@@ -1042,24 +1006,20 @@ packages:
|
||||
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/traverse@7.27.1':
|
||||
resolution: {integrity: sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/traverse@7.28.3':
|
||||
resolution: {integrity: sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/types@7.27.1':
|
||||
resolution: {integrity: sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/types@7.28.2':
|
||||
resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@comfyorg/comfyui-electron-types@0.4.43':
|
||||
resolution: {integrity: sha512-o6WFbYn9yAkGbkOwvhPF7pbKDvN0occZ21Tfyhya8CIsIqKpTHLft0aOqo4yhSh+kTxN16FYjsfrTH5Olk4WuA==}
|
||||
'@bcoe/v8-coverage@1.0.2':
|
||||
resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@comfyorg/comfyui-electron-types@0.4.69':
|
||||
resolution: {integrity: sha512-emEapJvbbx8lXiJ/84gmk+fYU73MmqkQKgBDQkyDwctcOb+eNe347PaH/+0AIjX8A/DtFHfnwgh9J8k3RVdqZA==}
|
||||
|
||||
'@csstools/color-helpers@5.1.0':
|
||||
resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==}
|
||||
@@ -1663,8 +1623,8 @@ packages:
|
||||
resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==}
|
||||
engines: {node: '>=18.18'}
|
||||
|
||||
'@iconify/json@2.2.245':
|
||||
resolution: {integrity: sha512-JbruddbGKghBe6fE1mzuo5hhUkisIW4mAdQGAyx0Q6sI52ukeQJHakolc2RQD/yWC3xp7rARNXMzWSXJynJ1vw==}
|
||||
'@iconify/json@2.2.380':
|
||||
resolution: {integrity: sha512-+Al/Q+mMB/nLz/tawmJEOkCs6+RKKVUS/Yg9I80h2yRpu0kIzxVLQRfF0NifXz/fH92vDVXbS399wio4lMVF4Q==}
|
||||
|
||||
'@iconify/tailwind@1.2.0':
|
||||
resolution: {integrity: sha512-KgpIHWOTcRYw1XcoUqyNSrmYyfLLqZYu3AmP8zdfLk0F5TqRO8YerhlvlQmGfn7rJXgPeZN569xPAJnJ53zZxA==}
|
||||
@@ -1709,6 +1669,10 @@ packages:
|
||||
resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@istanbuljs/schema@0.1.3':
|
||||
resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
'@jest/diff-sequences@30.0.1':
|
||||
resolution: {integrity: sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==}
|
||||
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
|
||||
@@ -1724,10 +1688,6 @@ packages:
|
||||
'@jridgewell/gen-mapping@0.3.13':
|
||||
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.5':
|
||||
resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@jridgewell/remapping@2.3.5':
|
||||
resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
|
||||
|
||||
@@ -1735,10 +1695,6 @@ packages:
|
||||
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@jridgewell/set-array@1.2.1':
|
||||
resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@jridgewell/source-map@0.3.6':
|
||||
resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==}
|
||||
|
||||
@@ -1748,9 +1704,6 @@ packages:
|
||||
'@jridgewell/sourcemap-codec@1.5.5':
|
||||
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.25':
|
||||
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.30':
|
||||
resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==}
|
||||
|
||||
@@ -1903,9 +1856,6 @@ packages:
|
||||
vite: ^5.0.0 || ^6.0.0
|
||||
vitest: ^1.3.1 || ^2.0.0 || ^3.0.0
|
||||
|
||||
'@nx/web@21.4.1':
|
||||
resolution: {integrity: sha512-SavfXtoCfvb+JmyDp1QHqLDyNUOgph1oQF9xgsNKCXXlIccBGxlsBPQR94qPYC290Hn4QvpLg0AYK6oNHPap2Q==}
|
||||
|
||||
'@nx/workspace@21.4.1':
|
||||
resolution: {integrity: sha512-3e33eTb1hRx6/i416Wc0mk/TPANxjx2Kz8ecnyqFFII5CM9tX7CPCwDF4O75N9mysI6PCKJ+Hc/1q76HZR4UgA==}
|
||||
|
||||
@@ -2272,10 +2222,6 @@ packages:
|
||||
resolution: {integrity: sha512-fuuVULB5/1vI8NoIwXwR3xwhJJqk+y4RdSdajExGF7nnUDBpwUJyXsmYJnOkBO+oLeEs58xaCpotCKiPUNnE3g==}
|
||||
engines: {node: '>=14.18'}
|
||||
|
||||
'@sentry/core@10.5.0':
|
||||
resolution: {integrity: sha512-jTJ8NhZSKB2yj3QTVRXfCCngQzAOLThQUxCl9A7Mv+XF10tP7xbH/88MVQ5WiOr2IzcmrB9r2nmUe36BnMlLjA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@sentry/core@8.48.0':
|
||||
resolution: {integrity: sha512-VGwYgTfLpvJ5LRO5A+qWo1gpo6SfqaGXL9TOzVgBucAdpzbrYHpZ87sEarDVq/4275uk1b0S293/mfsskFczyw==}
|
||||
engines: {node: '>=14.18'}
|
||||
@@ -2586,14 +2532,14 @@ packages:
|
||||
'@tiptap/starter-kit@2.10.4':
|
||||
resolution: {integrity: sha512-tu/WCs9Mkr5Nt8c3/uC4VvAbQlVX0OY7ygcqdzHGUeG9zP3twdW7o5xM3kyDKR2++sbVzqu5Ll5qNU+1JZvPGQ==}
|
||||
|
||||
'@trivago/prettier-plugin-sort-imports@5.2.0':
|
||||
resolution: {integrity: sha512-yEIJ7xMKYQwyNRjxSdi4Gs37iszikAjxfky+3hu9bn24u8eHLJNDMAoOTyowp8p6EpSl8IQMdkfBx+WnJTttsw==}
|
||||
'@trivago/prettier-plugin-sort-imports@5.2.2':
|
||||
resolution: {integrity: sha512-fYDQA9e6yTNmA13TLVSA+WMQRc5Bn/c0EUBditUHNfMMxN7M82c38b1kEggVE3pLpZ0FwkwJkUEKMiOi52JXFA==}
|
||||
engines: {node: '>18.12'}
|
||||
peerDependencies:
|
||||
'@vue/compiler-sfc': 3.x
|
||||
prettier: 2.x - 3.x
|
||||
prettier-plugin-svelte: 3.x
|
||||
svelte: 4.x
|
||||
svelte: 4.x || 5.x
|
||||
peerDependenciesMeta:
|
||||
'@vue/compiler-sfc':
|
||||
optional: true
|
||||
@@ -2629,9 +2575,6 @@ packages:
|
||||
'@types/diff-match-patch@1.0.36':
|
||||
resolution: {integrity: sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==}
|
||||
|
||||
'@types/dompurify@3.0.5':
|
||||
resolution: {integrity: sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==}
|
||||
|
||||
'@types/estree@1.0.5':
|
||||
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
|
||||
|
||||
@@ -2820,6 +2763,15 @@ packages:
|
||||
vite: ^5.0.0
|
||||
vue: ^3.2.25
|
||||
|
||||
'@vitest/coverage-v8@3.2.4':
|
||||
resolution: {integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==}
|
||||
peerDependencies:
|
||||
'@vitest/browser': 3.2.4
|
||||
vitest: 3.2.4
|
||||
peerDependenciesMeta:
|
||||
'@vitest/browser':
|
||||
optional: true
|
||||
|
||||
'@vitest/expect@3.2.4':
|
||||
resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==}
|
||||
|
||||
@@ -3148,12 +3100,12 @@ packages:
|
||||
resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
ast-v8-to-istanbul@0.3.5:
|
||||
resolution: {integrity: sha512-9SdXjNheSiE8bALAQCQQuT6fgQaoxJh7IRYrRGZ8/9nv8WhJeC1aXAwN8TbaOssGOukUvyvnkgD9+Yuykvl1aA==}
|
||||
|
||||
async@3.2.5:
|
||||
resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==}
|
||||
|
||||
async@3.2.6:
|
||||
resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
|
||||
|
||||
asynckit@0.4.0:
|
||||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||
|
||||
@@ -3220,10 +3172,6 @@ packages:
|
||||
base64-js@1.5.1:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
|
||||
basic-auth@2.0.1:
|
||||
resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
better-opn@3.0.2:
|
||||
resolution: {integrity: sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
@@ -3428,10 +3376,6 @@ packages:
|
||||
resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
commander@14.0.0:
|
||||
resolution: {integrity: sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
commander@2.20.3:
|
||||
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
|
||||
|
||||
@@ -3516,10 +3460,6 @@ packages:
|
||||
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
corser@2.0.1:
|
||||
resolution: {integrity: sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==}
|
||||
engines: {node: '>= 0.4.0'}
|
||||
|
||||
cosmiconfig@7.1.0:
|
||||
resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -3987,9 +3927,6 @@ packages:
|
||||
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
eventemitter3@4.0.7:
|
||||
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
|
||||
|
||||
eventemitter3@5.0.1:
|
||||
resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
|
||||
|
||||
@@ -4272,10 +4209,6 @@ packages:
|
||||
resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
globals@11.12.0:
|
||||
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
globals@13.24.0:
|
||||
resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -4316,9 +4249,6 @@ packages:
|
||||
resolution: {integrity: sha512-/zyxHbXriYJ8b9Urh43ILk/jd9tC07djURnJuAimJ3tJCOLOzOUp7dEHDwJOZyzROlrrooUhr/0INZIDBj1Bjw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
harmony-reflect@1.6.2:
|
||||
resolution: {integrity: sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==}
|
||||
|
||||
has-flag@4.0.0:
|
||||
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -4352,14 +4282,13 @@ packages:
|
||||
resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==}
|
||||
engines: {node: ^16.14.0 || >=18.0.0}
|
||||
|
||||
html-encoding-sniffer@3.0.0:
|
||||
resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
html-encoding-sniffer@4.0.0:
|
||||
resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
html-escaper@2.0.2:
|
||||
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
|
||||
|
||||
html-minifier-terser@6.1.0:
|
||||
resolution: {integrity: sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -4376,15 +4305,6 @@ packages:
|
||||
resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
http-proxy@1.18.1:
|
||||
resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
||||
http-server@14.1.1:
|
||||
resolution: {integrity: sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==}
|
||||
engines: {node: '>=12'}
|
||||
hasBin: true
|
||||
|
||||
https-proxy-agent@7.0.6:
|
||||
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
|
||||
engines: {node: '>= 14'}
|
||||
@@ -4412,10 +4332,6 @@ packages:
|
||||
idb@7.1.1:
|
||||
resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==}
|
||||
|
||||
identity-obj-proxy@3.0.0:
|
||||
resolution: {integrity: sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
ieee754@1.2.1:
|
||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||
|
||||
@@ -4624,6 +4540,22 @@ packages:
|
||||
isexe@2.0.0:
|
||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||
|
||||
istanbul-lib-coverage@3.2.2:
|
||||
resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
istanbul-lib-report@3.0.1:
|
||||
resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
istanbul-lib-source-maps@5.0.6:
|
||||
resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
istanbul-reports@3.2.0:
|
||||
resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
jackspeak@3.4.0:
|
||||
resolution: {integrity: sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -4975,6 +4907,13 @@ packages:
|
||||
magic-string@0.30.18:
|
||||
resolution: {integrity: sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==}
|
||||
|
||||
magicast@0.3.5:
|
||||
resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==}
|
||||
|
||||
make-dir@4.0.0:
|
||||
resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
markdown-it-task-lists@2.1.1:
|
||||
resolution: {integrity: sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==}
|
||||
|
||||
@@ -5176,11 +5115,6 @@ packages:
|
||||
resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
mime@1.6.0:
|
||||
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
|
||||
engines: {node: '>=4'}
|
||||
hasBin: true
|
||||
|
||||
mimic-fn@2.1.0:
|
||||
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -5402,10 +5336,6 @@ packages:
|
||||
zod:
|
||||
optional: true
|
||||
|
||||
opener@1.5.2:
|
||||
resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==}
|
||||
hasBin: true
|
||||
|
||||
optionator@0.9.4:
|
||||
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
@@ -5606,10 +5536,6 @@ packages:
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
portfinder@1.0.37:
|
||||
resolution: {integrity: sha512-yuGIEjDAYnnOex9ddMnKZEMFE0CcGo6zbfzDklkmT1m5z734ss6JMzN9rNB3+RR7iS+F10D4/BVIaXOyh8PQKw==}
|
||||
engines: {node: '>= 10.12'}
|
||||
|
||||
postcss-selector-parser@6.1.0:
|
||||
resolution: {integrity: sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -5901,9 +5827,6 @@ packages:
|
||||
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
requires-port@1.0.0:
|
||||
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
|
||||
|
||||
resolve-from@4.0.0:
|
||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -5984,9 +5907,6 @@ packages:
|
||||
resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
secure-compare@3.0.1:
|
||||
resolution: {integrity: sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==}
|
||||
|
||||
secure-json-parse@2.7.0:
|
||||
resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==}
|
||||
|
||||
@@ -6255,6 +6175,10 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
test-exclude@7.0.1:
|
||||
resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
text-table@0.2.0:
|
||||
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
|
||||
|
||||
@@ -6464,10 +6388,6 @@ packages:
|
||||
unified@11.0.5:
|
||||
resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
|
||||
|
||||
union@0.5.0:
|
||||
resolution: {integrity: sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
unist-util-is@6.0.0:
|
||||
resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==}
|
||||
|
||||
@@ -6549,9 +6469,6 @@ packages:
|
||||
uri-js@4.4.1:
|
||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||
|
||||
url-join@4.0.1:
|
||||
resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==}
|
||||
|
||||
use-sync-external-store@1.5.0:
|
||||
resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==}
|
||||
peerDependencies:
|
||||
@@ -6818,10 +6735,6 @@ packages:
|
||||
resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
|
||||
whatwg-encoding@2.0.0:
|
||||
resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
whatwg-encoding@3.1.1:
|
||||
resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -7152,8 +7065,8 @@ snapshots:
|
||||
|
||||
'@ampproject/remapping@2.3.0':
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.5
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
'@jridgewell/gen-mapping': 0.3.13
|
||||
'@jridgewell/trace-mapping': 0.3.30
|
||||
|
||||
'@antfu/install-pkg@0.5.0':
|
||||
dependencies:
|
||||
@@ -7225,14 +7138,6 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@babel/generator@7.27.1':
|
||||
dependencies:
|
||||
'@babel/parser': 7.28.3
|
||||
'@babel/types': 7.28.2
|
||||
'@jridgewell/gen-mapping': 0.3.5
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
jsesc: 3.1.0
|
||||
|
||||
'@babel/generator@7.28.3':
|
||||
dependencies:
|
||||
'@babel/parser': 7.28.3
|
||||
@@ -7359,10 +7264,6 @@ snapshots:
|
||||
'@babel/template': 7.27.2
|
||||
'@babel/types': 7.28.2
|
||||
|
||||
'@babel/parser@7.27.2':
|
||||
dependencies:
|
||||
'@babel/types': 7.28.2
|
||||
|
||||
'@babel/parser@7.28.3':
|
||||
dependencies:
|
||||
'@babel/types': 7.28.2
|
||||
@@ -7911,18 +7812,6 @@ snapshots:
|
||||
'@babel/parser': 7.28.3
|
||||
'@babel/types': 7.28.2
|
||||
|
||||
'@babel/traverse@7.27.1':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.27.1
|
||||
'@babel/generator': 7.28.3
|
||||
'@babel/parser': 7.28.3
|
||||
'@babel/template': 7.27.2
|
||||
'@babel/types': 7.28.2
|
||||
debug: 4.4.1
|
||||
globals: 11.12.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@babel/traverse@7.28.3':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.27.1
|
||||
@@ -7935,17 +7824,14 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@babel/types@7.27.1':
|
||||
dependencies:
|
||||
'@babel/helper-string-parser': 7.27.1
|
||||
'@babel/helper-validator-identifier': 7.27.1
|
||||
|
||||
'@babel/types@7.28.2':
|
||||
dependencies:
|
||||
'@babel/helper-string-parser': 7.27.1
|
||||
'@babel/helper-validator-identifier': 7.27.1
|
||||
|
||||
'@comfyorg/comfyui-electron-types@0.4.43': {}
|
||||
'@bcoe/v8-coverage@1.0.2': {}
|
||||
|
||||
'@comfyorg/comfyui-electron-types@0.4.69': {}
|
||||
|
||||
'@csstools/color-helpers@5.1.0': {}
|
||||
|
||||
@@ -8523,7 +8409,7 @@ snapshots:
|
||||
|
||||
'@humanwhocodes/retry@0.3.1': {}
|
||||
|
||||
'@iconify/json@2.2.245':
|
||||
'@iconify/json@2.2.380':
|
||||
dependencies:
|
||||
'@iconify/types': 2.0.0
|
||||
pathe: 1.1.2
|
||||
@@ -8603,6 +8489,8 @@ snapshots:
|
||||
dependencies:
|
||||
minipass: 7.1.2
|
||||
|
||||
'@istanbuljs/schema@0.1.3': {}
|
||||
|
||||
'@jest/diff-sequences@30.0.1': {}
|
||||
|
||||
'@jest/get-type@30.1.0': {}
|
||||
@@ -8616,12 +8504,6 @@ snapshots:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
'@jridgewell/trace-mapping': 0.3.30
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.5':
|
||||
dependencies:
|
||||
'@jridgewell/set-array': 1.2.1
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
|
||||
'@jridgewell/remapping@2.3.5':
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.13
|
||||
@@ -8629,8 +8511,6 @@ snapshots:
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2': {}
|
||||
|
||||
'@jridgewell/set-array@1.2.1': {}
|
||||
|
||||
'@jridgewell/source-map@0.3.6':
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.13
|
||||
@@ -8640,11 +8520,6 @@ snapshots:
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.25':
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.30':
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
@@ -8983,23 +8858,6 @@ snapshots:
|
||||
- typescript
|
||||
- verdaccio
|
||||
|
||||
'@nx/web@21.4.1(@babel/traverse@7.28.3)(nx@21.4.1)':
|
||||
dependencies:
|
||||
'@nx/devkit': 21.4.1(nx@21.4.1)
|
||||
'@nx/js': 21.4.1(@babel/traverse@7.28.3)(nx@21.4.1)
|
||||
detect-port: 1.6.1
|
||||
http-server: 14.1.1
|
||||
picocolors: 1.1.1
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- '@babel/traverse'
|
||||
- '@swc-node/register'
|
||||
- '@swc/core'
|
||||
- debug
|
||||
- nx
|
||||
- supports-color
|
||||
- verdaccio
|
||||
|
||||
'@nx/workspace@21.4.1':
|
||||
dependencies:
|
||||
'@nx/devkit': 21.4.1(nx@21.4.1)
|
||||
@@ -9316,8 +9174,6 @@ snapshots:
|
||||
'@sentry-internal/replay-canvas': 8.48.0
|
||||
'@sentry/core': 8.48.0
|
||||
|
||||
'@sentry/core@10.5.0': {}
|
||||
|
||||
'@sentry/core@8.48.0': {}
|
||||
|
||||
'@sentry/vue@8.48.0(pinia@2.2.2(typescript@5.9.2)(vue@3.5.13(typescript@5.9.2)))(vue@3.5.13(typescript@5.9.2))':
|
||||
@@ -9641,12 +9497,12 @@ snapshots:
|
||||
'@tiptap/extension-text-style': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
|
||||
'@tiptap/pm': 2.10.4
|
||||
|
||||
'@trivago/prettier-plugin-sort-imports@5.2.0(@vue/compiler-sfc@3.5.13)(prettier@3.3.2)':
|
||||
'@trivago/prettier-plugin-sort-imports@5.2.2(@vue/compiler-sfc@3.5.13)(prettier@3.3.2)':
|
||||
dependencies:
|
||||
'@babel/generator': 7.27.1
|
||||
'@babel/parser': 7.27.2
|
||||
'@babel/traverse': 7.27.1
|
||||
'@babel/types': 7.27.1
|
||||
'@babel/generator': 7.28.3
|
||||
'@babel/parser': 7.28.3
|
||||
'@babel/traverse': 7.28.3
|
||||
'@babel/types': 7.28.2
|
||||
javascript-natural-sort: 0.7.1
|
||||
lodash: 4.17.21
|
||||
prettier: 3.3.2
|
||||
@@ -9682,10 +9538,6 @@ snapshots:
|
||||
|
||||
'@types/diff-match-patch@1.0.36': {}
|
||||
|
||||
'@types/dompurify@3.0.5':
|
||||
dependencies:
|
||||
'@types/trusted-types': 2.0.7
|
||||
|
||||
'@types/estree@1.0.5': {}
|
||||
|
||||
'@types/estree@1.0.6': {}
|
||||
@@ -9769,7 +9621,8 @@ snapshots:
|
||||
|
||||
'@types/tough-cookie@4.0.5': {}
|
||||
|
||||
'@types/trusted-types@2.0.7': {}
|
||||
'@types/trusted-types@2.0.7':
|
||||
optional: true
|
||||
|
||||
'@types/unist@3.0.3': {}
|
||||
|
||||
@@ -9915,6 +9768,25 @@ snapshots:
|
||||
vite: 5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2)
|
||||
vue: 3.5.13(typescript@5.9.2)
|
||||
|
||||
'@vitest/coverage-v8@3.2.4(vitest@3.2.4)':
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
'@bcoe/v8-coverage': 1.0.2
|
||||
ast-v8-to-istanbul: 0.3.5
|
||||
debug: 4.4.1
|
||||
istanbul-lib-coverage: 3.2.2
|
||||
istanbul-lib-report: 3.0.1
|
||||
istanbul-lib-source-maps: 5.0.6
|
||||
istanbul-reports: 3.2.0
|
||||
magic-string: 0.30.18
|
||||
magicast: 0.3.5
|
||||
std-env: 3.9.0
|
||||
test-exclude: 7.0.1
|
||||
tinyrainbow: 2.0.0
|
||||
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.14.10)(@vitest/ui@3.2.4)(happy-dom@15.11.0)(jsdom@26.1.0)(lightningcss@1.30.1)(terser@5.39.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@vitest/expect@3.2.4':
|
||||
dependencies:
|
||||
'@types/chai': 5.2.2
|
||||
@@ -10336,9 +10208,13 @@ snapshots:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
async@3.2.5: {}
|
||||
ast-v8-to-istanbul@0.3.5:
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.30
|
||||
estree-walker: 3.0.3
|
||||
js-tokens: 9.0.1
|
||||
|
||||
async@3.2.6: {}
|
||||
async@3.2.5: {}
|
||||
|
||||
asynckit@0.4.0: {}
|
||||
|
||||
@@ -10420,10 +10296,6 @@ snapshots:
|
||||
|
||||
base64-js@1.5.1: {}
|
||||
|
||||
basic-auth@2.0.1:
|
||||
dependencies:
|
||||
safe-buffer: 5.1.2
|
||||
|
||||
better-opn@3.0.2:
|
||||
dependencies:
|
||||
open: 8.4.2
|
||||
@@ -10640,8 +10512,6 @@ snapshots:
|
||||
|
||||
commander@13.1.0: {}
|
||||
|
||||
commander@14.0.0: {}
|
||||
|
||||
commander@2.20.3: {}
|
||||
|
||||
commander@8.3.0: {}
|
||||
@@ -10720,8 +10590,6 @@ snapshots:
|
||||
object-assign: 4.1.1
|
||||
vary: 1.1.2
|
||||
|
||||
corser@2.0.1: {}
|
||||
|
||||
cosmiconfig@7.1.0:
|
||||
dependencies:
|
||||
'@types/parse-json': 4.0.2
|
||||
@@ -11206,8 +11074,6 @@ snapshots:
|
||||
|
||||
event-target-shim@5.0.1: {}
|
||||
|
||||
eventemitter3@4.0.7: {}
|
||||
|
||||
eventemitter3@5.0.1: {}
|
||||
|
||||
eventsource-parser@3.0.5: {}
|
||||
@@ -11582,8 +11448,6 @@ snapshots:
|
||||
dependencies:
|
||||
ini: 4.1.1
|
||||
|
||||
globals@11.12.0: {}
|
||||
|
||||
globals@13.24.0:
|
||||
dependencies:
|
||||
type-fest: 0.20.2
|
||||
@@ -11624,8 +11488,6 @@ snapshots:
|
||||
webidl-conversions: 7.0.0
|
||||
whatwg-mimetype: 3.0.0
|
||||
|
||||
harmony-reflect@1.6.2: {}
|
||||
|
||||
has-flag@4.0.0: {}
|
||||
|
||||
has-property-descriptors@1.0.2:
|
||||
@@ -11652,14 +11514,12 @@ snapshots:
|
||||
dependencies:
|
||||
lru-cache: 10.3.0
|
||||
|
||||
html-encoding-sniffer@3.0.0:
|
||||
dependencies:
|
||||
whatwg-encoding: 2.0.0
|
||||
|
||||
html-encoding-sniffer@4.0.0:
|
||||
dependencies:
|
||||
whatwg-encoding: 3.1.1
|
||||
|
||||
html-escaper@2.0.2: {}
|
||||
|
||||
html-minifier-terser@6.1.0:
|
||||
dependencies:
|
||||
camel-case: 4.1.2
|
||||
@@ -11687,33 +11547,6 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
http-proxy@1.18.1:
|
||||
dependencies:
|
||||
eventemitter3: 4.0.7
|
||||
follow-redirects: 1.15.6
|
||||
requires-port: 1.0.0
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
|
||||
http-server@14.1.1:
|
||||
dependencies:
|
||||
basic-auth: 2.0.1
|
||||
chalk: 4.1.2
|
||||
corser: 2.0.1
|
||||
he: 1.2.0
|
||||
html-encoding-sniffer: 3.0.0
|
||||
http-proxy: 1.18.1
|
||||
mime: 1.6.0
|
||||
minimist: 1.2.8
|
||||
opener: 1.5.2
|
||||
portfinder: 1.0.37
|
||||
secure-compare: 3.0.1
|
||||
union: 0.5.0
|
||||
url-join: 4.0.1
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
- supports-color
|
||||
|
||||
https-proxy-agent@7.0.6:
|
||||
dependencies:
|
||||
agent-base: 7.1.4
|
||||
@@ -11737,10 +11570,6 @@ snapshots:
|
||||
|
||||
idb@7.1.1: {}
|
||||
|
||||
identity-obj-proxy@3.0.0:
|
||||
dependencies:
|
||||
harmony-reflect: 1.6.2
|
||||
|
||||
ieee754@1.2.1: {}
|
||||
|
||||
ignore@5.3.1: {}
|
||||
@@ -11909,6 +11738,27 @@ snapshots:
|
||||
|
||||
isexe@2.0.0: {}
|
||||
|
||||
istanbul-lib-coverage@3.2.2: {}
|
||||
|
||||
istanbul-lib-report@3.0.1:
|
||||
dependencies:
|
||||
istanbul-lib-coverage: 3.2.2
|
||||
make-dir: 4.0.0
|
||||
supports-color: 7.2.0
|
||||
|
||||
istanbul-lib-source-maps@5.0.6:
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.30
|
||||
debug: 4.4.1
|
||||
istanbul-lib-coverage: 3.2.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
istanbul-reports@3.2.0:
|
||||
dependencies:
|
||||
html-escaper: 2.0.2
|
||||
istanbul-lib-report: 3.0.1
|
||||
|
||||
jackspeak@3.4.0:
|
||||
dependencies:
|
||||
'@isaacs/cliui': 8.0.2
|
||||
@@ -12266,6 +12116,16 @@ snapshots:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
magicast@0.3.5:
|
||||
dependencies:
|
||||
'@babel/parser': 7.28.3
|
||||
'@babel/types': 7.28.2
|
||||
source-map-js: 1.2.1
|
||||
|
||||
make-dir@4.0.0:
|
||||
dependencies:
|
||||
semver: 7.7.2
|
||||
|
||||
markdown-it-task-lists@2.1.1: {}
|
||||
|
||||
markdown-it@14.1.0:
|
||||
@@ -12669,8 +12529,6 @@ snapshots:
|
||||
dependencies:
|
||||
mime-db: 1.54.0
|
||||
|
||||
mime@1.6.0: {}
|
||||
|
||||
mimic-fn@2.1.0: {}
|
||||
|
||||
mimic-fn@4.0.0: {}
|
||||
@@ -12902,8 +12760,6 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
opener@1.5.2: {}
|
||||
|
||||
optionator@0.9.4:
|
||||
dependencies:
|
||||
deep-is: 0.1.4
|
||||
@@ -13104,13 +12960,6 @@ snapshots:
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
|
||||
portfinder@1.0.37:
|
||||
dependencies:
|
||||
async: 3.2.6
|
||||
debug: 4.4.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
postcss-selector-parser@6.1.0:
|
||||
dependencies:
|
||||
cssesc: 3.0.0
|
||||
@@ -13528,8 +13377,6 @@ snapshots:
|
||||
|
||||
require-from-string@2.0.2: {}
|
||||
|
||||
requires-port@1.0.0: {}
|
||||
|
||||
resolve-from@4.0.0: {}
|
||||
|
||||
resolve-pkg-maps@1.0.0: {}
|
||||
@@ -13623,8 +13470,6 @@ snapshots:
|
||||
extend-shallow: 2.0.1
|
||||
kind-of: 6.0.3
|
||||
|
||||
secure-compare@3.0.1: {}
|
||||
|
||||
secure-json-parse@2.7.0: {}
|
||||
|
||||
semver@6.3.1: {}
|
||||
@@ -13921,6 +13766,12 @@ snapshots:
|
||||
commander: 2.20.3
|
||||
source-map-support: 0.5.21
|
||||
|
||||
test-exclude@7.0.1:
|
||||
dependencies:
|
||||
'@istanbuljs/schema': 0.1.3
|
||||
glob: 10.4.5
|
||||
minimatch: 9.0.5
|
||||
|
||||
text-table@0.2.0: {}
|
||||
|
||||
three@0.170.0: {}
|
||||
@@ -14087,10 +13938,6 @@ snapshots:
|
||||
trough: 2.2.0
|
||||
vfile: 6.0.3
|
||||
|
||||
union@0.5.0:
|
||||
dependencies:
|
||||
qs: 6.14.0
|
||||
|
||||
unist-util-is@6.0.0:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
@@ -14183,8 +14030,6 @@ snapshots:
|
||||
dependencies:
|
||||
punycode: 2.3.1
|
||||
|
||||
url-join@4.0.1: {}
|
||||
|
||||
use-sync-external-store@1.5.0(react@19.1.1):
|
||||
dependencies:
|
||||
react: 19.1.1
|
||||
@@ -14489,10 +14334,6 @@ snapshots:
|
||||
|
||||
websocket-extensions@0.1.4: {}
|
||||
|
||||
whatwg-encoding@2.0.0:
|
||||
dependencies:
|
||||
iconv-lite: 0.6.3
|
||||
|
||||
whatwg-encoding@3.1.1:
|
||||
dependencies:
|
||||
iconv-lite: 0.6.3
|
||||
|
||||
@@ -15,10 +15,10 @@ import ProgressSpinner from 'primevue/progressspinner'
|
||||
import { computed, onMounted } from 'vue'
|
||||
|
||||
import GlobalDialog from '@/components/dialog/GlobalDialog.vue'
|
||||
import { useConflictDetection } from '@/composables/useConflictDetection'
|
||||
import config from '@/config'
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
|
||||
import { useConflictDetection } from './composables/useConflictDetection'
|
||||
import { electronAPI, isElectron } from './utils/envUtil'
|
||||
|
||||
const workspaceStore = useWorkspaceStore()
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<Button unstyled :class="buttonStyle" :disabled="disabled" @click="onClick">
|
||||
<Button
|
||||
v-bind="$attrs"
|
||||
unstyled
|
||||
:class="buttonStyle"
|
||||
:disabled="disabled"
|
||||
@click="onClick"
|
||||
>
|
||||
<slot></slot>
|
||||
</Button>
|
||||
</template>
|
||||
@@ -20,6 +26,10 @@ interface IconButtonProps extends BaseButtonProps {
|
||||
onClick: (event: Event) => void
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false
|
||||
})
|
||||
|
||||
const {
|
||||
size = 'md',
|
||||
type = 'secondary',
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<Button unstyled :class="buttonStyle" :disabled="disabled" @click="onClick">
|
||||
<Button
|
||||
v-bind="$attrs"
|
||||
unstyled
|
||||
:class="buttonStyle"
|
||||
:disabled="disabled"
|
||||
@click="onClick"
|
||||
>
|
||||
<slot v-if="iconPosition !== 'right'" name="icon"></slot>
|
||||
<span>{{ label }}</span>
|
||||
<slot v-if="iconPosition === 'right'" name="icon"></slot>
|
||||
@@ -18,6 +24,10 @@ import {
|
||||
getButtonTypeClasses
|
||||
} from '@/types/buttonTypes'
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false
|
||||
})
|
||||
|
||||
interface IconTextButtonProps extends BaseButtonProps {
|
||||
iconPosition?: 'left' | 'right'
|
||||
label: string
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<Button unstyled :class="buttonStyle" :disabled="disabled" @click="onClick">
|
||||
<Button
|
||||
v-bind="$attrs"
|
||||
unstyled
|
||||
:class="buttonStyle"
|
||||
:disabled="disabled"
|
||||
@click="onClick"
|
||||
>
|
||||
<span>{{ label }}</span>
|
||||
</Button>
|
||||
</template>
|
||||
@@ -21,6 +27,10 @@ interface TextButtonProps extends BaseButtonProps {
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false
|
||||
})
|
||||
|
||||
const {
|
||||
size = 'md',
|
||||
type = 'primary',
|
||||
|
||||
@@ -105,7 +105,7 @@ const showContactSupport = async () => {
|
||||
|
||||
onMounted(async () => {
|
||||
if (!systemStatsStore.systemStats) {
|
||||
await systemStatsStore.fetchSystemStats()
|
||||
await systemStatsStore.refetchSystemStats()
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<NoResultsPlaceholder
|
||||
class="pb-0"
|
||||
icon="pi pi-exclamation-circle"
|
||||
title="Some Nodes Are Missing"
|
||||
message="When loading the graph, the following node types were not found"
|
||||
:title="$t('loadWorkflowWarning.missingNodesTitle')"
|
||||
:message="$t('loadWorkflowWarning.missingNodesDescription')"
|
||||
/>
|
||||
<MissingCoreNodesMessage :missing-core-nodes="missingCoreNodes" />
|
||||
<ListBox
|
||||
@@ -53,19 +53,15 @@
|
||||
<script setup lang="ts">
|
||||
import Button from 'primevue/button'
|
||||
import ListBox from 'primevue/listbox'
|
||||
import { computed } from 'vue'
|
||||
import { computed, nextTick, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
|
||||
import MissingCoreNodesMessage from '@/components/dialog/content/MissingCoreNodesMessage.vue'
|
||||
import { useMissingNodes } from '@/composables/nodePack/useMissingNodes'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { useManagerState } from '@/composables/useManagerState'
|
||||
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import {
|
||||
ManagerUIState,
|
||||
useManagerStateStore
|
||||
} from '@/stores/managerStateStore'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
import { useToastStore } from '@/stores/toastStore'
|
||||
import type { MissingNodeType } from '@/types/comfy'
|
||||
import { ManagerTab } from '@/types/comfyManagerTypes'
|
||||
@@ -81,6 +77,7 @@ const { missingNodePacks, isLoading, error, missingCoreNodes } =
|
||||
useMissingNodes()
|
||||
|
||||
const comfyManagerStore = useComfyManagerStore()
|
||||
const managerState = useManagerState()
|
||||
|
||||
// Check if any of the missing packs are currently being installed
|
||||
const isInstalling = computed(() => {
|
||||
@@ -111,48 +108,51 @@ const uniqueNodes = computed(() => {
|
||||
})
|
||||
})
|
||||
|
||||
const managerStateStore = useManagerStateStore()
|
||||
|
||||
// Show manager buttons unless manager is disabled
|
||||
const showManagerButtons = computed(() => {
|
||||
return managerStateStore.managerUIState !== ManagerUIState.DISABLED
|
||||
return managerState.shouldShowManagerButtons.value
|
||||
})
|
||||
|
||||
// Only show Install All button for NEW_UI (new manager with v4 support)
|
||||
const showInstallAllButton = computed(() => {
|
||||
return managerStateStore.managerUIState === ManagerUIState.NEW_UI
|
||||
return managerState.shouldShowInstallButton.value
|
||||
})
|
||||
|
||||
const openManager = async () => {
|
||||
const state = managerStateStore.managerUIState
|
||||
|
||||
switch (state) {
|
||||
case ManagerUIState.DISABLED:
|
||||
useDialogService().showSettingsDialog('extension')
|
||||
break
|
||||
|
||||
case ManagerUIState.LEGACY_UI:
|
||||
try {
|
||||
await useCommandStore().execute('Comfy.Manager.Menu.ToggleVisibility')
|
||||
} catch {
|
||||
// If legacy command doesn't exist, show toast
|
||||
const { t } = useI18n()
|
||||
useToastStore().add({
|
||||
severity: 'error',
|
||||
summary: t('g.error'),
|
||||
detail: t('manager.legacyMenuNotAvailable'),
|
||||
life: 3000
|
||||
})
|
||||
}
|
||||
break
|
||||
|
||||
case ManagerUIState.NEW_UI:
|
||||
useDialogService().showManagerDialog({
|
||||
initialTab: ManagerTab.Missing
|
||||
})
|
||||
break
|
||||
}
|
||||
await managerState.openManager({
|
||||
initialTab: ManagerTab.Missing,
|
||||
showToastOnLegacyError: true
|
||||
})
|
||||
}
|
||||
|
||||
const { t } = useI18n()
|
||||
const dialogStore = useDialogStore()
|
||||
|
||||
// Computed to check if all missing nodes have been installed
|
||||
const allMissingNodesInstalled = computed(() => {
|
||||
return (
|
||||
!isLoading.value &&
|
||||
!isInstalling.value &&
|
||||
missingNodePacks.value?.length === 0
|
||||
)
|
||||
})
|
||||
// Watch for completion and close dialog
|
||||
watch(allMissingNodesInstalled, async (allInstalled) => {
|
||||
if (allInstalled) {
|
||||
// Use nextTick to ensure state updates are complete
|
||||
await nextTick()
|
||||
|
||||
dialogStore.closeDialog({ key: 'global-load-workflow-warning' })
|
||||
|
||||
// Show success toast
|
||||
useToastStore().add({
|
||||
severity: 'success',
|
||||
summary: t('g.success'),
|
||||
detail: t('manager.allMissingNodesInstalled'),
|
||||
life: 3000
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -42,9 +42,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { whenever } from '@vueuse/core'
|
||||
import Message from 'primevue/message'
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import { useSystemStatsStore } from '@/stores/systemStatsStore'
|
||||
@@ -60,20 +59,11 @@ const hasMissingCoreNodes = computed(() => {
|
||||
return Object.keys(props.missingCoreNodes).length > 0
|
||||
})
|
||||
|
||||
const currentComfyUIVersion = ref<string | null>(null)
|
||||
whenever(
|
||||
hasMissingCoreNodes,
|
||||
async () => {
|
||||
if (!systemStatsStore.systemStats) {
|
||||
await systemStatsStore.fetchSystemStats()
|
||||
}
|
||||
currentComfyUIVersion.value =
|
||||
systemStatsStore.systemStats?.system?.comfyui_version ?? null
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
// Use computed for reactive version tracking
|
||||
const currentComfyUIVersion = computed<string | null>(() => {
|
||||
if (!hasMissingCoreNodes.value) return null
|
||||
return systemStatsStore.systemStats?.system?.comfyui_version ?? null
|
||||
})
|
||||
|
||||
const sortedMissingCoreNodes = computed(() => {
|
||||
return Object.entries(props.missingCoreNodes).sort(([a], [b]) => {
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<!-- Conflict Warning Banner -->
|
||||
<div
|
||||
v-if="shouldShowManagerBanner"
|
||||
class="bg-yellow-600 bg-opacity-20 border border-yellow-400 rounded-lg p-4 mt-3 mb-4 flex items-center gap-6 relative"
|
||||
class="bg-yellow-500/20 rounded-lg p-4 mt-3 mb-4 flex items-center gap-6 relative"
|
||||
>
|
||||
<i class="pi pi-exclamation-triangle text-yellow-600 text-lg"></i>
|
||||
<div class="flex flex-col gap-2 flex-1">
|
||||
@@ -46,14 +46,15 @@
|
||||
{{ $t('manager.conflicts.warningBanner.button') }}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="absolute top-2 right-2 w-6 h-6 border-none outline-none bg-transparent flex items-center justify-center text-yellow-600 rounded transition-colors"
|
||||
:aria-label="$t('g.close')"
|
||||
<IconButton
|
||||
class="absolute top-0 right-0"
|
||||
type="transparent"
|
||||
@click="dismissWarningBanner"
|
||||
>
|
||||
<i class="pi pi-times text-sm"></i>
|
||||
</button>
|
||||
<i
|
||||
class="pi pi-times text-neutral-900 dark-theme:text-white text-xs"
|
||||
></i>
|
||||
</IconButton>
|
||||
</div>
|
||||
<RegistrySearchBar
|
||||
v-model:searchQuery="searchQuery"
|
||||
@@ -138,6 +139,7 @@ import {
|
||||
} from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import IconButton from '@/components/button/IconButton.vue'
|
||||
import ContentDivider from '@/components/common/ContentDivider.vue'
|
||||
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
|
||||
import VirtualGrid from '@/components/common/VirtualGrid.vue'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { VueWrapper, mount } from '@vue/test-utils'
|
||||
import { createPinia } from 'pinia'
|
||||
import PrimeVue from 'primevue/config'
|
||||
import Tooltip from 'primevue/tooltip'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { nextTick } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
@@ -31,11 +32,14 @@ const mockInstalledPacks = {
|
||||
'installed-pack': { ver: '2.0.0' }
|
||||
}
|
||||
|
||||
const mockIsPackEnabled = vi.fn(() => true)
|
||||
|
||||
vi.mock('@/stores/comfyManagerStore', () => ({
|
||||
useComfyManagerStore: vi.fn(() => ({
|
||||
installedPacks: mockInstalledPacks,
|
||||
isPackInstalled: (id: string) =>
|
||||
!!mockInstalledPacks[id as keyof typeof mockInstalledPacks]
|
||||
!!mockInstalledPacks[id as keyof typeof mockInstalledPacks],
|
||||
isPackEnabled: mockIsPackEnabled
|
||||
}))
|
||||
}))
|
||||
|
||||
@@ -60,6 +64,7 @@ describe('PackVersionBadge', () => {
|
||||
beforeEach(() => {
|
||||
mockToggle.mockReset()
|
||||
mockHide.mockReset()
|
||||
mockIsPackEnabled.mockReturnValue(true) // Reset to default enabled state
|
||||
})
|
||||
|
||||
const mountComponent = ({
|
||||
@@ -79,6 +84,9 @@ describe('PackVersionBadge', () => {
|
||||
},
|
||||
global: {
|
||||
plugins: [PrimeVue, createPinia(), i18n],
|
||||
directives: {
|
||||
tooltip: Tooltip
|
||||
},
|
||||
stubs: {
|
||||
Popover: PopoverStub,
|
||||
PackVersionSelectorPopover: true
|
||||
@@ -229,4 +237,63 @@ describe('PackVersionBadge', () => {
|
||||
expect(mockHide).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('disabled state', () => {
|
||||
beforeEach(() => {
|
||||
mockIsPackEnabled.mockReturnValue(false) // Set all packs as disabled for these tests
|
||||
})
|
||||
|
||||
it('adds disabled styles when pack is disabled', () => {
|
||||
const wrapper = mountComponent()
|
||||
|
||||
const badge = wrapper.find('[role="text"]') // role changes to "text" when disabled
|
||||
expect(badge.exists()).toBe(true)
|
||||
expect(badge.classes()).toContain('cursor-not-allowed')
|
||||
expect(badge.classes()).toContain('opacity-60')
|
||||
})
|
||||
|
||||
it('does not show chevron icon when disabled', () => {
|
||||
const wrapper = mountComponent()
|
||||
|
||||
const chevronIcon = wrapper.find('.pi-chevron-right')
|
||||
expect(chevronIcon.exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('does not show update arrow when disabled', () => {
|
||||
const wrapper = mountComponent()
|
||||
|
||||
const updateIcon = wrapper.find('.pi-arrow-circle-up')
|
||||
expect(updateIcon.exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('does not toggle popover when clicked while disabled', async () => {
|
||||
const wrapper = mountComponent()
|
||||
|
||||
const badge = wrapper.find('[role="text"]') // role changes to "text" when disabled
|
||||
expect(badge.exists()).toBe(true)
|
||||
await badge.trigger('click')
|
||||
|
||||
// Since it's disabled, the popover should not be toggled
|
||||
expect(mockToggle).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('has correct tabindex when disabled', () => {
|
||||
const wrapper = mountComponent()
|
||||
|
||||
const badge = wrapper.find('[role="text"]') // role changes to "text" when disabled
|
||||
expect(badge.exists()).toBe(true)
|
||||
expect(badge.attributes('tabindex')).toBe('-1')
|
||||
})
|
||||
|
||||
it('does not respond to keyboard events when disabled', async () => {
|
||||
const wrapper = mountComponent()
|
||||
|
||||
const badge = wrapper.find('[role="text"]') // role changes to "text" when disabled
|
||||
expect(badge.exists()).toBe(true)
|
||||
await badge.trigger('keydown.enter')
|
||||
await badge.trigger('keydown.space')
|
||||
|
||||
expect(mockToggle).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,21 +1,28 @@
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="inline-flex items-center gap-1 rounded-2xl text-xs cursor-pointer py-1"
|
||||
:class="{ 'bg-gray-100 dark-theme:bg-neutral-700 px-1.5': fill }"
|
||||
aria-haspopup="true"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="toggleVersionSelector"
|
||||
@keydown.enter="toggleVersionSelector"
|
||||
@keydown.space="toggleVersionSelector"
|
||||
v-tooltip.top="
|
||||
isDisabled ? $t('manager.enablePackToChangeVersion') : null
|
||||
"
|
||||
class="inline-flex items-center gap-1 rounded-2xl text-xs py-1"
|
||||
:class="{
|
||||
'bg-gray-100 dark-theme:bg-neutral-700 px-1.5': fill,
|
||||
'cursor-pointer': !isDisabled,
|
||||
'cursor-not-allowed opacity-60': isDisabled
|
||||
}"
|
||||
:aria-haspopup="!isDisabled"
|
||||
:role="isDisabled ? 'text' : 'button'"
|
||||
:tabindex="isDisabled ? -1 : 0"
|
||||
@click="!isDisabled && toggleVersionSelector($event)"
|
||||
@keydown.enter="!isDisabled && toggleVersionSelector($event)"
|
||||
@keydown.space="!isDisabled && toggleVersionSelector($event)"
|
||||
>
|
||||
<i
|
||||
v-if="isUpdateAvailable"
|
||||
class="pi pi-arrow-circle-up text-blue-600 text-xs"
|
||||
/>
|
||||
<span>{{ installedVersion }}</span>
|
||||
<i class="pi pi-chevron-right text-xxs" />
|
||||
<i v-if="!isDisabled" class="pi pi-chevron-right text-xxs" />
|
||||
</div>
|
||||
|
||||
<Popover
|
||||
@@ -61,6 +68,11 @@ const popoverRef = ref()
|
||||
|
||||
const managerStore = useComfyManagerStore()
|
||||
|
||||
const isInstalled = computed(() => managerStore.isPackInstalled(nodePack?.id))
|
||||
const isDisabled = computed(
|
||||
() => isInstalled.value && !managerStore.isPackEnabled(nodePack?.id)
|
||||
)
|
||||
|
||||
const installedVersion = computed(() => {
|
||||
if (!nodePack.id) return 'nightly'
|
||||
const version =
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<IconTextButton
|
||||
v-tooltip.top="
|
||||
hasDisabledUpdatePacks ? $t('manager.disabledNodesWontUpdate') : null
|
||||
"
|
||||
v-bind="$attrs"
|
||||
type="transparent"
|
||||
:label="$t('manager.updateAll')"
|
||||
@@ -24,8 +27,9 @@ import type { components } from '@/types/comfyRegistryTypes'
|
||||
|
||||
type NodePack = components['schemas']['Node']
|
||||
|
||||
const { nodePacks } = defineProps<{
|
||||
const { nodePacks, hasDisabledUpdatePacks } = defineProps<{
|
||||
nodePacks: NodePack[]
|
||||
hasDisabledUpdatePacks?: boolean
|
||||
}>()
|
||||
|
||||
const isUpdating = ref<boolean>(false)
|
||||
|
||||
@@ -13,7 +13,11 @@
|
||||
:has-conflict="hasConflicts"
|
||||
:conflict-info="conflictInfo"
|
||||
/>
|
||||
<PackEnableToggle v-else :node-pack="nodePack" />
|
||||
<PackEnableToggle
|
||||
v-else
|
||||
:has-conflict="hasConflicts"
|
||||
:node-pack="nodePack"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -34,7 +34,8 @@
|
||||
/>
|
||||
<PackUpdateButton
|
||||
v-if="isUpdateAvailableTab && hasUpdateAvailable"
|
||||
:node-packs="updateAvailableNodePacks"
|
||||
:node-packs="enabledUpdateAvailableNodePacks"
|
||||
:has-disabled-update-packs="hasDisabledUpdatePacks"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex mt-3 text-sm">
|
||||
@@ -103,8 +104,11 @@ const { t } = useI18n()
|
||||
const { missingNodePacks, isLoading, error } = useMissingNodes()
|
||||
|
||||
// Use the composable to get update available nodes
|
||||
const { hasUpdateAvailable, updateAvailableNodePacks } =
|
||||
useUpdateAvailableNodes()
|
||||
const {
|
||||
hasUpdateAvailable,
|
||||
enabledUpdateAvailableNodePacks,
|
||||
hasDisabledUpdatePacks
|
||||
} = useUpdateAvailableNodes()
|
||||
|
||||
const hasResults = computed(
|
||||
() => searchQuery.value?.trim() && searchResults?.length
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
<script setup lang="ts">
|
||||
import Divider from 'primevue/divider'
|
||||
import Tag from 'primevue/tag'
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
import SystemStatsPanel from '@/components/common/SystemStatsPanel.vue'
|
||||
import { useAboutPanelStore } from '@/stores/aboutPanelStore'
|
||||
@@ -44,10 +43,4 @@ import PanelTemplate from './PanelTemplate.vue'
|
||||
|
||||
const systemStatsStore = useSystemStatsStore()
|
||||
const aboutPanelStore = useAboutPanelStore()
|
||||
|
||||
onMounted(async () => {
|
||||
if (!systemStatsStore.systemStats) {
|
||||
await systemStatsStore.fetchSystemStats()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
<div
|
||||
ref="toolboxRef"
|
||||
style="transform: translate(var(--tb-x), var(--tb-y))"
|
||||
class="fixed left-0 top-0 z-40"
|
||||
class="fixed left-0 top-0 z-40 pointer-events-none"
|
||||
>
|
||||
<Transition name="slide-up">
|
||||
<Panel
|
||||
v-if="visible"
|
||||
class="rounded-lg selection-toolbox"
|
||||
class="rounded-lg selection-toolbox pointer-events-auto"
|
||||
:pt="{
|
||||
header: 'hidden',
|
||||
content: 'p-0 flex flex-row'
|
||||
@@ -83,7 +83,6 @@ const extensionToolboxCommands = computed<ComfyCommandImpl[]>(() => {
|
||||
<style scoped>
|
||||
.selection-toolbox {
|
||||
transform: translateX(-50%) translateY(-120%);
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
|
||||
@@ -147,7 +147,8 @@ watch(
|
||||
showColorPicker.value = false
|
||||
selectedColorOption.value = null
|
||||
currentColorOption.value = getItemsColorOption(newSelectedItems)
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
|
||||
@@ -142,11 +142,12 @@ import { useI18n } from 'vue-i18n'
|
||||
|
||||
import PuzzleIcon from '@/components/icons/PuzzleIcon.vue'
|
||||
import { useConflictAcknowledgment } from '@/composables/useConflictAcknowledgment'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { useManagerState } from '@/composables/useManagerState'
|
||||
import { type ReleaseNote } from '@/services/releaseService'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useReleaseStore } from '@/stores/releaseStore'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
import { ManagerTab } from '@/types/comfyManagerTypes'
|
||||
import { electronAPI, isElectron } from '@/utils/envUtil'
|
||||
import { formatVersionAnchor } from '@/utils/formatUtil'
|
||||
|
||||
@@ -191,7 +192,6 @@ const { t, locale } = useI18n()
|
||||
const releaseStore = useReleaseStore()
|
||||
const commandStore = useCommandStore()
|
||||
const settingStore = useSettingStore()
|
||||
const dialogService = useDialogService()
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
@@ -313,8 +313,11 @@ const menuItems = computed<MenuItem[]>(() => {
|
||||
icon: PuzzleIcon,
|
||||
label: t('helpCenter.managerExtension'),
|
||||
showRedDot: shouldShowManagerRedDot.value,
|
||||
action: () => {
|
||||
dialogService.showManagerDialog()
|
||||
action: async () => {
|
||||
await useManagerState().openManager({
|
||||
initialTab: ManagerTab.All,
|
||||
showToastOnLegacyError: false
|
||||
})
|
||||
emit('close')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -88,8 +88,8 @@ const { shouldShowRedDot: shouldShowConflictRedDot, markConflictsAsSeen } =
|
||||
useConflictAcknowledgment()
|
||||
|
||||
// Use either release red dot or conflict red dot
|
||||
const shouldShowRedDot = computed(() => {
|
||||
const releaseRedDot = showReleaseRedDot
|
||||
const shouldShowRedDot = computed((): boolean => {
|
||||
const releaseRedDot = showReleaseRedDot.value
|
||||
return releaseRedDot || shouldShowConflictRedDot.value
|
||||
})
|
||||
|
||||
|
||||
@@ -106,16 +106,13 @@ import { useI18n } from 'vue-i18n'
|
||||
import SubgraphBreadcrumb from '@/components/breadcrumb/SubgraphBreadcrumb.vue'
|
||||
import SettingDialogContent from '@/components/dialog/content/SettingDialogContent.vue'
|
||||
import SettingDialogHeader from '@/components/dialog/header/SettingDialogHeader.vue'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { useManagerState } from '@/composables/useManagerState'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
import {
|
||||
ManagerUIState,
|
||||
useManagerStateStore
|
||||
} from '@/stores/managerStateStore'
|
||||
import { useMenuItemStore } from '@/stores/menuItemStore'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
|
||||
import { ManagerTab } from '@/types/comfyManagerTypes'
|
||||
import { showNativeSystemMenu } from '@/utils/envUtil'
|
||||
import { normalizeI18nKey } from '@/utils/formatUtil'
|
||||
import { whileMouseDown } from '@/utils/mouseDownUtil'
|
||||
@@ -127,6 +124,8 @@ const dialogStore = useDialogStore()
|
||||
const settingStore = useSettingStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
const managerState = useManagerState()
|
||||
|
||||
const menuRef = ref<
|
||||
({ dirty: boolean } & TieredMenuMethods & TieredMenuState) | null
|
||||
>(null)
|
||||
@@ -159,29 +158,11 @@ const showSettings = (defaultPanel?: string) => {
|
||||
})
|
||||
}
|
||||
|
||||
const managerStateStore = useManagerStateStore()
|
||||
|
||||
const showManageExtensions = async () => {
|
||||
const state = managerStateStore.managerUIState
|
||||
|
||||
switch (state) {
|
||||
case ManagerUIState.DISABLED:
|
||||
showSettings('extension')
|
||||
break
|
||||
|
||||
case ManagerUIState.LEGACY_UI:
|
||||
try {
|
||||
await commandStore.execute('Comfy.Manager.Menu.ToggleVisibility')
|
||||
} catch {
|
||||
// If legacy command doesn't exist, fall back to extensions panel
|
||||
showSettings('extension')
|
||||
}
|
||||
break
|
||||
|
||||
case ManagerUIState.NEW_UI:
|
||||
useDialogService().showManagerDialog()
|
||||
break
|
||||
}
|
||||
await managerState.openManager({
|
||||
initialTab: ManagerTab.All,
|
||||
showToastOnLegacyError: false
|
||||
})
|
||||
}
|
||||
|
||||
const extraMenuItems = computed<MenuItem[]>(() => [
|
||||
|
||||
@@ -109,6 +109,66 @@ const pixversePricingCalculator = (node: LGraphNode): string => {
|
||||
return '$0.9/Run'
|
||||
}
|
||||
|
||||
const byteDanceVideoPricingCalculator = (node: LGraphNode): string => {
|
||||
const modelWidget = node.widgets?.find(
|
||||
(w) => w.name === 'model'
|
||||
) as IComboWidget
|
||||
const durationWidget = node.widgets?.find(
|
||||
(w) => w.name === 'duration'
|
||||
) as IComboWidget
|
||||
const resolutionWidget = node.widgets?.find(
|
||||
(w) => w.name === 'resolution'
|
||||
) as IComboWidget
|
||||
|
||||
if (!modelWidget || !durationWidget || !resolutionWidget) return 'Token-based'
|
||||
|
||||
const model = String(modelWidget.value).toLowerCase()
|
||||
const resolution = String(resolutionWidget.value).toLowerCase()
|
||||
const seconds = parseFloat(String(durationWidget.value))
|
||||
const priceByModel: Record<string, Record<string, [number, number]>> = {
|
||||
'seedance-1-0-pro': {
|
||||
'480p': [0.23, 0.24],
|
||||
'720p': [0.51, 0.56],
|
||||
'1080p': [1.18, 1.22]
|
||||
},
|
||||
'seedance-1-0-lite': {
|
||||
'480p': [0.17, 0.18],
|
||||
'720p': [0.37, 0.41],
|
||||
'1080p': [0.85, 0.88]
|
||||
}
|
||||
}
|
||||
|
||||
const modelKey = model.includes('seedance-1-0-pro')
|
||||
? 'seedance-1-0-pro'
|
||||
: model.includes('seedance-1-0-lite')
|
||||
? 'seedance-1-0-lite'
|
||||
: ''
|
||||
|
||||
const resKey = resolution.includes('1080')
|
||||
? '1080p'
|
||||
: resolution.includes('720')
|
||||
? '720p'
|
||||
: resolution.includes('480')
|
||||
? '480p'
|
||||
: ''
|
||||
|
||||
const baseRange =
|
||||
modelKey && resKey ? priceByModel[modelKey]?.[resKey] : undefined
|
||||
if (!baseRange) return 'Token-based'
|
||||
|
||||
const [min10s, max10s] = baseRange
|
||||
const scale = seconds / 10
|
||||
const minCost = min10s * scale
|
||||
const maxCost = max10s * scale
|
||||
|
||||
const minStr = `$${minCost.toFixed(2)}/Run`
|
||||
const maxStr = `$${maxCost.toFixed(2)}/Run`
|
||||
|
||||
return minStr === maxStr
|
||||
? minStr
|
||||
: `$${minCost.toFixed(2)}-$${maxCost.toFixed(2)}/Run`
|
||||
}
|
||||
|
||||
/**
|
||||
* Static pricing data for API nodes, now supporting both strings and functions
|
||||
*/
|
||||
@@ -993,7 +1053,7 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
|
||||
) as IComboWidget
|
||||
|
||||
if (!modelWidget || !generateAudioWidget) {
|
||||
return '$2.00-6.00/Run (varies with model & audio generation)'
|
||||
return '$0.80-3.20/Run (varies with model & audio generation)'
|
||||
}
|
||||
|
||||
const model = String(modelWidget.value)
|
||||
@@ -1001,13 +1061,13 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
|
||||
String(generateAudioWidget.value).toLowerCase() === 'true'
|
||||
|
||||
if (model.includes('veo-3.0-fast-generate-001')) {
|
||||
return generateAudio ? '$3.20/Run' : '$2.00/Run'
|
||||
return generateAudio ? '$1.20/Run' : '$0.80/Run'
|
||||
} else if (model.includes('veo-3.0-generate-001')) {
|
||||
return generateAudio ? '$6.00/Run' : '$4.00/Run'
|
||||
return generateAudio ? '$3.20/Run' : '$1.60/Run'
|
||||
}
|
||||
|
||||
// Default fallback
|
||||
return '$2.00-6.00/Run'
|
||||
return '$0.80-3.20/Run'
|
||||
}
|
||||
},
|
||||
LumaImageNode: {
|
||||
@@ -1441,6 +1501,44 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
|
||||
}
|
||||
return 'Token-based'
|
||||
}
|
||||
},
|
||||
ByteDanceSeedreamNode: {
|
||||
displayPrice: (node: LGraphNode): string => {
|
||||
const sequentialGenerationWidget = node.widgets?.find(
|
||||
(w) => w.name === 'sequential_image_generation'
|
||||
) as IComboWidget
|
||||
const maxImagesWidget = node.widgets?.find(
|
||||
(w) => w.name === 'max_images'
|
||||
) as IComboWidget
|
||||
|
||||
if (!sequentialGenerationWidget || !maxImagesWidget)
|
||||
return '$0.03/Run ($0.03 for one output image)'
|
||||
|
||||
if (
|
||||
String(sequentialGenerationWidget.value).toLowerCase() === 'disabled'
|
||||
) {
|
||||
return '$0.03/Run'
|
||||
}
|
||||
|
||||
const maxImages = Number(maxImagesWidget.value)
|
||||
if (maxImages === 1) {
|
||||
return '$0.03/Run'
|
||||
}
|
||||
const cost = (0.03 * maxImages).toFixed(2)
|
||||
return `$${cost}/Run ($0.03 for one output image)`
|
||||
}
|
||||
},
|
||||
ByteDanceTextToVideoNode: {
|
||||
displayPrice: byteDanceVideoPricingCalculator
|
||||
},
|
||||
ByteDanceImageToVideoNode: {
|
||||
displayPrice: byteDanceVideoPricingCalculator
|
||||
},
|
||||
ByteDanceFirstLastFrameNode: {
|
||||
displayPrice: byteDanceVideoPricingCalculator
|
||||
},
|
||||
ByteDanceImageReferenceNode: {
|
||||
displayPrice: byteDanceVideoPricingCalculator
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1531,7 +1629,16 @@ export const useNodePricing = () => {
|
||||
OpenAIChatNode: ['model'],
|
||||
// ByteDance
|
||||
ByteDanceImageNode: ['model'],
|
||||
ByteDanceImageEditNode: ['model']
|
||||
ByteDanceImageEditNode: ['model'],
|
||||
ByteDanceSeedreamNode: [
|
||||
'model',
|
||||
'sequential_image_generation',
|
||||
'max_images'
|
||||
],
|
||||
ByteDanceTextToVideoNode: ['model', 'duration', 'resolution'],
|
||||
ByteDanceImageToVideoNode: ['model', 'duration', 'resolution'],
|
||||
ByteDanceFirstLastFrameNode: ['model', 'duration', 'resolution'],
|
||||
ByteDanceImageReferenceNode: ['model', 'duration', 'resolution']
|
||||
}
|
||||
return widgetMap[nodeType] || []
|
||||
}
|
||||
|
||||
@@ -44,9 +44,24 @@ export const useUpdateAvailableNodes = () => {
|
||||
return filterOutdatedPacks(installedPacks.value)
|
||||
})
|
||||
|
||||
// Check if there are any outdated packs
|
||||
// Filter only enabled outdated packs
|
||||
const enabledUpdateAvailableNodePacks = computed(() => {
|
||||
return updateAvailableNodePacks.value.filter((pack) =>
|
||||
comfyManagerStore.isPackEnabled(pack.id)
|
||||
)
|
||||
})
|
||||
|
||||
// Check if there are any enabled outdated packs
|
||||
const hasUpdateAvailable = computed(() => {
|
||||
return updateAvailableNodePacks.value.length > 0
|
||||
return enabledUpdateAvailableNodePacks.value.length > 0
|
||||
})
|
||||
|
||||
// Check if there are disabled packs with updates
|
||||
const hasDisabledUpdatePacks = computed(() => {
|
||||
return (
|
||||
updateAvailableNodePacks.value.length >
|
||||
enabledUpdateAvailableNodePacks.value.length
|
||||
)
|
||||
})
|
||||
|
||||
// Automatically fetch installed pack data when composable is used
|
||||
@@ -58,7 +73,9 @@ export const useUpdateAvailableNodes = () => {
|
||||
|
||||
return {
|
||||
updateAvailableNodePacks,
|
||||
enabledUpdateAvailableNodePacks,
|
||||
hasUpdateAvailable,
|
||||
hasDisabledUpdatePacks,
|
||||
isLoading,
|
||||
error
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ export const useWorkflowPacks = (options: UseNodePacksOptions = {}) => {
|
||||
const nodeDef = nodeDefStore.nodeDefsByName[nodeName]
|
||||
if (nodeDef?.nodeSource.type === 'core') {
|
||||
if (!systemStatsStore.systemStats) {
|
||||
await systemStatsStore.fetchSystemStats()
|
||||
await systemStatsStore.refetchSystemStats()
|
||||
}
|
||||
return {
|
||||
id: CORE_NODES_PACK_NAME,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { until } from '@vueuse/core'
|
||||
import { uniqBy } from 'es-toolkit/compat'
|
||||
import { computed, getCurrentInstance, onUnmounted, readonly, ref } from 'vue'
|
||||
|
||||
@@ -21,6 +22,7 @@ import type {
|
||||
NodePackRequirements,
|
||||
SystemEnvironment
|
||||
} from '@/types/conflictDetectionTypes'
|
||||
import { normalizePackId } from '@/utils/packUtils'
|
||||
import {
|
||||
cleanVersion,
|
||||
satisfiesVersion,
|
||||
@@ -78,9 +80,8 @@ export function useConflictDetection() {
|
||||
try {
|
||||
// Get system stats from store (primary source of system information)
|
||||
const systemStatsStore = useSystemStatsStore()
|
||||
if (!systemStatsStore.systemStats) {
|
||||
await systemStatsStore.fetchSystemStats()
|
||||
}
|
||||
// Wait for systemStats to be initialized if not already
|
||||
await until(systemStatsStore.isInitialized)
|
||||
|
||||
// Fetch version information from backend (with error resilience)
|
||||
const [frontendVersion] = await Promise.allSettled([
|
||||
@@ -127,7 +128,7 @@ export function useConflictDetection() {
|
||||
}
|
||||
|
||||
systemEnvironment.value = environment
|
||||
console.log(
|
||||
console.debug(
|
||||
'[ConflictDetection] System environment detection completed:',
|
||||
environment
|
||||
)
|
||||
@@ -427,7 +428,7 @@ export function useConflictDetection() {
|
||||
Object.entries(bulkResult).forEach(([packageId, failInfo]) => {
|
||||
if (failInfo !== null) {
|
||||
importFailures[packageId] = failInfo
|
||||
console.log(
|
||||
console.debug(
|
||||
`[ConflictDetection] Import failure found for ${packageId}:`,
|
||||
failInfo
|
||||
)
|
||||
@@ -500,7 +501,7 @@ export function useConflictDetection() {
|
||||
*/
|
||||
async function performConflictDetection(): Promise<ConflictDetectionResponse> {
|
||||
if (isDetecting.value) {
|
||||
console.log('[ConflictDetection] Already detecting, skipping')
|
||||
console.debug('[ConflictDetection] Already detecting, skipping')
|
||||
return {
|
||||
success: false,
|
||||
error_message: 'Already detecting conflicts',
|
||||
@@ -556,7 +557,10 @@ export function useConflictDetection() {
|
||||
detectionSummary.value = summary
|
||||
lastDetectionTime.value = new Date().toISOString()
|
||||
|
||||
console.log('[ConflictDetection] Conflict detection completed:', summary)
|
||||
console.debug(
|
||||
'[ConflictDetection] Conflict detection completed:',
|
||||
summary
|
||||
)
|
||||
|
||||
// Store conflict results for later UI display
|
||||
// Dialog will be shown based on specific events, not on app mount
|
||||
@@ -568,7 +572,7 @@ export function useConflictDetection() {
|
||||
// Merge conflicts for packages with the same name
|
||||
const mergedConflicts = mergeConflictsByPackageName(conflictedResults)
|
||||
|
||||
console.log(
|
||||
console.debug(
|
||||
'[ConflictDetection] Conflicts detected (stored for UI):',
|
||||
mergedConflicts
|
||||
)
|
||||
@@ -632,11 +636,22 @@ export function useConflictDetection() {
|
||||
/**
|
||||
* Error-resilient initialization (called on app mount).
|
||||
* Async function that doesn't block UI setup.
|
||||
* Ensures proper order: installed -> system_stats -> versions bulk -> import_fail_info_bulk
|
||||
* Ensures proper order: system_stats -> manager state -> installed -> versions bulk -> import_fail_info_bulk
|
||||
*/
|
||||
async function initializeConflictDetection(): Promise<void> {
|
||||
try {
|
||||
// Simply perform conflict detection
|
||||
// Check if manager is new Manager before proceeding
|
||||
const { useManagerState } = await import('@/composables/useManagerState')
|
||||
const managerState = useManagerState()
|
||||
|
||||
if (!managerState.isNewManagerUI.value) {
|
||||
console.debug(
|
||||
'[ConflictDetection] Manager is not new Manager, skipping conflict detection'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Manager is new Manager, perform conflict detection
|
||||
// The useInstalledPacks will handle fetching installed list if needed
|
||||
await performConflictDetection()
|
||||
} catch (error) {
|
||||
@@ -671,13 +686,13 @@ export function useConflictDetection() {
|
||||
* Check if conflicts should trigger modal display after "What's New" dismissal
|
||||
*/
|
||||
async function shouldShowConflictModalAfterUpdate(): Promise<boolean> {
|
||||
console.log(
|
||||
console.debug(
|
||||
'[ConflictDetection] Checking if conflict modal should show after update...'
|
||||
)
|
||||
|
||||
// Ensure conflict detection has run
|
||||
if (detectionResults.value.length === 0) {
|
||||
console.log(
|
||||
console.debug(
|
||||
'[ConflictDetection] No detection results, running conflict detection...'
|
||||
)
|
||||
await performConflictDetection()
|
||||
@@ -689,7 +704,7 @@ export function useConflictDetection() {
|
||||
const hasActualConflicts = hasConflicts.value
|
||||
const canShowModal = acknowledgment.shouldShowConflictModal.value
|
||||
|
||||
console.log('[ConflictDetection] Modal check:', {
|
||||
console.debug('[ConflictDetection] Modal check:', {
|
||||
hasConflicts: hasActualConflicts,
|
||||
canShowModal: canShowModal,
|
||||
conflictedPackagesCount: conflictedPackages.value.length
|
||||
@@ -860,9 +875,7 @@ function mergeConflictsByPackageName(
|
||||
|
||||
conflicts.forEach((conflict) => {
|
||||
// Normalize package name by removing version suffix (@1_0_3) for consistent merging
|
||||
const normalizedPackageName = conflict.package_name.includes('@')
|
||||
? conflict.package_name.substring(0, conflict.package_name.indexOf('@'))
|
||||
: conflict.package_name
|
||||
const normalizedPackageName = normalizePackId(conflict.package_name)
|
||||
|
||||
if (mergedMap.has(normalizedPackageName)) {
|
||||
// Package already exists, merge conflicts
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
|
||||
import { useSelectedLiteGraphItems } from '@/composables/canvas/useSelectedLiteGraphItems'
|
||||
import { ManagerUIState, useManagerState } from '@/composables/useManagerState'
|
||||
import { useModelSelectorDialog } from '@/composables/useModelSelectorDialog'
|
||||
import {
|
||||
DEFAULT_DARK_COLOR_PALETTE,
|
||||
@@ -20,15 +21,10 @@ import { useDialogService } from '@/services/dialogService'
|
||||
import { useLitegraphService } from '@/services/litegraphService'
|
||||
import { useWorkflowService } from '@/services/workflowService'
|
||||
import type { ComfyCommand } from '@/stores/commandStore'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useExecutionStore } from '@/stores/executionStore'
|
||||
import { useCanvasStore, useTitleEditorStore } from '@/stores/graphStore'
|
||||
import { useHelpCenterStore } from '@/stores/helpCenterStore'
|
||||
import { useNodeOutputStore } from '@/stores/imagePreviewStore'
|
||||
import {
|
||||
ManagerUIState,
|
||||
useManagerStateStore
|
||||
} from '@/stores/managerStateStore'
|
||||
import { useQueueSettingsStore, useQueueStore } from '@/stores/queueStore'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore'
|
||||
@@ -720,34 +716,9 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
label: 'Custom Nodes Manager',
|
||||
versionAdded: '1.12.10',
|
||||
function: async () => {
|
||||
const managerState = useManagerStateStore().managerUIState
|
||||
|
||||
switch (managerState) {
|
||||
case ManagerUIState.DISABLED:
|
||||
dialogService.showSettingsDialog('extension')
|
||||
break
|
||||
|
||||
case ManagerUIState.LEGACY_UI:
|
||||
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()
|
||||
}
|
||||
break
|
||||
|
||||
case ManagerUIState.NEW_UI:
|
||||
dialogService.showManagerDialog()
|
||||
break
|
||||
}
|
||||
await useManagerState().openManager({
|
||||
showToastOnLegacyError: true
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -755,33 +726,25 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
icon: 'pi pi-sync',
|
||||
label: 'Check for Custom Node Updates',
|
||||
versionAdded: '1.17.0',
|
||||
function: () => {
|
||||
const managerStore = useManagerStateStore()
|
||||
const state = managerStore.managerUIState
|
||||
function: async () => {
|
||||
const managerState = useManagerState()
|
||||
const state = managerState.managerUIState.value
|
||||
|
||||
switch (state) {
|
||||
case ManagerUIState.DISABLED:
|
||||
toastStore.add({
|
||||
severity: 'error',
|
||||
summary: t('g.error'),
|
||||
detail: t('manager.notAvailable'),
|
||||
life: 3000
|
||||
})
|
||||
break
|
||||
|
||||
case ManagerUIState.LEGACY_UI:
|
||||
useCommandStore()
|
||||
.execute('Comfy.Manager.Menu.ToggleVisibility')
|
||||
.catch(() => {
|
||||
// If legacy command doesn't exist, fall back to extensions panel
|
||||
dialogService.showSettingsDialog('extension')
|
||||
})
|
||||
break
|
||||
|
||||
case ManagerUIState.NEW_UI:
|
||||
dialogService.showManagerDialog()
|
||||
break
|
||||
// For DISABLED state, show error toast instead of opening settings
|
||||
if (state === ManagerUIState.DISABLED) {
|
||||
toastStore.add({
|
||||
severity: 'error',
|
||||
summary: t('g.error'),
|
||||
detail: t('manager.notAvailable'),
|
||||
life: 3000
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
await managerState.openManager({
|
||||
initialTab: ManagerTab.UpdateAvailable,
|
||||
showToastOnLegacyError: false
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -790,32 +753,10 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
label: 'Install Missing Custom Nodes',
|
||||
versionAdded: '1.17.0',
|
||||
function: async () => {
|
||||
const managerStore = useManagerStateStore()
|
||||
const state = managerStore.managerUIState
|
||||
|
||||
switch (state) {
|
||||
case ManagerUIState.DISABLED:
|
||||
// When manager is disabled, open the extensions panel in settings
|
||||
dialogService.showSettingsDialog('extension')
|
||||
break
|
||||
|
||||
case ManagerUIState.LEGACY_UI:
|
||||
try {
|
||||
await useCommandStore().execute(
|
||||
'Comfy.Manager.Menu.ToggleVisibility'
|
||||
)
|
||||
} catch {
|
||||
// If legacy command doesn't exist, fall back to extensions panel
|
||||
dialogService.showSettingsDialog('extension')
|
||||
}
|
||||
break
|
||||
|
||||
case ManagerUIState.NEW_UI:
|
||||
dialogService.showManagerDialog({
|
||||
initialTab: ManagerTab.Missing
|
||||
})
|
||||
break
|
||||
}
|
||||
await useManagerState().openManager({
|
||||
initialTab: ManagerTab.Missing,
|
||||
showToastOnLegacyError: false
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -921,8 +862,11 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
id: 'Comfy.OpenManagerDialog',
|
||||
icon: 'mdi mdi-puzzle-outline',
|
||||
label: 'Manager',
|
||||
function: () => {
|
||||
dialogService.showManagerDialog()
|
||||
function: async () => {
|
||||
await useManagerState().openManager({
|
||||
initialTab: ManagerTab.All,
|
||||
showToastOnLegacyError: false
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -987,18 +931,11 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
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
|
||||
})
|
||||
}
|
||||
await useManagerState().openManager({
|
||||
legacyCommand: 'Comfy.Manager.CustomNodesManager.ToggleVisibility',
|
||||
showToastOnLegacyError: true,
|
||||
isLegacyOnly: true
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1007,16 +944,10 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
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
|
||||
})
|
||||
}
|
||||
await useManagerState().openManager({
|
||||
showToastOnLegacyError: true,
|
||||
isLegacyOnly: true
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Ref, computed, ref } from 'vue'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { components } from '@/types/generatedManagerTypes'
|
||||
import { normalizePackKeys } from '@/utils/packUtils'
|
||||
|
||||
type ManagerTaskHistory = Record<
|
||||
string,
|
||||
@@ -98,7 +99,8 @@ export const useManagerQueue = (
|
||||
taskHistory.value = filterHistoryByClientId(state.history)
|
||||
|
||||
if (state.installed_packs) {
|
||||
installedPacks.value = state.installed_packs
|
||||
// Normalize pack keys to ensure consistent access
|
||||
installedPacks.value = normalizePackKeys(state.installed_packs)
|
||||
}
|
||||
updateProcessingState()
|
||||
}
|
||||
|
||||
203
src/composables/useManagerState.ts
Normal file
@@ -0,0 +1,203 @@
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed, readonly } from 'vue'
|
||||
|
||||
import { t } from '@/i18n'
|
||||
import { api } from '@/scripts/api'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useSystemStatsStore } from '@/stores/systemStatsStore'
|
||||
import { useToastStore } from '@/stores/toastStore'
|
||||
import { ManagerTab } from '@/types/comfyManagerTypes'
|
||||
|
||||
export enum ManagerUIState {
|
||||
DISABLED = 'disabled',
|
||||
LEGACY_UI = 'legacy',
|
||||
NEW_UI = 'new'
|
||||
}
|
||||
|
||||
export function useManagerState() {
|
||||
const systemStatsStore = useSystemStatsStore()
|
||||
const { systemStats, isInitialized: systemInitialized } =
|
||||
storeToRefs(systemStatsStore)
|
||||
|
||||
/**
|
||||
* The current manager UI state.
|
||||
* Computed once and cached until dependencies change (which they don't during runtime).
|
||||
* This follows Vue's conventions and provides better performance through caching.
|
||||
*/
|
||||
const managerUIState = readonly(
|
||||
computed((): ManagerUIState => {
|
||||
// Wait for systemStats to be initialized
|
||||
if (!systemInitialized.value) {
|
||||
// Default to DISABLED while loading
|
||||
return ManagerUIState.DISABLED
|
||||
}
|
||||
|
||||
// Get current values
|
||||
const clientSupportsV4 =
|
||||
api.getClientFeatureFlags().supports_manager_v4_ui ?? false
|
||||
|
||||
const serverSupportsV4 = api.getServerFeature(
|
||||
'extension.manager.supports_v4'
|
||||
)
|
||||
|
||||
// Check command line args first (highest priority)
|
||||
if (systemStats.value?.system?.argv?.includes('--disable-manager')) {
|
||||
return ManagerUIState.DISABLED
|
||||
}
|
||||
|
||||
if (
|
||||
systemStats.value?.system?.argv?.includes('--enable-manager-legacy-ui')
|
||||
) {
|
||||
return ManagerUIState.LEGACY_UI
|
||||
}
|
||||
|
||||
// Both client and server support v4 = NEW_UI
|
||||
if (clientSupportsV4 && serverSupportsV4 === true) {
|
||||
return ManagerUIState.NEW_UI
|
||||
}
|
||||
|
||||
// Server supports v4 but client doesn't = LEGACY_UI
|
||||
if (serverSupportsV4 === true && !clientSupportsV4) {
|
||||
return ManagerUIState.LEGACY_UI
|
||||
}
|
||||
|
||||
// Server explicitly doesn't support v4 = LEGACY_UI
|
||||
if (serverSupportsV4 === false) {
|
||||
return ManagerUIState.LEGACY_UI
|
||||
}
|
||||
|
||||
// If server feature flags haven't loaded yet, default to NEW_UI
|
||||
// This is a temporary state - feature flags are exchanged immediately on WebSocket connection
|
||||
// NEW_UI is the safest default since v2 API is the current standard
|
||||
// If the server doesn't support v2, API calls will fail with 404 and be handled gracefully
|
||||
if (serverSupportsV4 === undefined) {
|
||||
return ManagerUIState.NEW_UI
|
||||
}
|
||||
|
||||
// Should never reach here, but if we do, disable manager
|
||||
return ManagerUIState.DISABLED
|
||||
})
|
||||
)
|
||||
|
||||
/**
|
||||
* Check if manager is enabled (not DISABLED)
|
||||
*/
|
||||
const isManagerEnabled = readonly(
|
||||
computed((): boolean => {
|
||||
return managerUIState.value !== ManagerUIState.DISABLED
|
||||
})
|
||||
)
|
||||
|
||||
/**
|
||||
* Check if manager UI is in NEW_UI mode
|
||||
*/
|
||||
const isNewManagerUI = readonly(
|
||||
computed((): boolean => {
|
||||
return managerUIState.value === ManagerUIState.NEW_UI
|
||||
})
|
||||
)
|
||||
|
||||
/**
|
||||
* Check if manager UI is in LEGACY_UI mode
|
||||
*/
|
||||
const isLegacyManagerUI = readonly(
|
||||
computed((): boolean => {
|
||||
return managerUIState.value === ManagerUIState.LEGACY_UI
|
||||
})
|
||||
)
|
||||
|
||||
/**
|
||||
* Check if install button should be shown (only in NEW_UI mode)
|
||||
*/
|
||||
const shouldShowInstallButton = readonly(
|
||||
computed((): boolean => {
|
||||
return isNewManagerUI.value
|
||||
})
|
||||
)
|
||||
|
||||
/**
|
||||
* Check if manager buttons should be shown (when manager is not disabled)
|
||||
*/
|
||||
const shouldShowManagerButtons = readonly(
|
||||
computed((): boolean => {
|
||||
return isManagerEnabled.value
|
||||
})
|
||||
)
|
||||
|
||||
/**
|
||||
* Opens the manager UI based on current state
|
||||
* Centralizes the logic for opening manager across the app
|
||||
* @param options - Optional configuration for opening the manager
|
||||
* @param options.initialTab - Initial tab to show (for NEW_UI mode)
|
||||
* @param options.legacyCommand - Legacy command to execute (for LEGACY_UI mode)
|
||||
* @param options.showToastOnLegacyError - Whether to show toast on legacy command failure
|
||||
* @param options.isLegacyOnly - If true, shows error in NEW_UI mode instead of opening manager
|
||||
*/
|
||||
const openManager = async (options?: {
|
||||
initialTab?: ManagerTab
|
||||
legacyCommand?: string
|
||||
showToastOnLegacyError?: boolean
|
||||
isLegacyOnly?: boolean
|
||||
}): Promise<void> => {
|
||||
const state = managerUIState.value
|
||||
const dialogService = useDialogService()
|
||||
const commandStore = useCommandStore()
|
||||
|
||||
switch (state) {
|
||||
case ManagerUIState.DISABLED:
|
||||
dialogService.showSettingsDialog('extension')
|
||||
break
|
||||
|
||||
case ManagerUIState.LEGACY_UI: {
|
||||
const command =
|
||||
options?.legacyCommand || 'Comfy.Manager.Menu.ToggleVisibility'
|
||||
try {
|
||||
await commandStore.execute(command)
|
||||
} catch {
|
||||
// If legacy command doesn't exist
|
||||
if (options?.showToastOnLegacyError !== false) {
|
||||
useToastStore().add({
|
||||
severity: 'error',
|
||||
summary: t('g.error'),
|
||||
detail: t('manager.legacyMenuNotAvailable'),
|
||||
life: 3000
|
||||
})
|
||||
}
|
||||
// Fallback to extensions panel if not showing toast
|
||||
if (options?.showToastOnLegacyError === false) {
|
||||
dialogService.showSettingsDialog('extension')
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case ManagerUIState.NEW_UI:
|
||||
if (options?.isLegacyOnly) {
|
||||
// Legacy command is not available in NEW_UI mode
|
||||
useToastStore().add({
|
||||
severity: 'error',
|
||||
summary: t('g.error'),
|
||||
detail: t('manager.legacyMenuNotAvailable'),
|
||||
life: 3000
|
||||
})
|
||||
dialogService.showManagerDialog({ initialTab: ManagerTab.All })
|
||||
} else {
|
||||
dialogService.showManagerDialog(
|
||||
options?.initialTab ? { initialTab: options.initialTab } : undefined
|
||||
)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
managerUIState,
|
||||
isManagerEnabled,
|
||||
isNewManagerUI,
|
||||
isLegacyManagerUI,
|
||||
shouldShowInstallButton,
|
||||
shouldShowManagerButtons,
|
||||
openManager
|
||||
}
|
||||
}
|
||||
@@ -187,6 +187,8 @@
|
||||
"updateSelected": "Update Selected",
|
||||
"updateAll": "Update All",
|
||||
"updatingAllPacks": "Updating all packages",
|
||||
"disabledNodesWontUpdate": "Disabled nodes will not be updated",
|
||||
"enablePackToChangeVersion": "Enable this pack to change versions",
|
||||
"license": "License",
|
||||
"nightlyVersion": "Nightly",
|
||||
"latestVersion": "Latest",
|
||||
@@ -205,6 +207,7 @@
|
||||
"noDescription": "No description available",
|
||||
"installSelected": "Install Selected",
|
||||
"installAllMissingNodes": "Install All Missing Nodes",
|
||||
"allMissingNodesInstalled": "All missing nodes have been successfully installed",
|
||||
"packsSelected": "packs selected",
|
||||
"mixedSelectionMessage": "Cannot perform bulk action on mixed selection",
|
||||
"notAvailable": "Not Available",
|
||||
@@ -1436,6 +1439,8 @@
|
||||
"missingModelsMessage": "When loading the graph, the following models were not found"
|
||||
},
|
||||
"loadWorkflowWarning": {
|
||||
"missingNodesTitle": "Some Nodes Are Missing",
|
||||
"missingNodesDescription": "When loading the graph, the following node types were not found.\nThis may also happen if your installed version is lower and that node type can’t be found.",
|
||||
"outdatedVersion": "Some nodes require a newer version of ComfyUI (current: {version}). Please update to use all nodes.",
|
||||
"outdatedVersionGeneric": "Some nodes require a newer version of ComfyUI. Please update to use all nodes.",
|
||||
"coreNodesFromVersion": "Requires ComfyUI {version}:"
|
||||
|
||||
@@ -2,6 +2,7 @@ import axios, { AxiosError, AxiosResponse } from 'axios'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { useManagerState } from '@/composables/useManagerState'
|
||||
import { api } from '@/scripts/api'
|
||||
import { components } from '@/types/generatedManagerTypes'
|
||||
import { isAbortError } from '@/utils/typeGuardUtil'
|
||||
@@ -44,11 +45,18 @@ const managerApiClient = axios.create({
|
||||
/**
|
||||
* Service for interacting with the ComfyUI Manager API
|
||||
* Provides methods for managing packs, ComfyUI-Manager queue operations, and system functions
|
||||
* Note: This service should only be used when Manager state is NEW_UI
|
||||
*/
|
||||
export const useComfyManagerService = () => {
|
||||
const isLoading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
// Check if manager service should be available
|
||||
const isManagerServiceAvailable = () => {
|
||||
const managerState = useManagerState()
|
||||
return managerState.isNewManagerUI.value
|
||||
}
|
||||
|
||||
const handleRequestError = (
|
||||
err: unknown,
|
||||
context: string,
|
||||
@@ -87,6 +95,12 @@ export const useComfyManagerService = () => {
|
||||
): Promise<T | null> => {
|
||||
const { errorContext, routeSpecificErrors, isQueueOperation } = options
|
||||
|
||||
// Block service calls if not in NEW_UI state
|
||||
if (!isManagerServiceAvailable()) {
|
||||
error.value = 'Manager service is not available in current mode'
|
||||
return null
|
||||
}
|
||||
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
@@ -151,6 +165,10 @@ export const useComfyManagerService = () => {
|
||||
) => {
|
||||
const errorContext = 'Fetching bulk import failure information'
|
||||
|
||||
if (!params.cnr_ids?.length && !params.urls?.length) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return executeRequest<components['schemas']['ImportFailInfoBulkResponse']>(
|
||||
() =>
|
||||
managerApiClient.post(ManagerRoute.IMPORT_FAIL_INFO_BULK, params, {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useEventListener, whenever } from '@vueuse/core'
|
||||
import { mapKeys } from 'es-toolkit/compat'
|
||||
import { defineStore } from 'pinia'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { ref, watch } from 'vue'
|
||||
@@ -14,6 +13,7 @@ import { useComfyManagerService } from '@/services/comfyManagerService'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { TaskLog } from '@/types/comfyManagerTypes'
|
||||
import { components } from '@/types/generatedManagerTypes'
|
||||
import { normalizePackKeys } from '@/utils/packUtils'
|
||||
|
||||
type InstallPackParams = components['schemas']['InstallPackParams']
|
||||
type InstalledPacksResponse = components['schemas']['InstalledPacksResponse']
|
||||
@@ -185,12 +185,8 @@ export const useComfyManagerStore = defineStore('comfyManager', () => {
|
||||
const refreshInstalledList = async () => {
|
||||
const packs = await managerService.listInstalledPacks()
|
||||
if (packs) {
|
||||
// The keys are 'cleaned' by stripping the version suffix.
|
||||
// The pack object itself (the value) still contains the version info.
|
||||
const packsWithCleanedKeys = mapKeys(packs, (_value, key) => {
|
||||
return key.split('@')[0]
|
||||
})
|
||||
installedPacks.value = packsWithCleanedKeys
|
||||
// Normalize pack keys to ensure consistent access
|
||||
installedPacks.value = normalizePackKeys(packs)
|
||||
}
|
||||
isStale.value = false
|
||||
}
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, readonly } from 'vue'
|
||||
|
||||
import { api } from '@/scripts/api'
|
||||
import { useExtensionStore } from '@/stores/extensionStore'
|
||||
import { useSystemStatsStore } from '@/stores/systemStatsStore'
|
||||
|
||||
export enum ManagerUIState {
|
||||
DISABLED = 'disabled',
|
||||
LEGACY_UI = 'legacy',
|
||||
NEW_UI = 'new'
|
||||
}
|
||||
|
||||
export const useManagerStateStore = defineStore('managerState', () => {
|
||||
const systemStatsStore = useSystemStatsStore()
|
||||
const extensionStore = useExtensionStore()
|
||||
|
||||
// Reactive computed manager state that updates when dependencies change
|
||||
const managerUIState = computed(() => {
|
||||
const systemStats = systemStatsStore.systemStats
|
||||
const clientSupportsV4 =
|
||||
api.getClientFeatureFlags().supports_manager_v4_ui ?? false
|
||||
const hasLegacyManager = extensionStore.extensions.some(
|
||||
(ext) => ext.name === 'Comfy.CustomNodesManager'
|
||||
)
|
||||
|
||||
const serverSupportsV4 = api.getServerFeature(
|
||||
'extension.manager.supports_v4'
|
||||
)
|
||||
|
||||
console.log('[Manager State Debug]', {
|
||||
systemStats: systemStats?.system?.argv,
|
||||
clientSupportsV4,
|
||||
serverSupportsV4,
|
||||
hasLegacyManager,
|
||||
extensions: extensionStore.extensions.map((e) => e.name)
|
||||
})
|
||||
|
||||
// Check command line args first
|
||||
if (systemStats?.system?.argv?.includes('--disable-manager')) {
|
||||
return ManagerUIState.DISABLED // comfyui_manager package not installed
|
||||
}
|
||||
|
||||
if (systemStats?.system?.argv?.includes('--enable-manager-legacy-ui')) {
|
||||
return ManagerUIState.LEGACY_UI // forced legacy
|
||||
}
|
||||
|
||||
// Both client and server support v4 = NEW_UI
|
||||
if (clientSupportsV4 && serverSupportsV4 === true) {
|
||||
return ManagerUIState.NEW_UI
|
||||
}
|
||||
|
||||
// Server supports v4 but client doesn't = LEGACY_UI
|
||||
if (serverSupportsV4 === true) {
|
||||
return ManagerUIState.LEGACY_UI
|
||||
}
|
||||
|
||||
// No server v4 support but legacy manager extension exists = LEGACY_UI
|
||||
if (hasLegacyManager) {
|
||||
return ManagerUIState.LEGACY_UI
|
||||
}
|
||||
|
||||
// If server feature flags haven't loaded yet, return DISABLED for now
|
||||
// This will update reactively once feature flags load
|
||||
if (serverSupportsV4 === undefined) {
|
||||
return ManagerUIState.DISABLED
|
||||
}
|
||||
|
||||
// No manager at all = DISABLED
|
||||
return ManagerUIState.DISABLED
|
||||
})
|
||||
|
||||
return {
|
||||
managerUIState: readonly(managerUIState)
|
||||
}
|
||||
})
|
||||
@@ -1,3 +1,4 @@
|
||||
import { until } from '@vueuse/core'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
@@ -240,7 +241,7 @@ export const useReleaseStore = defineStore('release', () => {
|
||||
try {
|
||||
// Ensure system stats are loaded
|
||||
if (!systemStatsStore.systemStats) {
|
||||
await systemStatsStore.fetchSystemStats()
|
||||
await until(systemStatsStore.isInitialized)
|
||||
}
|
||||
|
||||
const fetchedReleases = await releaseService.getReleases({
|
||||
|
||||
@@ -1,32 +1,34 @@
|
||||
import { useAsyncState } from '@vueuse/core'
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import type { SystemStats } from '@/schemas/apiSchema'
|
||||
import { api } from '@/scripts/api'
|
||||
import { isElectron } from '@/utils/envUtil'
|
||||
|
||||
export const useSystemStatsStore = defineStore('systemStats', () => {
|
||||
const systemStats = ref<SystemStats | null>(null)
|
||||
const isLoading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
async function fetchSystemStats() {
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
const fetchSystemStatsData = async () => {
|
||||
try {
|
||||
systemStats.value = await api.getSystemStats()
|
||||
return await api.getSystemStats()
|
||||
} catch (err) {
|
||||
error.value =
|
||||
err instanceof Error
|
||||
? err.message
|
||||
: 'An error occurred while fetching system stats'
|
||||
console.error('Error fetching system stats:', err)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
state: systemStats,
|
||||
isLoading,
|
||||
error,
|
||||
isReady: isInitialized,
|
||||
execute: refetchSystemStats
|
||||
} = useAsyncState<SystemStats | null>(
|
||||
fetchSystemStatsData,
|
||||
null, // initial value
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
function getFormFactor(): string {
|
||||
if (!systemStats.value?.system?.os) {
|
||||
return 'other'
|
||||
@@ -62,7 +64,8 @@ export const useSystemStatsStore = defineStore('systemStats', () => {
|
||||
systemStats,
|
||||
isLoading,
|
||||
error,
|
||||
fetchSystemStats,
|
||||
isInitialized,
|
||||
refetchSystemStats,
|
||||
getFormFactor
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { until, useStorage } from '@vueuse/core'
|
||||
import { defineStore } from 'pinia'
|
||||
import * as semver from 'semver'
|
||||
import { computed } from 'vue'
|
||||
@@ -103,7 +103,7 @@ export const useVersionCompatibilityStore = defineStore(
|
||||
|
||||
async function checkVersionCompatibility() {
|
||||
if (!systemStatsStore.systemStats) {
|
||||
await systemStatsStore.fetchSystemStats()
|
||||
await until(systemStatsStore.isInitialized)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import type { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
|
||||
import { NodeSourceType, getNodeSource } from '@/types/nodeSource'
|
||||
import { normalizePackId } from '@/utils/packUtils'
|
||||
|
||||
export function extractCustomNodeName(
|
||||
pythonModule: string | undefined
|
||||
): string | null {
|
||||
const modules = pythonModule?.split('.') || []
|
||||
if (modules.length >= 2 && modules[0] === 'custom_nodes') {
|
||||
return modules[1].split('@')[0]
|
||||
// Use normalizePackId to remove version suffix
|
||||
return normalizePackId(modules[1])
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
35
src/utils/packUtils.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { mapKeys } from 'es-toolkit/compat'
|
||||
|
||||
/**
|
||||
* Normalizes a pack ID by removing the version suffix.
|
||||
*
|
||||
* ComfyUI-Manager returns pack IDs in different formats:
|
||||
* - Enabled packs: "packname" (without version)
|
||||
* - Disabled packs: "packname@1_0_3" (with version suffix)
|
||||
* - Latest versions from registry: "packname" (without version)
|
||||
*
|
||||
* Since the pack object itself contains the version info (ver field),
|
||||
* we normalize all pack IDs to just the base name for consistent access.
|
||||
* This ensures we can always find a pack by its base name (nodePack.id)
|
||||
* regardless of its enabled/disabled state.
|
||||
*
|
||||
* @param packId - The pack ID that may contain a version suffix
|
||||
* @returns The normalized pack ID without version suffix
|
||||
*/
|
||||
export function normalizePackId(packId: string): string {
|
||||
return packId.split('@')[0]
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes all keys in a pack record by removing version suffixes.
|
||||
* This is used when receiving pack data from the server to ensure
|
||||
* consistent key format across the application.
|
||||
*
|
||||
* @param packs - Record of packs with potentially versioned keys
|
||||
* @returns Record with normalized keys
|
||||
*/
|
||||
export function normalizePackKeys<T>(
|
||||
packs: Record<string, T>
|
||||
): Record<string, T> {
|
||||
return mapKeys(packs, (_value, key) => normalizePackId(key))
|
||||
}
|
||||
@@ -33,14 +33,14 @@ const createMockNode = (type: string, version?: string): LGraphNode =>
|
||||
describe('MissingCoreNodesMessage', () => {
|
||||
const mockSystemStatsStore = {
|
||||
systemStats: null as { system?: { comfyui_version?: string } } | null,
|
||||
fetchSystemStats: vi.fn()
|
||||
refetchSystemStats: vi.fn()
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
// Reset the mock store state
|
||||
mockSystemStatsStore.systemStats = null
|
||||
mockSystemStatsStore.fetchSystemStats = vi.fn()
|
||||
mockSystemStatsStore.refetchSystemStats = vi.fn()
|
||||
// @ts-expect-error - Mocking the return value of useSystemStatsStore for testing.
|
||||
// The actual store has more properties, but we only need these for our tests.
|
||||
useSystemStatsStore.mockReturnValue(mockSystemStatsStore)
|
||||
@@ -86,15 +86,11 @@ describe('MissingCoreNodesMessage', () => {
|
||||
expect(wrapper.findComponent(Message).exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('fetches and displays current ComfyUI version', async () => {
|
||||
// Start with no systemStats to trigger fetch
|
||||
mockSystemStatsStore.fetchSystemStats.mockImplementation(() => {
|
||||
// Simulate the fetch setting the systemStats
|
||||
mockSystemStatsStore.systemStats = {
|
||||
system: { comfyui_version: '1.0.0' }
|
||||
}
|
||||
return Promise.resolve()
|
||||
})
|
||||
it('displays current ComfyUI version when available', async () => {
|
||||
// Set systemStats directly (store auto-fetches with useAsyncState)
|
||||
mockSystemStatsStore.systemStats = {
|
||||
system: { comfyui_version: '1.0.0' }
|
||||
}
|
||||
|
||||
const missingCoreNodes = {
|
||||
'1.2.0': [createMockNode('TestNode', '1.2.0')]
|
||||
@@ -102,20 +98,18 @@ describe('MissingCoreNodesMessage', () => {
|
||||
|
||||
const wrapper = mountComponent({ missingCoreNodes })
|
||||
|
||||
// Wait for all async operations
|
||||
await nextTick()
|
||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||
// Wait for component to render
|
||||
await nextTick()
|
||||
|
||||
expect(mockSystemStatsStore.fetchSystemStats).toHaveBeenCalled()
|
||||
// No need to check if fetchSystemStats was called since useAsyncState auto-fetches
|
||||
expect(wrapper.text()).toContain(
|
||||
'Some nodes require a newer version of ComfyUI (current: 1.0.0)'
|
||||
)
|
||||
})
|
||||
|
||||
it('displays generic message when version is unavailable', async () => {
|
||||
// Mock fetchSystemStats to resolve without setting systemStats
|
||||
mockSystemStatsStore.fetchSystemStats.mockResolvedValue(undefined)
|
||||
// No systemStats set - version unavailable
|
||||
mockSystemStatsStore.systemStats = null
|
||||
|
||||
const missingCoreNodes = {
|
||||
'1.2.0': [createMockNode('TestNode', '1.2.0')]
|
||||
|
||||
@@ -505,7 +505,7 @@ describe('useNodePricing', () => {
|
||||
})
|
||||
|
||||
describe('dynamic pricing - Veo3VideoGenerationNode', () => {
|
||||
it('should return $2.00 for veo-3.0-fast-generate-001 without audio', () => {
|
||||
it('should return $0.80 for veo-3.0-fast-generate-001 without audio', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('Veo3VideoGenerationNode', [
|
||||
{ name: 'model', value: 'veo-3.0-fast-generate-001' },
|
||||
@@ -513,49 +513,49 @@ describe('useNodePricing', () => {
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$2.00/Run')
|
||||
expect(price).toBe('$0.80/Run')
|
||||
})
|
||||
|
||||
it('should return $3.20 for veo-3.0-fast-generate-001 with audio', () => {
|
||||
it('should return $1.20 for veo-3.0-fast-generate-001 with audio', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('Veo3VideoGenerationNode', [
|
||||
{ name: 'model', value: 'veo-3.0-fast-generate-001' },
|
||||
{ name: 'generate_audio', value: true }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$1.20/Run')
|
||||
})
|
||||
|
||||
it('should return $1.60 for veo-3.0-generate-001 without audio', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('Veo3VideoGenerationNode', [
|
||||
{ name: 'model', value: 'veo-3.0-generate-001' },
|
||||
{ name: 'generate_audio', value: false }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$1.60/Run')
|
||||
})
|
||||
|
||||
it('should return $3.20 for veo-3.0-generate-001 with audio', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('Veo3VideoGenerationNode', [
|
||||
{ name: 'model', value: 'veo-3.0-generate-001' },
|
||||
{ name: 'generate_audio', value: true }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$3.20/Run')
|
||||
})
|
||||
|
||||
it('should return $4.00 for veo-3.0-generate-001 without audio', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('Veo3VideoGenerationNode', [
|
||||
{ name: 'model', value: 'veo-3.0-generate-001' },
|
||||
{ name: 'generate_audio', value: false }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$4.00/Run')
|
||||
})
|
||||
|
||||
it('should return $6.00 for veo-3.0-generate-001 with audio', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('Veo3VideoGenerationNode', [
|
||||
{ name: 'model', value: 'veo-3.0-generate-001' },
|
||||
{ name: 'generate_audio', value: true }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$6.00/Run')
|
||||
})
|
||||
|
||||
it('should return range when widgets are missing', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('Veo3VideoGenerationNode', [])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe(
|
||||
'$2.00-6.00/Run (varies with model & audio generation)'
|
||||
'$0.80-3.20/Run (varies with model & audio generation)'
|
||||
)
|
||||
})
|
||||
|
||||
@@ -567,7 +567,7 @@ describe('useNodePricing', () => {
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe(
|
||||
'$2.00-6.00/Run (varies with model & audio generation)'
|
||||
'$0.80-3.20/Run (varies with model & audio generation)'
|
||||
)
|
||||
})
|
||||
|
||||
@@ -579,7 +579,7 @@ describe('useNodePricing', () => {
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe(
|
||||
'$2.00-6.00/Run (varies with model & audio generation)'
|
||||
'$0.80-3.20/Run (varies with model & audio generation)'
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1780,4 +1780,118 @@ describe('useNodePricing', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('dynamic pricing - ByteDanceSeedreamNode', () => {
|
||||
it('should return fallback when widgets are missing', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('ByteDanceSeedreamNode', [])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.03/Run ($0.03 for one output image)')
|
||||
})
|
||||
|
||||
it('should return $0.03/Run when sequential generation is disabled', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('ByteDanceSeedreamNode', [
|
||||
{ name: 'sequential_image_generation', value: 'disabled' },
|
||||
{ name: 'max_images', value: 5 }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.03/Run')
|
||||
})
|
||||
|
||||
it('should multiply by max_images when sequential generation is enabled', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('ByteDanceSeedreamNode', [
|
||||
{ name: 'sequential_image_generation', value: 'enabled' },
|
||||
{ name: 'max_images', value: 4 }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.12/Run ($0.03 for one output image)')
|
||||
})
|
||||
})
|
||||
|
||||
describe('dynamic pricing - ByteDance Seedance video nodes', () => {
|
||||
it('should return base 10s range for PRO 1080p on ByteDanceTextToVideoNode', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('ByteDanceTextToVideoNode', [
|
||||
{ name: 'model', value: 'seedance-1-0-pro' },
|
||||
{ name: 'duration', value: '10' },
|
||||
{ name: 'resolution', value: '1080p' }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$1.18-$1.22/Run')
|
||||
})
|
||||
|
||||
it('should scale to half for 5s PRO 1080p on ByteDanceTextToVideoNode', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('ByteDanceTextToVideoNode', [
|
||||
{ name: 'model', value: 'seedance-1-0-pro' },
|
||||
{ name: 'duration', value: '5' },
|
||||
{ name: 'resolution', value: '1080p' }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.59-$0.61/Run')
|
||||
})
|
||||
|
||||
it('should scale for 8s PRO 480p on ByteDanceImageToVideoNode', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('ByteDanceImageToVideoNode', [
|
||||
{ name: 'model', value: 'seedance-1-0-pro' },
|
||||
{ name: 'duration', value: '8' },
|
||||
{ name: 'resolution', value: '480p' }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.18-$0.19/Run')
|
||||
})
|
||||
|
||||
it('should scale correctly for 12s PRO 720p on ByteDanceFirstLastFrameNode', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('ByteDanceFirstLastFrameNode', [
|
||||
{ name: 'model', value: 'seedance-1-0-pro' },
|
||||
{ name: 'duration', value: '12' },
|
||||
{ name: 'resolution', value: '720p' }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.61-$0.67/Run')
|
||||
})
|
||||
|
||||
it('should collapse to a single value when min and max round equal for LITE 480p 3s on ByteDanceImageReferenceNode', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const node = createMockNode('ByteDanceImageReferenceNode', [
|
||||
{ name: 'model', value: 'seedance-1-0-lite' },
|
||||
{ name: 'duration', value: '3' },
|
||||
{ name: 'resolution', value: '480p' }
|
||||
])
|
||||
|
||||
const price = getNodeDisplayPrice(node)
|
||||
expect(price).toBe('$0.05/Run') // 0.17..0.18 scaled by 0.3 both round to 0.05
|
||||
})
|
||||
|
||||
it('should return Token-based when required widgets are missing', () => {
|
||||
const { getNodeDisplayPrice } = useNodePricing()
|
||||
const missingModel = createMockNode('ByteDanceFirstLastFrameNode', [
|
||||
{ name: 'duration', value: '10' },
|
||||
{ name: 'resolution', value: '1080p' }
|
||||
])
|
||||
const missingResolution = createMockNode('ByteDanceImageToVideoNode', [
|
||||
{ name: 'model', value: 'seedance-1-0-pro' },
|
||||
{ name: 'duration', value: '10' }
|
||||
])
|
||||
const missingDuration = createMockNode('ByteDanceTextToVideoNode', [
|
||||
{ name: 'model', value: 'seedance-1-0-lite' },
|
||||
{ name: 'resolution', value: '720p' }
|
||||
])
|
||||
|
||||
expect(getNodeDisplayPrice(missingModel)).toBe('Token-based')
|
||||
expect(getNodeDisplayPrice(missingResolution)).toBe('Token-based')
|
||||
expect(getNodeDisplayPrice(missingDuration)).toBe('Token-based')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -96,7 +96,7 @@ describe.skip('useConflictDetection with Registry Store', () => {
|
||||
}
|
||||
|
||||
const mockSystemStatsStore = {
|
||||
fetchSystemStats: vi.fn(),
|
||||
refetchSystemStats: vi.fn(),
|
||||
systemStats: {
|
||||
system: {
|
||||
comfyui_version: '0.3.41',
|
||||
@@ -133,7 +133,7 @@ describe.skip('useConflictDetection with Registry Store', () => {
|
||||
} as any
|
||||
|
||||
// Reset mock functions
|
||||
mockSystemStatsStore.fetchSystemStats.mockResolvedValue(undefined)
|
||||
mockSystemStatsStore.refetchSystemStats.mockResolvedValue(undefined)
|
||||
mockComfyManagerService.listInstalledPacks.mockReset()
|
||||
mockComfyManagerService.getImportFailInfo.mockReset()
|
||||
mockRegistryService.getPackByVersion.mockReset()
|
||||
@@ -185,7 +185,7 @@ describe.skip('useConflictDetection with Registry Store', () => {
|
||||
|
||||
it('should return fallback environment information when systemStatsStore fails', async () => {
|
||||
// Mock systemStatsStore failure
|
||||
mockSystemStatsStore.fetchSystemStats.mockRejectedValue(
|
||||
mockSystemStatsStore.refetchSystemStats.mockRejectedValue(
|
||||
new Error('Store failure')
|
||||
)
|
||||
mockSystemStatsStore.systemStats = null
|
||||
@@ -754,7 +754,7 @@ describe.skip('useConflictDetection with Registry Store', () => {
|
||||
describe('error resilience with Registry Store', () => {
|
||||
it('should continue execution even when system environment detection fails', async () => {
|
||||
// Mock system stats store failure
|
||||
mockSystemStatsStore.fetchSystemStats.mockRejectedValue(
|
||||
mockSystemStatsStore.refetchSystemStats.mockRejectedValue(
|
||||
new Error('Store error')
|
||||
)
|
||||
mockSystemStatsStore.systemStats = null
|
||||
@@ -851,7 +851,7 @@ describe.skip('useConflictDetection with Registry Store', () => {
|
||||
|
||||
it('should handle complete system failure gracefully', async () => {
|
||||
// Mock all stores/services failing
|
||||
mockSystemStatsStore.fetchSystemStats.mockRejectedValue(
|
||||
mockSystemStatsStore.refetchSystemStats.mockRejectedValue(
|
||||
new Error('Critical error')
|
||||
)
|
||||
mockSystemStatsStore.systemStats = null
|
||||
|
||||
320
tests-ui/tests/composables/useManagerState.test.ts
Normal file
@@ -0,0 +1,320 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { useFeatureFlags } from '@/composables/useFeatureFlags'
|
||||
import { ManagerUIState, useManagerState } from '@/composables/useManagerState'
|
||||
import { api } from '@/scripts/api'
|
||||
import { useExtensionStore } from '@/stores/extensionStore'
|
||||
import { useSystemStatsStore } from '@/stores/systemStatsStore'
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock('@/scripts/api', () => ({
|
||||
api: {
|
||||
getClientFeatureFlags: vi.fn(),
|
||||
getServerFeature: vi.fn()
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/composables/useFeatureFlags', () => ({
|
||||
useFeatureFlags: vi.fn(() => ({
|
||||
flags: { supportsManagerV4: false },
|
||||
featureFlag: vi.fn()
|
||||
}))
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/extensionStore', () => ({
|
||||
useExtensionStore: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/systemStatsStore', () => ({
|
||||
useSystemStatsStore: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('@/services/dialogService', () => ({
|
||||
useDialogService: vi.fn(() => ({
|
||||
showManagerPopup: vi.fn(),
|
||||
showLegacyManagerPopup: vi.fn(),
|
||||
showSettingsDialog: vi.fn()
|
||||
}))
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/commandStore', () => ({
|
||||
useCommandStore: vi.fn(() => ({
|
||||
execute: vi.fn()
|
||||
}))
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/toastStore', () => ({
|
||||
useToastStore: vi.fn(() => ({
|
||||
add: vi.fn()
|
||||
}))
|
||||
}))
|
||||
|
||||
describe('useManagerState', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('managerUIState property', () => {
|
||||
it('should return DISABLED state when --disable-manager is present', () => {
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
||||
systemStats: ref({
|
||||
system: { argv: ['python', 'main.py', '--disable-manager'] }
|
||||
}),
|
||||
isInitialized: ref(true)
|
||||
} as any)
|
||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
|
||||
vi.mocked(useExtensionStore).mockReturnValue({
|
||||
extensions: []
|
||||
} as any)
|
||||
|
||||
const managerState = useManagerState()
|
||||
|
||||
expect(managerState.managerUIState.value).toBe(ManagerUIState.DISABLED)
|
||||
})
|
||||
|
||||
it('should return LEGACY_UI state when --enable-manager-legacy-ui is present', () => {
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
||||
systemStats: ref({
|
||||
system: { argv: ['python', 'main.py', '--enable-manager-legacy-ui'] }
|
||||
}),
|
||||
isInitialized: ref(true)
|
||||
} as any)
|
||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
|
||||
vi.mocked(useExtensionStore).mockReturnValue({
|
||||
extensions: []
|
||||
} as any)
|
||||
|
||||
const managerState = useManagerState()
|
||||
|
||||
expect(managerState.managerUIState.value).toBe(ManagerUIState.LEGACY_UI)
|
||||
})
|
||||
|
||||
it('should return NEW_UI state when client and server both support v4', () => {
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
||||
systemStats: ref({ system: { argv: ['python', 'main.py'] } }),
|
||||
isInitialized: ref(true)
|
||||
} as any)
|
||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({
|
||||
supports_manager_v4_ui: true
|
||||
})
|
||||
vi.mocked(api.getServerFeature).mockReturnValue(true)
|
||||
vi.mocked(useFeatureFlags).mockReturnValue({
|
||||
flags: { supportsManagerV4: true },
|
||||
featureFlag: vi.fn()
|
||||
} as any)
|
||||
vi.mocked(useExtensionStore).mockReturnValue({
|
||||
extensions: []
|
||||
} as any)
|
||||
|
||||
const managerState = useManagerState()
|
||||
|
||||
expect(managerState.managerUIState.value).toBe(ManagerUIState.NEW_UI)
|
||||
})
|
||||
|
||||
it('should return LEGACY_UI state when server supports v4 but client does not', () => {
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
||||
systemStats: ref({ system: { argv: ['python', 'main.py'] } }),
|
||||
isInitialized: ref(true)
|
||||
} as any)
|
||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({
|
||||
supports_manager_v4_ui: false
|
||||
})
|
||||
vi.mocked(api.getServerFeature).mockReturnValue(true)
|
||||
vi.mocked(useFeatureFlags).mockReturnValue({
|
||||
flags: { supportsManagerV4: true },
|
||||
featureFlag: vi.fn()
|
||||
} as any)
|
||||
vi.mocked(useExtensionStore).mockReturnValue({
|
||||
extensions: []
|
||||
} as any)
|
||||
|
||||
const managerState = useManagerState()
|
||||
|
||||
expect(managerState.managerUIState.value).toBe(ManagerUIState.LEGACY_UI)
|
||||
})
|
||||
|
||||
it('should return LEGACY_UI state when legacy manager extension exists', () => {
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
||||
systemStats: ref({ system: { argv: ['python', 'main.py'] } }),
|
||||
isInitialized: ref(true)
|
||||
} as any)
|
||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
|
||||
vi.mocked(useFeatureFlags).mockReturnValue({
|
||||
flags: { supportsManagerV4: false },
|
||||
featureFlag: vi.fn()
|
||||
} as any)
|
||||
vi.mocked(useExtensionStore).mockReturnValue({
|
||||
extensions: [{ name: 'Comfy.CustomNodesManager' }]
|
||||
} as any)
|
||||
|
||||
const managerState = useManagerState()
|
||||
|
||||
expect(managerState.managerUIState.value).toBe(ManagerUIState.LEGACY_UI)
|
||||
})
|
||||
|
||||
it('should return NEW_UI state when server feature flags are undefined', () => {
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
||||
systemStats: ref({ system: { argv: ['python', 'main.py'] } }),
|
||||
isInitialized: ref(true)
|
||||
} as any)
|
||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
|
||||
vi.mocked(api.getServerFeature).mockReturnValue(undefined)
|
||||
vi.mocked(useFeatureFlags).mockReturnValue({
|
||||
flags: { supportsManagerV4: undefined },
|
||||
featureFlag: vi.fn()
|
||||
} as any)
|
||||
vi.mocked(useExtensionStore).mockReturnValue({
|
||||
extensions: []
|
||||
} as any)
|
||||
|
||||
const managerState = useManagerState()
|
||||
|
||||
expect(managerState.managerUIState.value).toBe(ManagerUIState.NEW_UI)
|
||||
})
|
||||
|
||||
it('should return LEGACY_UI state when server does not support v4', () => {
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
||||
systemStats: ref({ system: { argv: ['python', 'main.py'] } }),
|
||||
isInitialized: ref(true)
|
||||
} as any)
|
||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
|
||||
vi.mocked(api.getServerFeature).mockReturnValue(false)
|
||||
vi.mocked(useFeatureFlags).mockReturnValue({
|
||||
flags: { supportsManagerV4: false },
|
||||
featureFlag: vi.fn()
|
||||
} as any)
|
||||
vi.mocked(useExtensionStore).mockReturnValue({
|
||||
extensions: []
|
||||
} as any)
|
||||
|
||||
const managerState = useManagerState()
|
||||
|
||||
expect(managerState.managerUIState.value).toBe(ManagerUIState.LEGACY_UI)
|
||||
})
|
||||
|
||||
it('should handle null systemStats gracefully', () => {
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
||||
systemStats: ref(null),
|
||||
isInitialized: ref(true)
|
||||
} as any)
|
||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({
|
||||
supports_manager_v4_ui: true
|
||||
})
|
||||
vi.mocked(api.getServerFeature).mockReturnValue(true)
|
||||
vi.mocked(useFeatureFlags).mockReturnValue({
|
||||
flags: { supportsManagerV4: true },
|
||||
featureFlag: vi.fn()
|
||||
} as any)
|
||||
vi.mocked(useExtensionStore).mockReturnValue({
|
||||
extensions: []
|
||||
} as any)
|
||||
|
||||
const managerState = useManagerState()
|
||||
|
||||
expect(managerState.managerUIState.value).toBe(ManagerUIState.NEW_UI)
|
||||
})
|
||||
})
|
||||
|
||||
describe('helper properties', () => {
|
||||
it('isManagerEnabled should return true when state is not DISABLED', () => {
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
||||
systemStats: ref({ system: { argv: ['python', 'main.py'] } }),
|
||||
isInitialized: ref(true)
|
||||
} as any)
|
||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({
|
||||
supports_manager_v4_ui: true
|
||||
})
|
||||
vi.mocked(api.getServerFeature).mockReturnValue(true)
|
||||
vi.mocked(useExtensionStore).mockReturnValue({
|
||||
extensions: []
|
||||
} as any)
|
||||
|
||||
const managerState = useManagerState()
|
||||
expect(managerState.isManagerEnabled.value).toBe(true)
|
||||
})
|
||||
|
||||
it('isManagerEnabled should return false when state is DISABLED', () => {
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
||||
systemStats: ref({
|
||||
system: { argv: ['python', 'main.py', '--disable-manager'] }
|
||||
}),
|
||||
isInitialized: ref(true)
|
||||
} as any)
|
||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
|
||||
vi.mocked(useExtensionStore).mockReturnValue({
|
||||
extensions: []
|
||||
} as any)
|
||||
|
||||
const managerState = useManagerState()
|
||||
expect(managerState.isManagerEnabled.value).toBe(false)
|
||||
})
|
||||
|
||||
it('isNewManagerUI should return true when state is NEW_UI', () => {
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
||||
systemStats: ref({ system: { argv: ['python', 'main.py'] } }),
|
||||
isInitialized: ref(true)
|
||||
} as any)
|
||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({
|
||||
supports_manager_v4_ui: true
|
||||
})
|
||||
vi.mocked(api.getServerFeature).mockReturnValue(true)
|
||||
vi.mocked(useExtensionStore).mockReturnValue({
|
||||
extensions: []
|
||||
} as any)
|
||||
|
||||
const managerState = useManagerState()
|
||||
expect(managerState.isNewManagerUI.value).toBe(true)
|
||||
})
|
||||
|
||||
it('isLegacyManagerUI should return true when state is LEGACY_UI', () => {
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
||||
systemStats: ref({
|
||||
system: { argv: ['python', 'main.py', '--enable-manager-legacy-ui'] }
|
||||
}),
|
||||
isInitialized: ref(true)
|
||||
} as any)
|
||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
|
||||
vi.mocked(useExtensionStore).mockReturnValue({
|
||||
extensions: []
|
||||
} as any)
|
||||
|
||||
const managerState = useManagerState()
|
||||
expect(managerState.isLegacyManagerUI.value).toBe(true)
|
||||
})
|
||||
|
||||
it('shouldShowInstallButton should return true only for NEW_UI', () => {
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
||||
systemStats: ref({ system: { argv: ['python', 'main.py'] } }),
|
||||
isInitialized: ref(true)
|
||||
} as any)
|
||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({
|
||||
supports_manager_v4_ui: true
|
||||
})
|
||||
vi.mocked(api.getServerFeature).mockReturnValue(true)
|
||||
vi.mocked(useExtensionStore).mockReturnValue({
|
||||
extensions: []
|
||||
} as any)
|
||||
|
||||
const managerState = useManagerState()
|
||||
expect(managerState.shouldShowInstallButton.value).toBe(true)
|
||||
})
|
||||
|
||||
it('shouldShowManagerButtons should return true when not DISABLED', () => {
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
||||
systemStats: ref({ system: { argv: ['python', 'main.py'] } }),
|
||||
isInitialized: ref(true)
|
||||
} as any)
|
||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({
|
||||
supports_manager_v4_ui: true
|
||||
})
|
||||
vi.mocked(api.getServerFeature).mockReturnValue(true)
|
||||
vi.mocked(useExtensionStore).mockReturnValue({
|
||||
extensions: []
|
||||
} as any)
|
||||
|
||||
const managerState = useManagerState()
|
||||
expect(managerState.shouldShowManagerButtons.value).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -63,12 +63,14 @@ describe('useUpdateAvailableNodes', () => {
|
||||
const mockStartFetchInstalled = vi.fn()
|
||||
const mockIsPackInstalled = vi.fn()
|
||||
const mockGetInstalledPackVersion = vi.fn()
|
||||
const mockIsPackEnabled = vi.fn()
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
|
||||
// Default setup
|
||||
mockIsPackInstalled.mockReturnValue(true)
|
||||
mockIsPackEnabled.mockReturnValue(true) // Default: all packs are enabled
|
||||
mockGetInstalledPackVersion.mockImplementation((id: string) => {
|
||||
switch (id) {
|
||||
case 'pack-1':
|
||||
@@ -100,7 +102,8 @@ describe('useUpdateAvailableNodes', () => {
|
||||
|
||||
mockUseComfyManagerStore.mockReturnValue({
|
||||
isPackInstalled: mockIsPackInstalled,
|
||||
getInstalledPackVersion: mockGetInstalledPackVersion
|
||||
getInstalledPackVersion: mockGetInstalledPackVersion,
|
||||
isPackEnabled: mockIsPackEnabled
|
||||
} as any)
|
||||
|
||||
mockUseInstalledPacks.mockReturnValue({
|
||||
@@ -357,4 +360,127 @@ describe('useUpdateAvailableNodes', () => {
|
||||
expect(mockIsPackInstalled).toHaveBeenCalledWith('pack-4')
|
||||
})
|
||||
})
|
||||
|
||||
describe('enabledUpdateAvailableNodePacks', () => {
|
||||
it('returns only enabled packs with updates', () => {
|
||||
mockIsPackEnabled.mockImplementation((id: string) => {
|
||||
// pack-1 is disabled
|
||||
return id !== 'pack-1'
|
||||
})
|
||||
|
||||
mockUseInstalledPacks.mockReturnValue({
|
||||
installedPacks: ref([mockInstalledPacks[0], mockInstalledPacks[1]]),
|
||||
isLoading: ref(false),
|
||||
error: ref(null),
|
||||
startFetchInstalled: mockStartFetchInstalled
|
||||
} as any)
|
||||
|
||||
const { updateAvailableNodePacks, enabledUpdateAvailableNodePacks } =
|
||||
useUpdateAvailableNodes()
|
||||
|
||||
// pack-1 has updates but is disabled
|
||||
expect(updateAvailableNodePacks.value).toHaveLength(1)
|
||||
expect(updateAvailableNodePacks.value[0].id).toBe('pack-1')
|
||||
|
||||
// enabledUpdateAvailableNodePacks should be empty
|
||||
expect(enabledUpdateAvailableNodePacks.value).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('returns all packs when all are enabled', () => {
|
||||
mockUseInstalledPacks.mockReturnValue({
|
||||
installedPacks: ref([mockInstalledPacks[0]]), // pack-1: outdated
|
||||
isLoading: ref(false),
|
||||
error: ref(null),
|
||||
startFetchInstalled: mockStartFetchInstalled
|
||||
} as any)
|
||||
|
||||
const { updateAvailableNodePacks, enabledUpdateAvailableNodePacks } =
|
||||
useUpdateAvailableNodes()
|
||||
|
||||
expect(updateAvailableNodePacks.value).toHaveLength(1)
|
||||
expect(enabledUpdateAvailableNodePacks.value).toHaveLength(1)
|
||||
expect(enabledUpdateAvailableNodePacks.value[0].id).toBe('pack-1')
|
||||
})
|
||||
})
|
||||
|
||||
describe('hasDisabledUpdatePacks', () => {
|
||||
it('returns true when there are disabled packs with updates', () => {
|
||||
mockIsPackEnabled.mockImplementation((id: string) => {
|
||||
// pack-1 is disabled
|
||||
return id !== 'pack-1'
|
||||
})
|
||||
|
||||
mockUseInstalledPacks.mockReturnValue({
|
||||
installedPacks: ref([mockInstalledPacks[0]]), // pack-1: outdated
|
||||
isLoading: ref(false),
|
||||
error: ref(null),
|
||||
startFetchInstalled: mockStartFetchInstalled
|
||||
} as any)
|
||||
|
||||
const { hasDisabledUpdatePacks } = useUpdateAvailableNodes()
|
||||
|
||||
expect(hasDisabledUpdatePacks.value).toBe(true)
|
||||
})
|
||||
|
||||
it('returns false when all packs with updates are enabled', () => {
|
||||
mockUseInstalledPacks.mockReturnValue({
|
||||
installedPacks: ref([mockInstalledPacks[0]]), // pack-1: outdated
|
||||
isLoading: ref(false),
|
||||
error: ref(null),
|
||||
startFetchInstalled: mockStartFetchInstalled
|
||||
} as any)
|
||||
|
||||
const { hasDisabledUpdatePacks } = useUpdateAvailableNodes()
|
||||
|
||||
expect(hasDisabledUpdatePacks.value).toBe(false)
|
||||
})
|
||||
|
||||
it('returns false when no packs have updates', () => {
|
||||
mockUseInstalledPacks.mockReturnValue({
|
||||
installedPacks: ref([mockInstalledPacks[1]]), // pack-2: up to date
|
||||
isLoading: ref(false),
|
||||
error: ref(null),
|
||||
startFetchInstalled: mockStartFetchInstalled
|
||||
} as any)
|
||||
|
||||
const { hasDisabledUpdatePacks } = useUpdateAvailableNodes()
|
||||
|
||||
expect(hasDisabledUpdatePacks.value).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('hasUpdateAvailable with disabled packs', () => {
|
||||
it('returns false when only disabled packs have updates', () => {
|
||||
mockIsPackEnabled.mockReturnValue(false) // All packs disabled
|
||||
|
||||
mockUseInstalledPacks.mockReturnValue({
|
||||
installedPacks: ref([mockInstalledPacks[0]]), // pack-1: outdated
|
||||
isLoading: ref(false),
|
||||
error: ref(null),
|
||||
startFetchInstalled: mockStartFetchInstalled
|
||||
} as any)
|
||||
|
||||
const { hasUpdateAvailable } = useUpdateAvailableNodes()
|
||||
|
||||
expect(hasUpdateAvailable.value).toBe(false)
|
||||
})
|
||||
|
||||
it('returns true when at least one enabled pack has updates', () => {
|
||||
mockIsPackEnabled.mockImplementation((id: string) => {
|
||||
// Only pack-1 is enabled
|
||||
return id === 'pack-1'
|
||||
})
|
||||
|
||||
mockUseInstalledPacks.mockReturnValue({
|
||||
installedPacks: ref([mockInstalledPacks[0]]), // pack-1: outdated
|
||||
isLoading: ref(false),
|
||||
error: ref(null),
|
||||
startFetchInstalled: mockStartFetchInstalled
|
||||
} as any)
|
||||
|
||||
const { hasUpdateAvailable } = useUpdateAvailableNodes()
|
||||
|
||||
expect(hasUpdateAvailable.value).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -161,5 +161,62 @@ describe('useManagerQueue', () => {
|
||||
expect(taskHistory.value).toHaveProperty('task1')
|
||||
expect(taskHistory.value).not.toHaveProperty('task2')
|
||||
})
|
||||
|
||||
it('normalizes pack IDs when updating installed packs', () => {
|
||||
const queue = createManagerQueue()
|
||||
|
||||
const mockState = {
|
||||
history: {},
|
||||
running_queue: [],
|
||||
pending_queue: [],
|
||||
installed_packs: {
|
||||
'ComfyUI-GGUF@1_1_4': {
|
||||
enabled: false,
|
||||
cnr_id: 'ComfyUI-GGUF',
|
||||
ver: '1.1.4'
|
||||
},
|
||||
'test-pack': {
|
||||
enabled: true,
|
||||
cnr_id: 'test-pack',
|
||||
ver: '2.0.0'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
queue.updateTaskState(mockState)
|
||||
|
||||
// Packs should be accessible by normalized keys
|
||||
expect(installedPacks.value['ComfyUI-GGUF']).toEqual({
|
||||
enabled: false,
|
||||
cnr_id: 'ComfyUI-GGUF',
|
||||
ver: '1.1.4'
|
||||
})
|
||||
expect(installedPacks.value['test-pack']).toEqual({
|
||||
enabled: true,
|
||||
cnr_id: 'test-pack',
|
||||
ver: '2.0.0'
|
||||
})
|
||||
|
||||
// Version suffixed keys should not exist after normalization
|
||||
// The pack should be accessible by its base name only (without @version)
|
||||
expect(installedPacks.value['ComfyUI-GGUF@1_1_4']).toBeUndefined()
|
||||
})
|
||||
|
||||
it('handles empty installed_packs gracefully', () => {
|
||||
const queue = createManagerQueue()
|
||||
|
||||
const mockState: any = {
|
||||
history: {},
|
||||
running_queue: [],
|
||||
pending_queue: [],
|
||||
installed_packs: undefined
|
||||
}
|
||||
|
||||
// Just call the function - if it throws, the test will fail automatically
|
||||
queue.updateTaskState(mockState)
|
||||
|
||||
// installedPacks should remain unchanged
|
||||
expect(installedPacks.value).toEqual({})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -439,4 +439,97 @@ describe('useComfyManagerStore', () => {
|
||||
expect(store.isPackInstalling('pack-3')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('refreshInstalledList with pack ID normalization', () => {
|
||||
it('normalizes pack IDs by removing version suffixes', async () => {
|
||||
const mockPacks = {
|
||||
'ComfyUI-GGUF@1_1_4': {
|
||||
enabled: false,
|
||||
cnr_id: 'ComfyUI-GGUF',
|
||||
ver: '1.1.4',
|
||||
aux_id: undefined
|
||||
},
|
||||
'ComfyUI-Manager': {
|
||||
enabled: true,
|
||||
cnr_id: 'ComfyUI-Manager',
|
||||
ver: '2.0.0',
|
||||
aux_id: undefined
|
||||
}
|
||||
}
|
||||
|
||||
vi.mocked(mockManagerService.listInstalledPacks).mockResolvedValue(
|
||||
mockPacks
|
||||
)
|
||||
|
||||
const store = useComfyManagerStore()
|
||||
await store.refreshInstalledList()
|
||||
|
||||
// Both packs should be accessible by their base name
|
||||
expect(store.installedPacks['ComfyUI-GGUF']).toEqual({
|
||||
enabled: false,
|
||||
cnr_id: 'ComfyUI-GGUF',
|
||||
ver: '1.1.4',
|
||||
aux_id: undefined
|
||||
})
|
||||
expect(store.installedPacks['ComfyUI-Manager']).toEqual({
|
||||
enabled: true,
|
||||
cnr_id: 'ComfyUI-Manager',
|
||||
ver: '2.0.0',
|
||||
aux_id: undefined
|
||||
})
|
||||
|
||||
// Version suffixed keys should not exist
|
||||
expect(store.installedPacks['ComfyUI-GGUF@1_1_4']).toBeUndefined()
|
||||
})
|
||||
|
||||
it('handles duplicate keys after normalization', async () => {
|
||||
const mockPacks = {
|
||||
'test-pack': {
|
||||
enabled: true,
|
||||
cnr_id: 'test-pack',
|
||||
ver: '1.0.0',
|
||||
aux_id: undefined
|
||||
},
|
||||
'test-pack@1_1_0': {
|
||||
enabled: false,
|
||||
cnr_id: 'test-pack',
|
||||
ver: '1.1.0',
|
||||
aux_id: undefined
|
||||
}
|
||||
}
|
||||
|
||||
vi.mocked(mockManagerService.listInstalledPacks).mockResolvedValue(
|
||||
mockPacks
|
||||
)
|
||||
|
||||
const store = useComfyManagerStore()
|
||||
await store.refreshInstalledList()
|
||||
|
||||
// The normalized key should exist (last one wins with mapKeys)
|
||||
expect(store.installedPacks['test-pack']).toBeDefined()
|
||||
expect(store.installedPacks['test-pack'].ver).toBe('1.1.0')
|
||||
})
|
||||
|
||||
it('preserves version information for disabled packs', async () => {
|
||||
const mockPacks = {
|
||||
'disabled-pack@2_0_0': {
|
||||
enabled: false,
|
||||
cnr_id: 'disabled-pack',
|
||||
ver: '2.0.0',
|
||||
aux_id: undefined
|
||||
}
|
||||
}
|
||||
|
||||
vi.mocked(mockManagerService.listInstalledPacks).mockResolvedValue(
|
||||
mockPacks
|
||||
)
|
||||
|
||||
const store = useComfyManagerStore()
|
||||
await store.refreshInstalledList()
|
||||
|
||||
// Pack should be accessible by base name with version preserved
|
||||
expect(store.getInstalledPackVersion('disabled-pack')).toBe('2.0.0')
|
||||
expect(store.isPackInstalled('disabled-pack')).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -9,6 +9,10 @@ vi.mock('@/utils/envUtil')
|
||||
vi.mock('@/services/releaseService')
|
||||
vi.mock('@/stores/settingStore')
|
||||
vi.mock('@/stores/systemStatsStore')
|
||||
vi.mock('@vueuse/core', () => ({
|
||||
until: vi.fn(() => Promise.resolve()),
|
||||
useStorage: vi.fn(() => ({ value: {} }))
|
||||
}))
|
||||
|
||||
describe('useReleaseStore', () => {
|
||||
let store: ReturnType<typeof useReleaseStore>
|
||||
@@ -49,7 +53,8 @@ describe('useReleaseStore', () => {
|
||||
comfyui_version: '1.0.0'
|
||||
}
|
||||
},
|
||||
fetchSystemStats: vi.fn(),
|
||||
isInitialized: true,
|
||||
refetchSystemStats: vi.fn(),
|
||||
getFormFactor: vi.fn(() => 'git-windows')
|
||||
}
|
||||
|
||||
@@ -334,12 +339,15 @@ describe('useReleaseStore', () => {
|
||||
})
|
||||
|
||||
it('should fetch system stats if not available', async () => {
|
||||
const { until } = await import('@vueuse/core')
|
||||
mockSystemStatsStore.systemStats = null
|
||||
mockSystemStatsStore.isInitialized = false
|
||||
mockReleaseService.getReleases.mockResolvedValue([mockRelease])
|
||||
|
||||
await store.initialize()
|
||||
|
||||
expect(mockSystemStatsStore.fetchSystemStats).toHaveBeenCalled()
|
||||
expect(until).toHaveBeenCalled()
|
||||
expect(mockReleaseService.getReleases).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not set loading state when notifications disabled', async () => {
|
||||
@@ -401,12 +409,14 @@ describe('useReleaseStore', () => {
|
||||
})
|
||||
|
||||
it('should proceed with fetchReleases when system stats are not available', async () => {
|
||||
const { until } = await import('@vueuse/core')
|
||||
mockSystemStatsStore.systemStats = null
|
||||
mockSystemStatsStore.isInitialized = false
|
||||
mockReleaseService.getReleases.mockResolvedValue([mockRelease])
|
||||
|
||||
await store.fetchReleases()
|
||||
|
||||
expect(mockSystemStatsStore.fetchSystemStats).toHaveBeenCalled()
|
||||
expect(until).toHaveBeenCalled()
|
||||
expect(mockReleaseService.getReleases).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -530,7 +540,7 @@ describe('useReleaseStore', () => {
|
||||
await store.initialize()
|
||||
|
||||
// Should not fetch system stats when notifications disabled
|
||||
expect(mockSystemStatsStore.fetchSystemStats).not.toHaveBeenCalled()
|
||||
expect(mockSystemStatsStore.refetchSystemStats).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should handle concurrent fetchReleases calls', async () => {
|
||||
|
||||
@@ -21,18 +21,25 @@ describe('useSystemStatsStore', () => {
|
||||
let store: ReturnType<typeof useSystemStatsStore>
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock API to prevent automatic fetch on store creation
|
||||
vi.mocked(api.getSystemStats).mockResolvedValue(null as any)
|
||||
setActivePinia(createPinia())
|
||||
store = useSystemStatsStore()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('should initialize with null systemStats', () => {
|
||||
expect(store.systemStats).toBeNull()
|
||||
expect(store.isLoading).toBe(false)
|
||||
expect(store.error).toBeNull()
|
||||
it('should initialize and start fetching immediately', async () => {
|
||||
// useAsyncState with immediate: true starts loading right away
|
||||
// In test environment, the mock resolves immediately so loading might be false already
|
||||
expect(store.systemStats).toBeNull() // Initial value is null
|
||||
expect(store.error).toBeUndefined()
|
||||
|
||||
// Wait for initial fetch to complete
|
||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||
expect(store.isInitialized).toBe(true) // Should be initialized after fetch
|
||||
})
|
||||
|
||||
describe('fetchSystemStats', () => {
|
||||
describe('refetchSystemStats', () => {
|
||||
it('should fetch system stats successfully', async () => {
|
||||
const mockStats = {
|
||||
system: {
|
||||
@@ -51,11 +58,12 @@ describe('useSystemStatsStore', () => {
|
||||
|
||||
vi.mocked(api.getSystemStats).mockResolvedValue(mockStats)
|
||||
|
||||
await store.fetchSystemStats()
|
||||
await store.refetchSystemStats()
|
||||
|
||||
expect(store.systemStats).toEqual(mockStats)
|
||||
expect(store.isLoading).toBe(false)
|
||||
expect(store.error).toBeNull()
|
||||
expect(store.error).toBeUndefined() // useAsyncState uses undefined for no error
|
||||
expect(store.isInitialized).toBe(true)
|
||||
expect(api.getSystemStats).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -63,19 +71,19 @@ describe('useSystemStatsStore', () => {
|
||||
const error = new Error('API Error')
|
||||
vi.mocked(api.getSystemStats).mockRejectedValue(error)
|
||||
|
||||
await store.fetchSystemStats()
|
||||
await store.refetchSystemStats()
|
||||
|
||||
expect(store.systemStats).toBeNull()
|
||||
expect(store.systemStats).toBeNull() // Initial value stays null on error
|
||||
expect(store.isLoading).toBe(false)
|
||||
expect(store.error).toBe('API Error')
|
||||
expect(store.error).toEqual(error) // useAsyncState stores the actual error object
|
||||
})
|
||||
|
||||
it('should handle non-Error objects', async () => {
|
||||
vi.mocked(api.getSystemStats).mockRejectedValue('String error')
|
||||
|
||||
await store.fetchSystemStats()
|
||||
await store.refetchSystemStats()
|
||||
|
||||
expect(store.error).toBe('An error occurred while fetching system stats')
|
||||
expect(store.error).toBe('String error') // useAsyncState stores the actual error
|
||||
})
|
||||
|
||||
it('should set loading state correctly', async () => {
|
||||
@@ -85,7 +93,7 @@ describe('useSystemStatsStore', () => {
|
||||
})
|
||||
vi.mocked(api.getSystemStats).mockReturnValue(promise)
|
||||
|
||||
const fetchPromise = store.fetchSystemStats()
|
||||
const fetchPromise = store.refetchSystemStats()
|
||||
expect(store.isLoading).toBe(true)
|
||||
|
||||
resolvePromise({})
|
||||
@@ -112,11 +120,12 @@ describe('useSystemStatsStore', () => {
|
||||
|
||||
vi.mocked(api.getSystemStats).mockResolvedValue(updatedStats)
|
||||
|
||||
await store.fetchSystemStats()
|
||||
await store.refetchSystemStats()
|
||||
|
||||
expect(store.systemStats).toEqual(updatedStats)
|
||||
expect(store.isLoading).toBe(false)
|
||||
expect(store.error).toBeNull()
|
||||
expect(store.error).toBeUndefined()
|
||||
expect(store.isInitialized).toBe(true)
|
||||
expect(api.getSystemStats).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -13,10 +13,11 @@ vi.mock('@/config', () => ({
|
||||
|
||||
vi.mock('@/stores/systemStatsStore')
|
||||
|
||||
// Mock useStorage from VueUse
|
||||
// Mock useStorage and until from VueUse
|
||||
const mockDismissalStorage = ref({} as Record<string, number>)
|
||||
vi.mock('@vueuse/core', () => ({
|
||||
useStorage: vi.fn(() => mockDismissalStorage)
|
||||
useStorage: vi.fn(() => mockDismissalStorage),
|
||||
until: vi.fn(() => Promise.resolve())
|
||||
}))
|
||||
|
||||
describe('useVersionCompatibilityStore', () => {
|
||||
@@ -31,7 +32,8 @@ describe('useVersionCompatibilityStore', () => {
|
||||
|
||||
mockSystemStatsStore = {
|
||||
systemStats: null,
|
||||
fetchSystemStats: vi.fn()
|
||||
isInitialized: false,
|
||||
refetchSystemStats: vi.fn()
|
||||
}
|
||||
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue(mockSystemStatsStore)
|
||||
@@ -51,6 +53,7 @@ describe('useVersionCompatibilityStore', () => {
|
||||
required_frontend_version: '1.25.0'
|
||||
}
|
||||
}
|
||||
mockSystemStatsStore.isInitialized = true
|
||||
|
||||
await store.checkVersionCompatibility()
|
||||
|
||||
@@ -68,6 +71,7 @@ describe('useVersionCompatibilityStore', () => {
|
||||
required_frontend_version: '1.23.0'
|
||||
}
|
||||
}
|
||||
mockSystemStatsStore.isInitialized = true
|
||||
|
||||
await store.checkVersionCompatibility()
|
||||
|
||||
@@ -83,6 +87,7 @@ describe('useVersionCompatibilityStore', () => {
|
||||
required_frontend_version: '1.24.0'
|
||||
}
|
||||
}
|
||||
mockSystemStatsStore.isInitialized = true
|
||||
|
||||
await store.checkVersionCompatibility()
|
||||
|
||||
@@ -98,6 +103,7 @@ describe('useVersionCompatibilityStore', () => {
|
||||
required_frontend_version: ''
|
||||
}
|
||||
}
|
||||
mockSystemStatsStore.isInitialized = true
|
||||
|
||||
await store.checkVersionCompatibility()
|
||||
|
||||
@@ -113,6 +119,7 @@ describe('useVersionCompatibilityStore', () => {
|
||||
required_frontend_version: 'not-a-version' // invalid semver format
|
||||
}
|
||||
}
|
||||
mockSystemStatsStore.isInitialized = true
|
||||
|
||||
await store.checkVersionCompatibility()
|
||||
|
||||
@@ -129,6 +136,7 @@ describe('useVersionCompatibilityStore', () => {
|
||||
required_frontend_version: '1.23.0' // Required is 1.23.0, frontend 1.24.0 meets this
|
||||
}
|
||||
}
|
||||
mockSystemStatsStore.isInitialized = true
|
||||
|
||||
await store.checkVersionCompatibility()
|
||||
|
||||
@@ -148,6 +156,7 @@ describe('useVersionCompatibilityStore', () => {
|
||||
required_frontend_version: '1.25.0'
|
||||
}
|
||||
}
|
||||
mockSystemStatsStore.isInitialized = true
|
||||
|
||||
await store.checkVersionCompatibility()
|
||||
|
||||
@@ -167,6 +176,7 @@ describe('useVersionCompatibilityStore', () => {
|
||||
required_frontend_version: '1.25.0'
|
||||
}
|
||||
}
|
||||
mockSystemStatsStore.isInitialized = true
|
||||
|
||||
await store.checkVersionCompatibility()
|
||||
|
||||
@@ -180,6 +190,7 @@ describe('useVersionCompatibilityStore', () => {
|
||||
required_frontend_version: '1.24.0'
|
||||
}
|
||||
}
|
||||
mockSystemStatsStore.isInitialized = true
|
||||
|
||||
await store.checkVersionCompatibility()
|
||||
|
||||
@@ -195,6 +206,7 @@ describe('useVersionCompatibilityStore', () => {
|
||||
required_frontend_version: '1.25.0'
|
||||
}
|
||||
}
|
||||
mockSystemStatsStore.isInitialized = true
|
||||
|
||||
await store.checkVersionCompatibility()
|
||||
|
||||
@@ -212,6 +224,7 @@ describe('useVersionCompatibilityStore', () => {
|
||||
required_frontend_version: '1.24.0'
|
||||
}
|
||||
}
|
||||
mockSystemStatsStore.isInitialized = true
|
||||
|
||||
await store.checkVersionCompatibility()
|
||||
|
||||
@@ -230,6 +243,7 @@ describe('useVersionCompatibilityStore', () => {
|
||||
required_frontend_version: '1.25.0'
|
||||
}
|
||||
}
|
||||
mockSystemStatsStore.isInitialized = true
|
||||
|
||||
await store.checkVersionCompatibility()
|
||||
store.dismissWarning()
|
||||
@@ -252,6 +266,7 @@ describe('useVersionCompatibilityStore', () => {
|
||||
required_frontend_version: '1.25.0'
|
||||
}
|
||||
}
|
||||
mockSystemStatsStore.isInitialized = true
|
||||
|
||||
await store.initialize()
|
||||
|
||||
@@ -270,6 +285,7 @@ describe('useVersionCompatibilityStore', () => {
|
||||
required_frontend_version: '1.25.0'
|
||||
}
|
||||
}
|
||||
mockSystemStatsStore.isInitialized = true
|
||||
|
||||
await store.initialize()
|
||||
|
||||
@@ -289,6 +305,7 @@ describe('useVersionCompatibilityStore', () => {
|
||||
required_frontend_version: '1.26.0'
|
||||
}
|
||||
}
|
||||
mockSystemStatsStore.isInitialized = true
|
||||
|
||||
await store.initialize()
|
||||
|
||||
@@ -298,24 +315,28 @@ describe('useVersionCompatibilityStore', () => {
|
||||
|
||||
describe('initialization', () => {
|
||||
it('should fetch system stats if not available', async () => {
|
||||
const { until } = await import('@vueuse/core')
|
||||
mockSystemStatsStore.systemStats = null
|
||||
mockSystemStatsStore.isInitialized = false
|
||||
|
||||
await store.initialize()
|
||||
|
||||
expect(mockSystemStatsStore.fetchSystemStats).toHaveBeenCalled()
|
||||
expect(until).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not fetch system stats if already available', async () => {
|
||||
const { until } = await import('@vueuse/core')
|
||||
mockSystemStatsStore.systemStats = {
|
||||
system: {
|
||||
comfyui_version: '1.24.0',
|
||||
required_frontend_version: '1.24.0'
|
||||
}
|
||||
}
|
||||
mockSystemStatsStore.isInitialized = true
|
||||
|
||||
await store.initialize()
|
||||
|
||||
expect(mockSystemStatsStore.fetchSystemStats).not.toHaveBeenCalled()
|
||||
expect(until).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,194 +0,0 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { useFeatureFlags } from '@/composables/useFeatureFlags'
|
||||
import { api } from '@/scripts/api'
|
||||
import { useExtensionStore } from '@/stores/extensionStore'
|
||||
import {
|
||||
ManagerUIState,
|
||||
useManagerStateStore
|
||||
} from '@/stores/managerStateStore'
|
||||
import { useSystemStatsStore } from '@/stores/systemStatsStore'
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock('@/scripts/api', () => ({
|
||||
api: {
|
||||
getClientFeatureFlags: vi.fn(),
|
||||
getServerFeature: vi.fn()
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/composables/useFeatureFlags', () => ({
|
||||
useFeatureFlags: vi.fn(() => ({
|
||||
flags: { supportsManagerV4: false },
|
||||
featureFlag: vi.fn()
|
||||
}))
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/extensionStore', () => ({
|
||||
useExtensionStore: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/systemStatsStore', () => ({
|
||||
useSystemStatsStore: vi.fn()
|
||||
}))
|
||||
|
||||
describe('useManagerStateStore', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('managerUIState computed', () => {
|
||||
it('should return DISABLED state when --disable-manager is present', () => {
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
||||
systemStats: {
|
||||
system: { argv: ['python', 'main.py', '--disable-manager'] }
|
||||
}
|
||||
} as any)
|
||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
|
||||
vi.mocked(useExtensionStore).mockReturnValue({
|
||||
extensions: []
|
||||
} as any)
|
||||
|
||||
const store = useManagerStateStore()
|
||||
|
||||
expect(store.managerUIState).toBe(ManagerUIState.DISABLED)
|
||||
})
|
||||
|
||||
it('should return LEGACY_UI state when --enable-manager-legacy-ui is present', () => {
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
||||
systemStats: {
|
||||
system: { argv: ['python', 'main.py', '--enable-manager-legacy-ui'] }
|
||||
}
|
||||
} as any)
|
||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
|
||||
vi.mocked(useExtensionStore).mockReturnValue({
|
||||
extensions: []
|
||||
} as any)
|
||||
|
||||
const store = useManagerStateStore()
|
||||
|
||||
expect(store.managerUIState).toBe(ManagerUIState.LEGACY_UI)
|
||||
})
|
||||
|
||||
it('should return NEW_UI state when client and server both support v4', () => {
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
||||
systemStats: { system: { argv: ['python', 'main.py'] } }
|
||||
} as any)
|
||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({
|
||||
supports_manager_v4_ui: true
|
||||
})
|
||||
vi.mocked(api.getServerFeature).mockReturnValue(true)
|
||||
vi.mocked(useFeatureFlags).mockReturnValue({
|
||||
flags: { supportsManagerV4: true },
|
||||
featureFlag: vi.fn()
|
||||
} as any)
|
||||
vi.mocked(useExtensionStore).mockReturnValue({
|
||||
extensions: []
|
||||
} as any)
|
||||
|
||||
const store = useManagerStateStore()
|
||||
|
||||
expect(store.managerUIState).toBe(ManagerUIState.NEW_UI)
|
||||
})
|
||||
|
||||
it('should return LEGACY_UI state when server supports v4 but client does not', () => {
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
||||
systemStats: { system: { argv: ['python', 'main.py'] } }
|
||||
} as any)
|
||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({
|
||||
supports_manager_v4_ui: false
|
||||
})
|
||||
vi.mocked(api.getServerFeature).mockReturnValue(true)
|
||||
vi.mocked(useFeatureFlags).mockReturnValue({
|
||||
flags: { supportsManagerV4: true },
|
||||
featureFlag: vi.fn()
|
||||
} as any)
|
||||
vi.mocked(useExtensionStore).mockReturnValue({
|
||||
extensions: []
|
||||
} as any)
|
||||
|
||||
const store = useManagerStateStore()
|
||||
|
||||
expect(store.managerUIState).toBe(ManagerUIState.LEGACY_UI)
|
||||
})
|
||||
|
||||
it('should return LEGACY_UI state when legacy manager extension exists', () => {
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
||||
systemStats: { system: { argv: ['python', 'main.py'] } }
|
||||
} as any)
|
||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
|
||||
vi.mocked(useFeatureFlags).mockReturnValue({
|
||||
flags: { supportsManagerV4: false },
|
||||
featureFlag: vi.fn()
|
||||
} as any)
|
||||
vi.mocked(useExtensionStore).mockReturnValue({
|
||||
extensions: [{ name: 'Comfy.CustomNodesManager' }]
|
||||
} as any)
|
||||
|
||||
const store = useManagerStateStore()
|
||||
|
||||
expect(store.managerUIState).toBe(ManagerUIState.LEGACY_UI)
|
||||
})
|
||||
|
||||
it('should return DISABLED state when feature flags are undefined', () => {
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
||||
systemStats: { system: { argv: ['python', 'main.py'] } }
|
||||
} as any)
|
||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
|
||||
vi.mocked(api.getServerFeature).mockReturnValue(undefined)
|
||||
vi.mocked(useFeatureFlags).mockReturnValue({
|
||||
flags: { supportsManagerV4: undefined },
|
||||
featureFlag: vi.fn()
|
||||
} as any)
|
||||
vi.mocked(useExtensionStore).mockReturnValue({
|
||||
extensions: []
|
||||
} as any)
|
||||
|
||||
const store = useManagerStateStore()
|
||||
|
||||
expect(store.managerUIState).toBe(ManagerUIState.DISABLED)
|
||||
})
|
||||
|
||||
it('should return DISABLED state when no manager is available', () => {
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
||||
systemStats: { system: { argv: ['python', 'main.py'] } }
|
||||
} as any)
|
||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
|
||||
vi.mocked(api.getServerFeature).mockReturnValue(false)
|
||||
vi.mocked(useFeatureFlags).mockReturnValue({
|
||||
flags: { supportsManagerV4: false },
|
||||
featureFlag: vi.fn()
|
||||
} as any)
|
||||
vi.mocked(useExtensionStore).mockReturnValue({
|
||||
extensions: []
|
||||
} as any)
|
||||
|
||||
const store = useManagerStateStore()
|
||||
|
||||
expect(store.managerUIState).toBe(ManagerUIState.DISABLED)
|
||||
})
|
||||
|
||||
it('should handle null systemStats gracefully', () => {
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
||||
systemStats: null
|
||||
} as any)
|
||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({
|
||||
supports_manager_v4_ui: true
|
||||
})
|
||||
vi.mocked(api.getServerFeature).mockReturnValue(true)
|
||||
vi.mocked(useFeatureFlags).mockReturnValue({
|
||||
flags: { supportsManagerV4: true },
|
||||
featureFlag: vi.fn()
|
||||
} as any)
|
||||
vi.mocked(useExtensionStore).mockReturnValue({
|
||||
extensions: []
|
||||
} as any)
|
||||
|
||||
const store = useManagerStateStore()
|
||||
|
||||
expect(store.managerUIState).toBe(ManagerUIState.NEW_UI)
|
||||
})
|
||||
})
|
||||
})
|
||||
254
tests-ui/tests/utils/packUtils.test.ts
Normal file
@@ -0,0 +1,254 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { normalizePackId, normalizePackKeys } from '@/utils/packUtils'
|
||||
|
||||
describe('packUtils', () => {
|
||||
describe('normalizePackId', () => {
|
||||
it('should return pack ID unchanged when no version suffix exists', () => {
|
||||
expect(normalizePackId('ComfyUI-GGUF')).toBe('ComfyUI-GGUF')
|
||||
expect(normalizePackId('ComfyUI-Manager')).toBe('ComfyUI-Manager')
|
||||
expect(normalizePackId('simple-pack')).toBe('simple-pack')
|
||||
})
|
||||
|
||||
it('should remove version suffix with underscores', () => {
|
||||
expect(normalizePackId('ComfyUI-GGUF@1_1_4')).toBe('ComfyUI-GGUF')
|
||||
expect(normalizePackId('ComfyUI-Manager@2_0_0')).toBe('ComfyUI-Manager')
|
||||
expect(normalizePackId('pack@1_0_0_beta')).toBe('pack')
|
||||
})
|
||||
|
||||
it('should remove version suffix with dots', () => {
|
||||
expect(normalizePackId('ComfyUI-GGUF@1.1.4')).toBe('ComfyUI-GGUF')
|
||||
expect(normalizePackId('pack@2.0.0')).toBe('pack')
|
||||
})
|
||||
|
||||
it('should handle multiple @ symbols by only removing after first @', () => {
|
||||
expect(normalizePackId('pack@1_0_0@extra')).toBe('pack')
|
||||
expect(normalizePackId('my@pack@1_0_0')).toBe('my')
|
||||
})
|
||||
|
||||
it('should handle empty string', () => {
|
||||
expect(normalizePackId('')).toBe('')
|
||||
})
|
||||
|
||||
it('should handle pack ID with @ but no version', () => {
|
||||
expect(normalizePackId('pack@')).toBe('pack')
|
||||
})
|
||||
|
||||
it('should handle special characters in pack name', () => {
|
||||
expect(normalizePackId('my-pack_v2@1_0_0')).toBe('my-pack_v2')
|
||||
expect(normalizePackId('pack.with.dots@2_0_0')).toBe('pack.with.dots')
|
||||
expect(normalizePackId('UPPERCASE-Pack@1_0_0')).toBe('UPPERCASE-Pack')
|
||||
})
|
||||
|
||||
it('should handle edge cases', () => {
|
||||
// Only @ symbol
|
||||
expect(normalizePackId('@')).toBe('')
|
||||
expect(normalizePackId('@1_0_0')).toBe('')
|
||||
|
||||
// Whitespace
|
||||
expect(normalizePackId(' pack @1_0_0')).toBe(' pack ')
|
||||
expect(normalizePackId('pack @1_0_0')).toBe('pack ')
|
||||
})
|
||||
})
|
||||
|
||||
describe('normalizePackKeys', () => {
|
||||
it('should normalize all keys with version suffixes', () => {
|
||||
const input = {
|
||||
'ComfyUI-GGUF': { ver: '1.1.4', enabled: true },
|
||||
'ComfyUI-Manager@2_0_0': { ver: '2.0.0', enabled: false },
|
||||
'another-pack@1_0_0': { ver: '1.0.0', enabled: true }
|
||||
}
|
||||
|
||||
const expected = {
|
||||
'ComfyUI-GGUF': { ver: '1.1.4', enabled: true },
|
||||
'ComfyUI-Manager': { ver: '2.0.0', enabled: false },
|
||||
'another-pack': { ver: '1.0.0', enabled: true }
|
||||
}
|
||||
|
||||
expect(normalizePackKeys(input)).toEqual(expected)
|
||||
})
|
||||
|
||||
it('should handle empty object', () => {
|
||||
expect(normalizePackKeys({})).toEqual({})
|
||||
})
|
||||
|
||||
it('should handle keys without version suffixes', () => {
|
||||
const input = {
|
||||
pack1: { data: 'value1' },
|
||||
pack2: { data: 'value2' }
|
||||
}
|
||||
|
||||
expect(normalizePackKeys(input)).toEqual(input)
|
||||
})
|
||||
|
||||
it('should handle mixed keys (with and without versions)', () => {
|
||||
const input = {
|
||||
'normal-pack': { ver: '1.0.0' },
|
||||
'versioned-pack@2_0_0': { ver: '2.0.0' },
|
||||
'another-normal': { ver: '3.0.0' },
|
||||
'another-versioned@4_0_0': { ver: '4.0.0' }
|
||||
}
|
||||
|
||||
const expected = {
|
||||
'normal-pack': { ver: '1.0.0' },
|
||||
'versioned-pack': { ver: '2.0.0' },
|
||||
'another-normal': { ver: '3.0.0' },
|
||||
'another-versioned': { ver: '4.0.0' }
|
||||
}
|
||||
|
||||
expect(normalizePackKeys(input)).toEqual(expected)
|
||||
})
|
||||
|
||||
it('should handle duplicate keys after normalization (last one wins)', () => {
|
||||
const input = {
|
||||
'pack@1_0_0': { ver: '1.0.0', data: 'first' },
|
||||
'pack@2_0_0': { ver: '2.0.0', data: 'second' },
|
||||
pack: { ver: '3.0.0', data: 'third' }
|
||||
}
|
||||
|
||||
const result = normalizePackKeys(input)
|
||||
|
||||
// The exact behavior depends on object iteration order,
|
||||
// but there should only be one 'pack' key in the result
|
||||
expect(Object.keys(result)).toEqual(['pack'])
|
||||
expect(result.pack).toBeDefined()
|
||||
expect(result.pack.ver).toBeDefined()
|
||||
})
|
||||
|
||||
it('should preserve value references', () => {
|
||||
const value1 = { ver: '1.0.0', complex: { nested: 'data' } }
|
||||
const value2 = { ver: '2.0.0', complex: { nested: 'data2' } }
|
||||
|
||||
const input = {
|
||||
'pack1@1_0_0': value1,
|
||||
'pack2@2_0_0': value2
|
||||
}
|
||||
|
||||
const result = normalizePackKeys(input)
|
||||
|
||||
// Values should be the same references, not cloned
|
||||
expect(result.pack1).toBe(value1)
|
||||
expect(result.pack2).toBe(value2)
|
||||
})
|
||||
|
||||
it('should handle special characters in keys', () => {
|
||||
const input = {
|
||||
'@1_0_0': { ver: '1.0.0' },
|
||||
'my-pack.v2@2_0_0': { ver: '2.0.0' },
|
||||
'UPPERCASE@3_0_0': { ver: '3.0.0' }
|
||||
}
|
||||
|
||||
const expected = {
|
||||
'': { ver: '1.0.0' },
|
||||
'my-pack.v2': { ver: '2.0.0' },
|
||||
UPPERCASE: { ver: '3.0.0' }
|
||||
}
|
||||
|
||||
expect(normalizePackKeys(input)).toEqual(expected)
|
||||
})
|
||||
|
||||
it('should work with different value types', () => {
|
||||
const input = {
|
||||
'pack1@1_0_0': 'string value',
|
||||
'pack2@2_0_0': 123,
|
||||
'pack3@3_0_0': null,
|
||||
'pack4@4_0_0': undefined,
|
||||
'pack5@5_0_0': true,
|
||||
pack6: []
|
||||
}
|
||||
|
||||
const expected = {
|
||||
pack1: 'string value',
|
||||
pack2: 123,
|
||||
pack3: null,
|
||||
pack4: undefined,
|
||||
pack5: true,
|
||||
pack6: []
|
||||
}
|
||||
|
||||
expect(normalizePackKeys(input)).toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Integration scenarios from JSDoc examples', () => {
|
||||
it('should handle the examples from normalizePackId JSDoc', () => {
|
||||
expect(normalizePackId('ComfyUI-GGUF')).toBe('ComfyUI-GGUF')
|
||||
expect(normalizePackId('ComfyUI-GGUF@1_1_4')).toBe('ComfyUI-GGUF')
|
||||
})
|
||||
|
||||
it('should handle the examples from normalizePackKeys JSDoc', () => {
|
||||
const input = {
|
||||
'ComfyUI-GGUF': { ver: '1.1.4', enabled: true },
|
||||
'ComfyUI-Manager@2_0_0': { ver: '2.0.0', enabled: false }
|
||||
}
|
||||
|
||||
const expected = {
|
||||
'ComfyUI-GGUF': { ver: '1.1.4', enabled: true },
|
||||
'ComfyUI-Manager': { ver: '2.0.0', enabled: false }
|
||||
}
|
||||
|
||||
expect(normalizePackKeys(input)).toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Real-world scenarios', () => {
|
||||
it('should handle typical ComfyUI-Manager response with mixed enabled/disabled packs', () => {
|
||||
// Simulating actual server response pattern
|
||||
const serverResponse = {
|
||||
// Enabled packs come without version suffix
|
||||
'ComfyUI-Essential': { ver: '1.2.3', enabled: true, aux_id: undefined },
|
||||
'ComfyUI-Impact': { ver: '2.0.0', enabled: true, aux_id: undefined },
|
||||
// Disabled packs come with version suffix
|
||||
'ComfyUI-GGUF@1_1_4': {
|
||||
ver: '1.1.4',
|
||||
enabled: false,
|
||||
aux_id: undefined
|
||||
},
|
||||
'ComfyUI-Manager@2_5_0': {
|
||||
ver: '2.5.0',
|
||||
enabled: false,
|
||||
aux_id: undefined
|
||||
}
|
||||
}
|
||||
|
||||
const normalized = normalizePackKeys(serverResponse)
|
||||
|
||||
// All keys should be normalized (no version suffixes)
|
||||
expect(Object.keys(normalized)).toEqual([
|
||||
'ComfyUI-Essential',
|
||||
'ComfyUI-Impact',
|
||||
'ComfyUI-GGUF',
|
||||
'ComfyUI-Manager'
|
||||
])
|
||||
|
||||
// Values should be preserved
|
||||
expect(normalized['ComfyUI-GGUF']).toEqual({
|
||||
ver: '1.1.4',
|
||||
enabled: false,
|
||||
aux_id: undefined
|
||||
})
|
||||
})
|
||||
|
||||
it('should allow consistent access by pack ID regardless of enabled state', () => {
|
||||
const packsBeforeToggle = {
|
||||
'my-pack': { ver: '1.0.0', enabled: true }
|
||||
}
|
||||
|
||||
const packsAfterToggle = {
|
||||
'my-pack@1_0_0': { ver: '1.0.0', enabled: false }
|
||||
}
|
||||
|
||||
const normalizedBefore = normalizePackKeys(packsBeforeToggle)
|
||||
const normalizedAfter = normalizePackKeys(packsAfterToggle)
|
||||
|
||||
// Both should have the same key after normalization
|
||||
expect(normalizedBefore['my-pack']).toBeDefined()
|
||||
expect(normalizedAfter['my-pack']).toBeDefined()
|
||||
|
||||
// Can access by the same key regardless of the original format
|
||||
expect(Object.keys(normalizedBefore)).toEqual(
|
||||
Object.keys(normalizedAfter)
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||