mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-05 21:54:50 +00:00
Compare commits
23 Commits
codex/qpov
...
drjkl/roun
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0afbffd386 | ||
|
|
3358786991 | ||
|
|
0a1418424e | ||
|
|
7ffe20da4f | ||
|
|
8dedd7543a | ||
|
|
27483a6d25 | ||
|
|
b3846b62b2 | ||
|
|
1c3f498f57 | ||
|
|
1bc7bb7544 | ||
|
|
93d3578106 | ||
|
|
bccc693430 | ||
|
|
a7bf3f38b7 | ||
|
|
21ba6a6823 | ||
|
|
f01da3c462 | ||
|
|
5361618a1f | ||
|
|
24eba16866 | ||
|
|
3251b7bf60 | ||
|
|
fde5dfa24d | ||
|
|
b7ac5b7f10 | ||
|
|
c117599bb6 | ||
|
|
0099fc6989 | ||
|
|
83aa879b07 | ||
|
|
2b2a72ffda |
251
.dependency-cruiser.mjs
Normal file
251
.dependency-cruiser.mjs
Normal file
@@ -0,0 +1,251 @@
|
||||
/** @type {import('dependency-cruiser').IConfiguration} */
|
||||
const config = {
|
||||
forbidden: [
|
||||
{
|
||||
name: 'no-circular',
|
||||
severity: 'warn',
|
||||
comment:
|
||||
'This dependency is part of a circular relationship. You might want to revise ' +
|
||||
'your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ',
|
||||
from: {},
|
||||
to: {
|
||||
circular: true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'no-orphans',
|
||||
comment:
|
||||
"This is an orphan module - it's likely not used (anymore?). Either use it or " +
|
||||
"remove it. If it's logical this module is an orphan (i.e. it's a config file), " +
|
||||
'add an exception for it in your dependency-cruiser configuration. By default ' +
|
||||
'this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration ' +
|
||||
'files (.d.ts), tsconfig.json and some of the babel and webpack configs.',
|
||||
severity: 'warn',
|
||||
from: {
|
||||
orphan: true,
|
||||
pathNot: [
|
||||
'(^|/)[.][^/]+[.](?:js|cjs|mjs|ts|cts|mts|json)$', // dot files
|
||||
'[.]d[.]ts$', // TypeScript declaration files
|
||||
'(^|/)tsconfig[.]json$', // TypeScript config
|
||||
'(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$' // other configs
|
||||
]
|
||||
},
|
||||
to: {}
|
||||
},
|
||||
{
|
||||
name: 'no-deprecated-core',
|
||||
comment:
|
||||
'A module depends on a node core module that has been deprecated. Find an alternative - these are ' +
|
||||
"bound to exist - node doesn't deprecate lightly.",
|
||||
severity: 'warn',
|
||||
from: {},
|
||||
to: {
|
||||
dependencyTypes: ['core'],
|
||||
path: [
|
||||
'^v8/tools/codemap$',
|
||||
'^v8/tools/consarray$',
|
||||
'^v8/tools/csvparser$',
|
||||
'^v8/tools/logreader$',
|
||||
'^v8/tools/profile_view$',
|
||||
'^v8/tools/profile$',
|
||||
'^v8/tools/SourceMap$',
|
||||
'^v8/tools/splaytree$',
|
||||
'^v8/tools/tickprocessor-driver$',
|
||||
'^v8/tools/tickprocessor$',
|
||||
'^node-inspect/lib/_inspect$',
|
||||
'^node-inspect/lib/internal/inspect_client$',
|
||||
'^node-inspect/lib/internal/inspect_repl$',
|
||||
'^async_hooks$',
|
||||
'^punycode$',
|
||||
'^domain$',
|
||||
'^constants$',
|
||||
'^sys$',
|
||||
'^_linklist$',
|
||||
'^_stream_wrap$'
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'not-to-deprecated',
|
||||
comment:
|
||||
'This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later ' +
|
||||
'version of that module, or find an alternative. Deprecated modules are a security risk.',
|
||||
severity: 'warn',
|
||||
from: {},
|
||||
to: {
|
||||
dependencyTypes: ['deprecated']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'no-non-package-json',
|
||||
severity: 'error',
|
||||
comment:
|
||||
"This module depends on an npm package that isn't in the 'dependencies' section of your package.json. " +
|
||||
"That's problematic as the package either (1) won't be available on live (2 - worse) will be " +
|
||||
'available on live with an non-guaranteed version. Fix it by adding the package to the dependencies ' +
|
||||
'in your package.json.',
|
||||
from: {},
|
||||
to: {
|
||||
dependencyTypes: ['npm-no-pkg', 'npm-unknown']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'not-to-unresolvable',
|
||||
comment:
|
||||
"This module depends on a module that cannot be found ('resolved to disk'). If it's an npm " +
|
||||
'module: add it to your package.json. In all other cases you likely already know what to do.',
|
||||
severity: 'error',
|
||||
from: {},
|
||||
to: {
|
||||
couldNotResolve: true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'no-duplicate-dep-types',
|
||||
comment:
|
||||
"Likely this module depends on an external ('npm') package that occurs more than once " +
|
||||
'in your package.json i.e. bot as a devDependencies and in dependencies. This will cause ' +
|
||||
'maintenance problems later on.',
|
||||
severity: 'warn',
|
||||
from: {},
|
||||
to: {
|
||||
moreThanOneDependencyType: true,
|
||||
// as it's common to use a devDependency for type-only imports: don't
|
||||
// consider type-only dependencyTypes for this rule
|
||||
dependencyTypesNot: ['type-only']
|
||||
}
|
||||
},
|
||||
|
||||
// rules you might want to tweak for your specific situation:
|
||||
|
||||
{
|
||||
name: 'not-to-spec',
|
||||
comment:
|
||||
'This module depends on a spec (test) file. The responsibility of a spec file is to test code. ' +
|
||||
"If there's something in a spec that's of use to other modules, it doesn't have that single " +
|
||||
'responsibility anymore. Factor it out into (e.g.) a separate utility/ helper or a mock.',
|
||||
severity: 'error',
|
||||
from: {},
|
||||
to: {
|
||||
path: '[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'not-to-dev-dep',
|
||||
severity: 'error',
|
||||
comment:
|
||||
"This module depends on an npm package from the 'devDependencies' section of your " +
|
||||
'package.json. It looks like something that ships to production, though. To prevent problems ' +
|
||||
"with npm packages that aren't there on production declare it (only!) in the 'dependencies'" +
|
||||
'section of your package.json. If this module is development only - add it to the ' +
|
||||
'from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration',
|
||||
from: {
|
||||
path: '^(packages)',
|
||||
pathNot: '[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$'
|
||||
},
|
||||
to: {
|
||||
dependencyTypes: ['npm-dev'],
|
||||
// type only dependencies are not a problem as they don't end up in the
|
||||
// production code or are ignored by the runtime.
|
||||
dependencyTypesNot: ['type-only'],
|
||||
pathNot: ['node_modules/@types/']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'optional-deps-used',
|
||||
severity: 'info',
|
||||
comment:
|
||||
'This module depends on an npm package that is declared as an optional dependency ' +
|
||||
"in your package.json. As this makes sense in limited situations only, it's flagged here. " +
|
||||
'If you use an optional dependency here by design - add an exception to your' +
|
||||
'dependency-cruiser configuration.',
|
||||
from: {},
|
||||
to: {
|
||||
dependencyTypes: ['npm-optional']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'peer-deps-used',
|
||||
comment:
|
||||
'This module depends on an npm package that is declared as a peer dependency ' +
|
||||
'in your package.json. This makes sense if your package is e.g. a plugin, but in ' +
|
||||
'other cases - maybe not so much. If the use of a peer dependency is intentional ' +
|
||||
'add an exception to your dependency-cruiser configuration.',
|
||||
severity: 'warn',
|
||||
from: {},
|
||||
to: {
|
||||
dependencyTypes: ['npm-peer']
|
||||
}
|
||||
}
|
||||
],
|
||||
options: {
|
||||
// Which modules not to follow further when encountered
|
||||
doNotFollow: {
|
||||
// path: an array of regular expressions in strings to match against
|
||||
path: ['node_modules']
|
||||
},
|
||||
|
||||
// false: don't look at JSDoc imports (the default)
|
||||
// true: detect dependencies in JSDoc-style import statements.
|
||||
// Implies parser: 'tsc', which a.o. means the typescript compiler will need
|
||||
// to be installed in the same spot you run dependency-cruiser from.
|
||||
detectJSDocImports: true,
|
||||
|
||||
// false: don't look at process.getBuiltinModule calls (the default)
|
||||
// true: dependency-cruiser will detect calls to process.getBuiltinModule/
|
||||
// globalThis.process.getBuiltinModule as imports.
|
||||
detectProcessBuiltinModuleCalls: true,
|
||||
|
||||
// false (the default): ignore dependencies that only exist before typescript-to-javascript compilation
|
||||
// true: also detect dependencies that only exist before typescript-to-javascript compilation
|
||||
// 'specify': for each dependency identify whether it only exists before compilation or also after
|
||||
tsPreCompilationDeps: true,
|
||||
|
||||
// if true combines the package.jsons found from the module up to the base
|
||||
// folder the cruise is initiated from. Useful for how (some) mono-repos
|
||||
// manage dependencies & dependency definitions.
|
||||
combinedDependencies: true,
|
||||
|
||||
// TypeScript project file ('tsconfig.json') to use for
|
||||
// (1) compilation and
|
||||
// (2) resolution (e.g. with the paths property)
|
||||
tsConfig: {
|
||||
fileName: 'tsconfig.json'
|
||||
},
|
||||
|
||||
// options to pass on to enhanced-resolve, the package dependency-cruiser
|
||||
// uses to resolve module references to disk.
|
||||
enhancedResolveOptions: {
|
||||
// What to consider as an 'exports' field in package.jsons
|
||||
exportsFields: ['exports'],
|
||||
|
||||
// List of conditions to check for in the exports field.
|
||||
// Only works when the 'exportsFields' array is non-empty.
|
||||
conditionNames: ['import', 'require', 'node', 'default', 'types'],
|
||||
|
||||
// What to consider a 'main' field in package.json
|
||||
mainFields: ['module', 'main', 'types', 'typings']
|
||||
},
|
||||
|
||||
// skipAnalysisNotInRules will make dependency-cruiser execute
|
||||
// analysis strictly necessary for checking the rule set only.
|
||||
skipAnalysisNotInRules: true,
|
||||
|
||||
reporterOptions: {
|
||||
dot: {
|
||||
// Pattern of modules to consolidate to.
|
||||
collapsePattern: 'node_modules/(?:@[^/]+/[^/]+|[^/]+)'
|
||||
},
|
||||
archi: {
|
||||
// Pattern of modules to consolidate to.
|
||||
collapsePattern:
|
||||
'^(?:packages|src|lib(s?)|app(s?)|bin|test(s?)|spec(s?))/[^/]+|node_modules/(?:@[^/]+/[^/]+|[^/]+)'
|
||||
},
|
||||
text: {
|
||||
highlightFocused: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default config
|
||||
@@ -78,6 +78,7 @@
|
||||
"@vue/test-utils": "catalog:",
|
||||
"@webgpu/types": "catalog:",
|
||||
"cross-env": "catalog:",
|
||||
"dependency-cruiser": "catalog:",
|
||||
"eslint": "catalog:",
|
||||
"eslint-config-prettier": "catalog:",
|
||||
"eslint-import-resolver-typescript": "catalog:",
|
||||
|
||||
141
pnpm-lock.yaml
generated
141
pnpm-lock.yaml
generated
@@ -15,15 +15,9 @@ catalogs:
|
||||
'@eslint/js':
|
||||
specifier: ^9.39.1
|
||||
version: 9.39.1
|
||||
'@iconify-json/lucide':
|
||||
specifier: ^1.1.178
|
||||
version: 1.2.79
|
||||
'@iconify/json':
|
||||
specifier: ^2.2.380
|
||||
version: 2.2.380
|
||||
'@iconify/tailwind':
|
||||
specifier: ^1.1.3
|
||||
version: 1.2.0
|
||||
'@intlify/eslint-plugin-vue-i18n':
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0
|
||||
@@ -141,6 +135,9 @@ catalogs:
|
||||
cva:
|
||||
specifier: 1.0.0-beta.4
|
||||
version: 1.0.0-beta.4
|
||||
dependency-cruiser:
|
||||
specifier: ^17.3.7
|
||||
version: 17.3.7
|
||||
dotenv:
|
||||
specifier: ^16.4.5
|
||||
version: 16.6.1
|
||||
@@ -591,6 +588,9 @@ importers:
|
||||
cross-env:
|
||||
specifier: 'catalog:'
|
||||
version: 10.1.0
|
||||
dependency-cruiser:
|
||||
specifier: 'catalog:'
|
||||
version: 17.3.7
|
||||
eslint:
|
||||
specifier: 'catalog:'
|
||||
version: 9.39.1(jiti@2.6.1)
|
||||
@@ -4147,11 +4147,22 @@ packages:
|
||||
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
|
||||
engines: {node: '>=6.5'}
|
||||
|
||||
acorn-jsx-walk@2.0.0:
|
||||
resolution: {integrity: sha512-uuo6iJj4D4ygkdzd6jPtcxs8vZgDX9YFIkqczGImoypX2fQ4dVImmu3UzA4ynixCIMTrEOWW+95M2HuBaCEOVA==}
|
||||
|
||||
acorn-jsx@5.3.2:
|
||||
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
||||
peerDependencies:
|
||||
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
|
||||
acorn-loose@8.5.2:
|
||||
resolution: {integrity: sha512-PPvV6g8UGMGgjrMu+n/f9E/tCSkNQ2Y97eFvuVdJfG11+xdIeDcLyNdC8SHcrHbRqkfwLASdplyR6B6sKM1U4A==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
acorn-walk@8.3.4:
|
||||
resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
acorn@7.4.1:
|
||||
resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
@@ -4850,6 +4861,11 @@ packages:
|
||||
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
dependency-cruiser@17.3.7:
|
||||
resolution: {integrity: sha512-WEEOrnf0eshNirg4CMWuB7kK+qVZ+fecW6EBJa6AomEFhDDZKi3Zel1Tyl4ihcWtiSDhF+vALQb8NJS+wQiwLA==}
|
||||
engines: {node: ^20.12||^22||>=24}
|
||||
hasBin: true
|
||||
|
||||
dequal@2.0.3:
|
||||
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -4975,6 +4991,10 @@ packages:
|
||||
resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
enhanced-resolve@5.18.4:
|
||||
resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
enquirer@2.3.6:
|
||||
resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==}
|
||||
engines: {node: '>=8.6'}
|
||||
@@ -5713,6 +5733,10 @@ packages:
|
||||
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
interpret@3.1.1:
|
||||
resolution: {integrity: sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
is-array-buffer@3.0.5:
|
||||
resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -6095,6 +6119,10 @@ packages:
|
||||
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
kleur@3.0.3:
|
||||
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
knip@5.75.1:
|
||||
resolution: {integrity: sha512-raguBFxTUO5JKrv8rtC8wrOtzrDwWp/fOu1F1GhrHD1F3TD2fqI1Z74JB+PyFZubL+RxqOkhGStdPAvaaXSOWQ==}
|
||||
engines: {node: '>=18.18.0'}
|
||||
@@ -7049,6 +7077,10 @@ packages:
|
||||
promise@7.3.1:
|
||||
resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==}
|
||||
|
||||
prompts@2.4.2:
|
||||
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
prosemirror-changeset@2.2.1:
|
||||
resolution: {integrity: sha512-J7msc6wbxB4ekDFj+n9gTW/jav/p53kdlivvuppHsrZXCaQdVgRghoZbSS3kwrRyAstRVQ4/+u5k7YfLgkkQvQ==}
|
||||
|
||||
@@ -7222,6 +7254,10 @@ packages:
|
||||
resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
rechoir@0.8.0:
|
||||
resolution: {integrity: sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==}
|
||||
engines: {node: '>= 10.13.0'}
|
||||
|
||||
recorder-audio-worklet-processor@5.0.35:
|
||||
resolution: {integrity: sha512-5Nzbk/6QzC3QFQ1EG2SE34c1ygLE22lIOvLyjy7N6XxE/jpAZrL4e7xR+yihiTaG3ajiWy6UjqL4XEBMM9ahFQ==}
|
||||
|
||||
@@ -7243,6 +7279,10 @@ packages:
|
||||
regenerate@1.4.2:
|
||||
resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==}
|
||||
|
||||
regexp-tree@0.1.27:
|
||||
resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==}
|
||||
hasBin: true
|
||||
|
||||
regexp.prototype.flags@1.5.4:
|
||||
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -7388,6 +7428,9 @@ packages:
|
||||
resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
safe-regex@2.1.1:
|
||||
resolution: {integrity: sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==}
|
||||
|
||||
saxes@6.0.0:
|
||||
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
|
||||
engines: {node: '>=v12.22.7'}
|
||||
@@ -7469,6 +7512,9 @@ packages:
|
||||
resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
sisteransi@1.0.5:
|
||||
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
|
||||
|
||||
slash@3.0.0:
|
||||
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -7797,6 +7843,10 @@ packages:
|
||||
ts-map@1.0.3:
|
||||
resolution: {integrity: sha512-vDWbsl26LIcPGmDpoVzjEP6+hvHZkBkLW7JpvwbCv/5IYPJlsbzCVXY3wsCeAxAUeTclNOUZxnLdGh3VBD/J6w==}
|
||||
|
||||
tsconfig-paths-webpack-plugin@4.2.0:
|
||||
resolution: {integrity: sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
tsconfig-paths@3.15.0:
|
||||
resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==}
|
||||
|
||||
@@ -8168,8 +8218,8 @@ packages:
|
||||
vue-component-type-helpers@3.2.1:
|
||||
resolution: {integrity: sha512-gKV7XOkQl4urSuLHNY1tnVQf7wVgtb/mKbRyxSLWGZUY9RK7aDPhBenTjm+i8ZFe0zC2PZeHMPtOZXZfyaFOzQ==}
|
||||
|
||||
vue-component-type-helpers@3.2.2:
|
||||
resolution: {integrity: sha512-x8C2nx5XlUNM0WirgfTkHjJGO/ABBxlANZDtHw2HclHtQnn+RFPTnbjMJn8jHZW4TlUam0asHcA14lf1C6Jb+A==}
|
||||
vue-component-type-helpers@3.2.4:
|
||||
resolution: {integrity: sha512-05lR16HeZDcDpB23ku5b5f1fBOoHqFnMiKRr2CiEvbG5Ux4Yi0McmQBOET0dR0nxDXosxyVqv67q6CzS3AK8rw==}
|
||||
|
||||
vue-demi@0.14.10:
|
||||
resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
|
||||
@@ -8250,6 +8300,11 @@ packages:
|
||||
resolution: {integrity: sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
watskeburt@5.0.2:
|
||||
resolution: {integrity: sha512-8xIz2RALjwTA7kYeRtkiQ2uaFyr327T1GXJnVcGOoPuzQX2axpUXqeJPcgOEVemCWB2YveZjhWCcW/eZ3uTkZA==}
|
||||
engines: {node: ^20.12||^22.13||>=24.0}
|
||||
hasBin: true
|
||||
|
||||
wcwidth@1.0.1:
|
||||
resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
|
||||
|
||||
@@ -11199,7 +11254,7 @@ snapshots:
|
||||
storybook: 10.1.9(@testing-library/dom@10.4.1)(prettier@3.7.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
type-fest: 2.19.0
|
||||
vue: 3.5.13(typescript@5.9.3)
|
||||
vue-component-type-helpers: 3.2.2
|
||||
vue-component-type-helpers: 3.2.4
|
||||
|
||||
'@swc/helpers@0.5.17':
|
||||
dependencies:
|
||||
@@ -12264,10 +12319,20 @@ snapshots:
|
||||
dependencies:
|
||||
event-target-shim: 5.0.1
|
||||
|
||||
acorn-jsx-walk@2.0.0: {}
|
||||
|
||||
acorn-jsx@5.3.2(acorn@8.15.0):
|
||||
dependencies:
|
||||
acorn: 8.15.0
|
||||
|
||||
acorn-loose@8.5.2:
|
||||
dependencies:
|
||||
acorn: 8.15.0
|
||||
|
||||
acorn-walk@8.3.4:
|
||||
dependencies:
|
||||
acorn: 8.15.0
|
||||
|
||||
acorn@7.4.1: {}
|
||||
|
||||
acorn@8.15.0: {}
|
||||
@@ -13006,6 +13071,27 @@ snapshots:
|
||||
|
||||
delayed-stream@1.0.0: {}
|
||||
|
||||
dependency-cruiser@17.3.7:
|
||||
dependencies:
|
||||
acorn: 8.15.0
|
||||
acorn-jsx: 5.3.2(acorn@8.15.0)
|
||||
acorn-jsx-walk: 2.0.0
|
||||
acorn-loose: 8.5.2
|
||||
acorn-walk: 8.3.4
|
||||
commander: 14.0.2
|
||||
enhanced-resolve: 5.18.4
|
||||
ignore: 7.0.5
|
||||
interpret: 3.1.1
|
||||
is-installed-globally: 1.0.0
|
||||
json5: 2.2.3
|
||||
picomatch: 4.0.3
|
||||
prompts: 2.4.2
|
||||
rechoir: 0.8.0
|
||||
safe-regex: 2.1.1
|
||||
semver: 7.7.3
|
||||
tsconfig-paths-webpack-plugin: 4.2.0
|
||||
watskeburt: 5.0.2
|
||||
|
||||
dequal@2.0.3: {}
|
||||
|
||||
detect-libc@2.0.4: {}
|
||||
@@ -13137,6 +13223,11 @@ snapshots:
|
||||
graceful-fs: 4.2.11
|
||||
tapable: 2.2.3
|
||||
|
||||
enhanced-resolve@5.18.4:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
tapable: 2.2.3
|
||||
|
||||
enquirer@2.3.6:
|
||||
dependencies:
|
||||
ansi-colors: 4.1.3
|
||||
@@ -14068,6 +14159,8 @@ snapshots:
|
||||
side-channel: 1.1.0
|
||||
optional: true
|
||||
|
||||
interpret@3.1.1: {}
|
||||
|
||||
is-array-buffer@3.0.5:
|
||||
dependencies:
|
||||
call-bind: 1.0.8
|
||||
@@ -14464,6 +14557,8 @@ snapshots:
|
||||
|
||||
kind-of@6.0.3: {}
|
||||
|
||||
kleur@3.0.3: {}
|
||||
|
||||
knip@5.75.1(@types/node@24.10.4)(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@nodelib/fs.walk': 1.2.8
|
||||
@@ -15668,6 +15763,11 @@ snapshots:
|
||||
dependencies:
|
||||
asap: 2.0.6
|
||||
|
||||
prompts@2.4.2:
|
||||
dependencies:
|
||||
kleur: 3.0.3
|
||||
sisteransi: 1.0.5
|
||||
|
||||
prosemirror-changeset@2.2.1:
|
||||
dependencies:
|
||||
prosemirror-transform: 1.10.2
|
||||
@@ -15928,6 +16028,10 @@ snapshots:
|
||||
tiny-invariant: 1.3.3
|
||||
tslib: 2.8.1
|
||||
|
||||
rechoir@0.8.0:
|
||||
dependencies:
|
||||
resolve: 1.22.11
|
||||
|
||||
recorder-audio-worklet-processor@5.0.35:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
@@ -15967,6 +16071,8 @@ snapshots:
|
||||
|
||||
regenerate@1.4.2: {}
|
||||
|
||||
regexp-tree@0.1.27: {}
|
||||
|
||||
regexp.prototype.flags@1.5.4:
|
||||
dependencies:
|
||||
call-bind: 1.0.8
|
||||
@@ -16185,6 +16291,10 @@ snapshots:
|
||||
is-regex: 1.2.1
|
||||
optional: true
|
||||
|
||||
safe-regex@2.1.1:
|
||||
dependencies:
|
||||
regexp-tree: 0.1.27
|
||||
|
||||
saxes@6.0.0:
|
||||
dependencies:
|
||||
xmlchars: 2.2.0
|
||||
@@ -16282,6 +16392,8 @@ snapshots:
|
||||
mrmime: 2.0.1
|
||||
totalist: 3.0.1
|
||||
|
||||
sisteransi@1.0.5: {}
|
||||
|
||||
slash@3.0.0: {}
|
||||
|
||||
slice-ansi@4.0.0:
|
||||
@@ -16660,6 +16772,13 @@ snapshots:
|
||||
|
||||
ts-map@1.0.3: {}
|
||||
|
||||
tsconfig-paths-webpack-plugin@4.2.0:
|
||||
dependencies:
|
||||
chalk: 4.1.2
|
||||
enhanced-resolve: 5.18.4
|
||||
tapable: 2.2.3
|
||||
tsconfig-paths: 4.2.0
|
||||
|
||||
tsconfig-paths@3.15.0:
|
||||
dependencies:
|
||||
'@types/json5': 0.0.29
|
||||
@@ -17203,7 +17322,7 @@ snapshots:
|
||||
|
||||
vue-component-type-helpers@3.2.1: {}
|
||||
|
||||
vue-component-type-helpers@3.2.2: {}
|
||||
vue-component-type-helpers@3.2.4: {}
|
||||
|
||||
vue-demi@0.14.10(vue@3.5.13(typescript@5.9.3)):
|
||||
dependencies:
|
||||
@@ -17285,6 +17404,8 @@ snapshots:
|
||||
|
||||
walk-up-path@4.0.0: {}
|
||||
|
||||
watskeburt@5.0.2: {}
|
||||
|
||||
wcwidth@1.0.1:
|
||||
dependencies:
|
||||
defaults: 1.0.4
|
||||
|
||||
@@ -48,6 +48,7 @@ catalog:
|
||||
axios: ^1.8.2
|
||||
cross-env: ^10.1.0
|
||||
cva: 1.0.0-beta.4
|
||||
dependency-cruiser: ^17.3.7
|
||||
dotenv: ^16.4.5
|
||||
eslint: ^9.39.1
|
||||
eslint-config-prettier: ^10.1.8
|
||||
@@ -62,8 +63,8 @@ catalog:
|
||||
happy-dom: ^20.0.11
|
||||
husky: ^9.1.7
|
||||
jiti: 2.6.1
|
||||
jsonata: ^2.1.0
|
||||
jsdom: ^27.4.0
|
||||
jsonata: ^2.1.0
|
||||
knip: ^5.75.1
|
||||
lint-staged: ^16.2.7
|
||||
markdown-table: ^3.0.4
|
||||
|
||||
@@ -99,7 +99,7 @@ import { useI18n } from 'vue-i18n'
|
||||
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import type { ConfirmationDialogType } from '@/services/dialogService'
|
||||
import type { ConfirmationDialogType } from '@/services/dialogTypes'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
|
||||
const props = defineProps<{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { computed } from 'vue'
|
||||
import type { ComputedRef } from 'vue'
|
||||
|
||||
import type { ExecutionErrorDialogInput } from '@/services/dialogService'
|
||||
import type { ExecutionErrorDialogInput } from '@/services/dialogTypes'
|
||||
import type { TaskItemImpl } from '@/stores/queueStore'
|
||||
|
||||
type CopyHandler = (value: string) => void | Promise<void>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { computed, watch } from 'vue'
|
||||
|
||||
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
|
||||
import { t } from '@/i18n'
|
||||
import { userIdentityHook } from '@/platform/telemetry/userIdentityBus'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { useApiKeyAuthStore } from '@/stores/apiKeyAuthStore'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
@@ -35,7 +36,14 @@ export const useCurrentUser = () => {
|
||||
})
|
||||
|
||||
const onUserResolved = (callback: (user: AuthUserInfo) => void) =>
|
||||
whenever(resolvedUserInfo, callback, { immediate: true })
|
||||
whenever(
|
||||
resolvedUserInfo,
|
||||
(user) => {
|
||||
void userIdentityHook.trigger({ userId: user.id })
|
||||
callback(user)
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const onTokenRefreshed = (callback: () => void) =>
|
||||
whenever(() => authStore.tokenRefreshTrigger, callback)
|
||||
|
||||
@@ -101,14 +101,14 @@ export function useJobMenu(
|
||||
const executionError = target.taskRef?.executionError
|
||||
|
||||
if (executionError) {
|
||||
useDialogService().showExecutionErrorDialog(executionError)
|
||||
void useDialogService().showExecutionErrorDialog(executionError)
|
||||
return
|
||||
}
|
||||
|
||||
// Fall back to simple error dialog
|
||||
const message = target.taskRef?.errorMessage
|
||||
if (message) {
|
||||
useDialogService().showErrorDialog(new Error(message), {
|
||||
void useDialogService().showErrorDialog(new Error(message), {
|
||||
reportType: 'queueJobError'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { markRaw } from 'vue'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
import AssetsSidebarTab from '@/components/sidebar/tabs/AssetsSidebarTab.vue'
|
||||
import { useQueueStore } from '@/stores/queueStore'
|
||||
import type { SidebarTabExtension } from '@/types/extensionTypes'
|
||||
|
||||
const AssetsSidebarTab = defineAsyncComponent(
|
||||
() => import('@/components/sidebar/tabs/AssetsSidebarTab.vue')
|
||||
)
|
||||
|
||||
export const useAssetsSidebarTab = (): SidebarTabExtension => {
|
||||
return {
|
||||
id: 'assets',
|
||||
@@ -11,7 +14,7 @@ export const useAssetsSidebarTab = (): SidebarTabExtension => {
|
||||
title: 'sideToolbar.assets',
|
||||
tooltip: 'sideToolbar.assets',
|
||||
label: 'sideToolbar.labels.assets',
|
||||
component: markRaw(AssetsSidebarTab),
|
||||
component: AssetsSidebarTab,
|
||||
type: 'vue',
|
||||
iconBadge: () => {
|
||||
const queueStore = useQueueStore()
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { markRaw } from 'vue'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
import ModelLibrarySidebarTab from '@/components/sidebar/tabs/ModelLibrarySidebarTab.vue'
|
||||
import { useElectronDownloadStore } from '@/stores/electronDownloadStore'
|
||||
import type { SidebarTabExtension } from '@/types/extensionTypes'
|
||||
import { isElectron } from '@/utils/envUtil'
|
||||
|
||||
const ModelLibrarySidebarTab = defineAsyncComponent(
|
||||
() => import('@/components/sidebar/tabs/ModelLibrarySidebarTab.vue')
|
||||
)
|
||||
|
||||
export const useModelLibrarySidebarTab = (): SidebarTabExtension => {
|
||||
return {
|
||||
id: 'model-library',
|
||||
@@ -12,7 +15,7 @@ export const useModelLibrarySidebarTab = (): SidebarTabExtension => {
|
||||
title: 'sideToolbar.modelLibrary',
|
||||
tooltip: 'sideToolbar.modelLibrary',
|
||||
label: 'sideToolbar.labels.models',
|
||||
component: markRaw(ModelLibrarySidebarTab),
|
||||
component: ModelLibrarySidebarTab,
|
||||
type: 'vue',
|
||||
iconBadge: () => {
|
||||
if (isElectron()) {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { markRaw } from 'vue'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
import NodeLibrarySidebarTab from '@/components/sidebar/tabs/NodeLibrarySidebarTab.vue'
|
||||
import type { SidebarTabExtension } from '@/types/extensionTypes'
|
||||
|
||||
const NodeLibrarySidebarTab = defineAsyncComponent(
|
||||
() => import('@/components/sidebar/tabs/NodeLibrarySidebarTab.vue')
|
||||
)
|
||||
|
||||
export const useNodeLibrarySidebarTab = (): SidebarTabExtension => {
|
||||
return {
|
||||
id: 'node-library',
|
||||
@@ -10,7 +13,7 @@ export const useNodeLibrarySidebarTab = (): SidebarTabExtension => {
|
||||
title: 'sideToolbar.nodeLibrary',
|
||||
tooltip: 'sideToolbar.nodeLibrary',
|
||||
label: 'sideToolbar.labels.nodes',
|
||||
component: markRaw(NodeLibrarySidebarTab),
|
||||
component: NodeLibrarySidebarTab,
|
||||
type: 'vue'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -581,7 +581,7 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
versionAdded: '1.3.7',
|
||||
category: 'view-controls' as const,
|
||||
function: () => {
|
||||
dialogService.showSettingsDialog()
|
||||
void dialogService.showSettingsDialog()
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -830,7 +830,7 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
menubarLabel: 'About ComfyUI',
|
||||
versionAdded: '1.6.4',
|
||||
function: () => {
|
||||
dialogService.showSettingsDialog('about')
|
||||
void dialogService.showSettingsDialog('about')
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -72,7 +72,7 @@ export function useHelpCenter(
|
||||
* Show the node conflict dialog with current conflict data
|
||||
*/
|
||||
const showConflictModal = () => {
|
||||
showNodeConflictDialog({
|
||||
void showNodeConflictDialog({
|
||||
showAfterWhatsNew: true,
|
||||
dialogComponentProps: {
|
||||
onClose: () => {
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
} from '@/schemas/nodeDefSchema'
|
||||
import { useLitegraphService } from '@/services/litegraphService'
|
||||
import { app } from '@/scripts/app'
|
||||
import type { ComfyApp } from '@/scripts/app'
|
||||
import type { IComfyApp } from '@/types/appInterface'
|
||||
|
||||
const INLINE_INPUTS = false
|
||||
|
||||
@@ -69,7 +69,7 @@ function dynamicComboWidget(
|
||||
node: LGraphNode,
|
||||
inputName: string,
|
||||
untypedInputData: InputSpec,
|
||||
appArg: ComfyApp,
|
||||
appArg: IComfyApp,
|
||||
widgetName?: string
|
||||
) {
|
||||
const { addNodeInput } = useLitegraphService()
|
||||
|
||||
@@ -187,8 +187,9 @@ export class ClipspaceDialog extends ComfyDialog {
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Comfy.Clipspace',
|
||||
init(app) {
|
||||
app.openClipspace = function () {
|
||||
init(appArg) {
|
||||
const comfyApp = appArg as ComfyApp
|
||||
comfyApp.openClipspace = function () {
|
||||
if (!ClipspaceDialog.instance) {
|
||||
ClipspaceDialog.instance = new ClipspaceDialog()
|
||||
ComfyApp.clipspace_invalidate_handler = ClipspaceDialog.invalidate
|
||||
@@ -196,7 +197,7 @@ app.registerExtension({
|
||||
|
||||
if (ComfyApp.clipspace) {
|
||||
ClipspaceDialog.instance.show()
|
||||
} else app.ui.dialog.show('Clipspace is Empty!')
|
||||
} else comfyApp.ui.dialog.show('Clipspace is Empty!')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -8,7 +8,8 @@ import type {
|
||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
|
||||
import { type ComfyApp, app } from '../../scripts/app'
|
||||
import type { ComfyApp } from '../../scripts/app'
|
||||
import { app } from '../../scripts/app'
|
||||
import { $el } from '../../scripts/ui'
|
||||
import { ComfyDialog } from '../../scripts/ui/dialog'
|
||||
import { DraggableList } from '../../scripts/ui/draggableList'
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
addValueControlWidgets,
|
||||
isValidWidgetType
|
||||
} from '@/scripts/widgets'
|
||||
import { CONFIG, GET_CONFIG } from '@/services/litegraphService'
|
||||
import { CONFIG, GET_CONFIG } from '@/lib/litegraph/constants'
|
||||
import { mergeInputSpec } from '@/utils/nodeDefUtil'
|
||||
import { applyTextReplacements } from '@/utils/searchAndReplace'
|
||||
import { isPrimitiveNode } from '@/renderer/utils/nodeTypeGuards'
|
||||
|
||||
14
src/lib/litegraph/constants.ts
Normal file
14
src/lib/litegraph/constants.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Symbols used for widget configuration in litegraph nodes.
|
||||
* Extracted to break circular dependencies between litegraphService and extensions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Symbol used to access the config object on a widget.
|
||||
*/
|
||||
export const CONFIG = Symbol('CONFIG')
|
||||
|
||||
/**
|
||||
* Symbol used to access the config getter function on a widget.
|
||||
*/
|
||||
export const GET_CONFIG = Symbol('GET_CONFIG')
|
||||
@@ -99,7 +99,7 @@ import type {
|
||||
ISerialisedNode,
|
||||
SubgraphIO
|
||||
} from './types/serialisation'
|
||||
import type { NeverNever, PickNevers } from './types/utility'
|
||||
import type { NeverNever, PickNevers } from '../types/utility'
|
||||
import type { IBaseWidget, TWidgetValue } from './types/widgets'
|
||||
import { alignNodes, distributeNodes, getBoundaryNodes } from './utils/arrange'
|
||||
import { findFirstNode, getAllNestedItems } from './utils/collections'
|
||||
|
||||
@@ -18,7 +18,7 @@ import { LGraphButton } from './LGraphButton'
|
||||
import type { LGraphButtonOptions } from './LGraphButton'
|
||||
import { LGraphCanvas } from './LGraphCanvas'
|
||||
import { LLink } from './LLink'
|
||||
import type { Reroute, RerouteId } from './Reroute'
|
||||
import type { Reroute } from './Reroute'
|
||||
import { getNodeInputOnPos, getNodeOutputOnPos } from './canvas/measureSlots'
|
||||
import type { IDrawBoundingOptions } from './draw'
|
||||
import { NullGraphError } from './infrastructure/NullGraphError'
|
||||
@@ -73,6 +73,7 @@ import {
|
||||
RenderShape,
|
||||
TitleMode
|
||||
} from './types/globalEnums'
|
||||
import type { NodeId, RerouteId } from './types/ids'
|
||||
import type { ISerialisedNode, SubgraphIO } from './types/serialisation'
|
||||
import type {
|
||||
IBaseWidget,
|
||||
@@ -88,9 +89,9 @@ import { BaseWidget } from './widgets/BaseWidget'
|
||||
import { toConcreteWidget } from './widgets/widgetMap'
|
||||
import type { WidgetTypeMap } from './widgets/widgetMap'
|
||||
|
||||
// #region Types
|
||||
export type { NodeId } from './types/ids'
|
||||
|
||||
export type NodeId = number | string
|
||||
// #region Types
|
||||
|
||||
export type NodeProperty = string | number | boolean | object
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ import type { SubgraphOutput } from '@/lib/litegraph/src/subgraph/SubgraphOutput
|
||||
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
|
||||
import { LayoutSource } from '@/renderer/core/layout/types'
|
||||
|
||||
import type { LGraphNode, NodeId } from './LGraphNode'
|
||||
import type { Reroute, RerouteId } from './Reroute'
|
||||
import type { LGraphNode } from './LGraphNode'
|
||||
import type { Reroute } from './Reroute'
|
||||
import type {
|
||||
CanvasColour,
|
||||
INodeInputSlot,
|
||||
@@ -19,11 +19,12 @@ import type {
|
||||
Point,
|
||||
ReadonlyLinkNetwork
|
||||
} from './interfaces'
|
||||
import type { LinkId, NodeId, RerouteId } from './types/ids'
|
||||
import type { Serialisable, SerialisableLLink } from './types/serialisation'
|
||||
|
||||
const layoutMutations = useLayoutMutations()
|
||||
export type { LinkId } from './types/ids'
|
||||
|
||||
export type LinkId = number
|
||||
const layoutMutations = useLayoutMutations()
|
||||
|
||||
export type SerialisedLLinkArray = [
|
||||
id: LinkId,
|
||||
|
||||
@@ -2,9 +2,8 @@ import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMuta
|
||||
import { LayoutSource } from '@/renderer/core/layout/types'
|
||||
|
||||
import { LGraphBadge } from './LGraphBadge'
|
||||
import type { LGraphNode, NodeId } from './LGraphNode'
|
||||
import type { LGraphNode } from './LGraphNode'
|
||||
import { LLink } from './LLink'
|
||||
import type { LinkId } from './LLink'
|
||||
import type {
|
||||
CanvasColour,
|
||||
INodeInputSlot,
|
||||
@@ -17,11 +16,12 @@ import type {
|
||||
ReadonlyLinkNetwork
|
||||
} from './interfaces'
|
||||
import { distance, isPointInRect } from './measure'
|
||||
import type { LinkId, NodeId, RerouteId } from './types/ids'
|
||||
import type { Serialisable, SerialisableReroute } from './types/serialisation'
|
||||
|
||||
const layoutMutations = useLayoutMutations()
|
||||
export type { RerouteId } from './types/ids'
|
||||
|
||||
export type RerouteId = number
|
||||
const layoutMutations = useLayoutMutations()
|
||||
|
||||
/** The input or output slot that an incomplete reroute link is connected to. */
|
||||
export interface FloatingRerouteSlot {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { NeverNever, PickNevers } from '@/lib/litegraph/src/types/utility'
|
||||
import type { NeverNever, PickNevers } from '@/lib/litegraph/types/utility'
|
||||
|
||||
type EventListeners<T> = {
|
||||
readonly [K in keyof T]:
|
||||
|
||||
@@ -3,15 +3,36 @@ import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events'
|
||||
import type { TWidgetValue } from '@/lib/litegraph/src/types/widgets'
|
||||
|
||||
import type { ContextMenu } from './ContextMenu'
|
||||
import type { LGraphNode, NodeId } from './LGraphNode'
|
||||
import type { LLink, LinkId } from './LLink'
|
||||
import type { Reroute, RerouteId } from './Reroute'
|
||||
import type { LGraphNode } from './LGraphNode'
|
||||
import type { LLink } from './LLink'
|
||||
import type { Reroute } from './Reroute'
|
||||
import type { SubgraphInput } from './subgraph/SubgraphInput'
|
||||
import type { SubgraphInputNode } from './subgraph/SubgraphInputNode'
|
||||
import type { SubgraphOutputNode } from './subgraph/SubgraphOutputNode'
|
||||
import type { LinkDirection, RenderShape } from './types/globalEnums'
|
||||
import type {
|
||||
CanvasColour,
|
||||
LinkId,
|
||||
NodeId,
|
||||
Point,
|
||||
ReadOnlyRect,
|
||||
RerouteId,
|
||||
Size
|
||||
} from './types/index'
|
||||
import type { IBaseWidget } from './types/widgets'
|
||||
|
||||
export type {
|
||||
CanvasColour,
|
||||
LinkId,
|
||||
NodeId,
|
||||
Point,
|
||||
ReadOnlyRect,
|
||||
ReadOnlyTypedArray,
|
||||
Rect,
|
||||
RerouteId,
|
||||
Size
|
||||
} from './types/index'
|
||||
|
||||
export type Dictionary<T> = { [key: string]: T }
|
||||
|
||||
/** Allows all properties to be null. The same as `Partial<T>`, but adds null instead of undefined. */
|
||||
@@ -46,8 +67,6 @@ export type SharedIntersection<T1, T2> = {
|
||||
[P in keyof T2 as P extends keyof T1 ? P : never]: T2[P]
|
||||
}
|
||||
|
||||
export type CanvasColour = string | CanvasGradient | CanvasPattern
|
||||
|
||||
/**
|
||||
* Any object that has a {@link boundingRect}.
|
||||
*/
|
||||
@@ -226,27 +245,6 @@ export interface IFoundSlot extends IInputOrOutput {
|
||||
link_pos: Point
|
||||
}
|
||||
|
||||
/** A point represented as `[x, y]` co-ordinates */
|
||||
export type Point = [x: number, y: number]
|
||||
|
||||
/** A size represented as `[width, height]` */
|
||||
export type Size = [width: number, height: number]
|
||||
|
||||
/** A rectangle starting at top-left coordinates `[x, y, width, height]` */
|
||||
export type Rect =
|
||||
| [x: number, y: number, width: number, height: number]
|
||||
| Float64Array
|
||||
|
||||
/** A rectangle starting at top-left coordinates `[x, y, width, height]` that will not be modified */
|
||||
export type ReadOnlyRect =
|
||||
| readonly [x: number, y: number, width: number, height: number]
|
||||
| ReadOnlyTypedArray<Float64Array>
|
||||
|
||||
export type ReadOnlyTypedArray<T extends Float64Array> = Omit<
|
||||
Readonly<T>,
|
||||
'fill' | 'copyWithin' | 'reverse' | 'set' | 'sort' | 'subarray'
|
||||
>
|
||||
|
||||
/** Union of property names that are of type Match */
|
||||
type KeysOfType<T, Match> = Exclude<
|
||||
{ [P in keyof T]: T[P] extends Match ? P : never }[keyof T],
|
||||
|
||||
29
src/lib/litegraph/src/types/geometry.ts
Normal file
29
src/lib/litegraph/src/types/geometry.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
/** A point represented as `[x, y]` co-ordinates */
|
||||
export type Point = [x: number, y: number]
|
||||
|
||||
/** A size represented as `[width, height]` */
|
||||
export type Size = [width: number, height: number]
|
||||
|
||||
/** A rectangle starting at top-left coordinates `[x, y, width, height]` */
|
||||
export type Rect =
|
||||
| [x: number, y: number, width: number, height: number]
|
||||
| Float64Array
|
||||
|
||||
/** A rectangle starting at top-left coordinates `[x, y, width, height]` that will not be modified */
|
||||
export type ReadOnlyRect =
|
||||
| readonly [x: number, y: number, width: number, height: number]
|
||||
| ReadOnlyTypedArray<Float64Array>
|
||||
|
||||
export type ReadOnlyTypedArray<T extends Float64Array> = Omit<
|
||||
Readonly<T>,
|
||||
'fill' | 'copyWithin' | 'reverse' | 'set' | 'sort' | 'subarray'
|
||||
>
|
||||
|
||||
/** A 2D vector as `[x, y]` */
|
||||
export type Vector2 = [x: number, y: number]
|
||||
|
||||
/** A 4D vector as `[x, y, z, w]` */
|
||||
export type Vector4 = [x: number, y: number, z: number, w: number]
|
||||
|
||||
/** Margin values as `[top, right, bottom, left]` */
|
||||
export type Margin = [top: number, right: number, bottom: number, left: number]
|
||||
8
src/lib/litegraph/src/types/ids.ts
Normal file
8
src/lib/litegraph/src/types/ids.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/** Unique identifier for a node in the graph */
|
||||
export type NodeId = number | string
|
||||
|
||||
/** Unique identifier for a link between nodes */
|
||||
export type LinkId = number
|
||||
|
||||
/** Unique identifier for a reroute point on a link */
|
||||
export type RerouteId = number
|
||||
14
src/lib/litegraph/src/types/index.ts
Normal file
14
src/lib/litegraph/src/types/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export type {
|
||||
Margin,
|
||||
Point,
|
||||
ReadOnlyRect,
|
||||
ReadOnlyTypedArray,
|
||||
Rect,
|
||||
Size,
|
||||
Vector2,
|
||||
Vector4
|
||||
} from './geometry'
|
||||
|
||||
export type { LinkId, NodeId, RerouteId } from './ids'
|
||||
|
||||
export type { CanvasColour, INodeSlotBase, ISlotType } from './slots'
|
||||
37
src/lib/litegraph/src/types/slots.ts
Normal file
37
src/lib/litegraph/src/types/slots.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import type { LinkDirection, RenderShape } from './globalEnums'
|
||||
import type { Point, ReadOnlyRect } from './geometry'
|
||||
|
||||
/** Union type for slot connection types - can be a string name or a numeric type code */
|
||||
export type ISlotType = string | number
|
||||
|
||||
/** Colour type for canvas elements */
|
||||
export type CanvasColour = string | CanvasGradient | CanvasPattern
|
||||
|
||||
/**
|
||||
* Base interface for node slots (inputs and outputs).
|
||||
* Contains common properties shared between input and output slots.
|
||||
*/
|
||||
export interface INodeSlotBase {
|
||||
/** The unique name of the slot */
|
||||
name: string
|
||||
/** The type of the slot, used for connection compatibility */
|
||||
type: ISlotType
|
||||
/** Direction of the link connection */
|
||||
dir?: LinkDirection
|
||||
/** Whether the slot can be removed */
|
||||
removable?: boolean
|
||||
/** Visual shape of the slot */
|
||||
shape?: RenderShape
|
||||
/** Color when disconnected */
|
||||
color_off?: CanvasColour
|
||||
/** Color when connected */
|
||||
color_on?: CanvasColour
|
||||
/** Whether the slot is locked from modifications */
|
||||
locked?: boolean
|
||||
/** Whether the slot name is locked from changes */
|
||||
nameLocked?: boolean
|
||||
/** Position of the slot relative to the node */
|
||||
pos?: Point
|
||||
/** Bounding rectangle of the slot for hit detection */
|
||||
boundingRect: ReadOnlyRect
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
* General-purpose, TypeScript utility types.
|
||||
*/
|
||||
|
||||
/** {@link Pick} only properties that evaluate to `never`. */
|
||||
export type PickNevers<T> = {
|
||||
[K in keyof T as T[K] extends never ? K : never]: T[K]
|
||||
}
|
||||
|
||||
/** {@link Omit} all properties that evaluate to `never`. */
|
||||
export type NeverNever<T> = {
|
||||
[K in keyof T as T[K] extends never ? never : K]: T[K]
|
||||
}
|
||||
40
src/lib/litegraph/types/geometry.ts
Normal file
40
src/lib/litegraph/types/geometry.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Pure geometry types for litegraph.
|
||||
* These have no dependencies on runtime code.
|
||||
*/
|
||||
|
||||
/** A point represented as `[x, y]` co-ordinates */
|
||||
export type Point = [x: number, y: number]
|
||||
|
||||
/** A size represented as `[width, height]` */
|
||||
export type Size = [width: number, height: number]
|
||||
|
||||
/** A rectangle starting at top-left coordinates `[x, y, width, height]` */
|
||||
export type Rect =
|
||||
| [x: number, y: number, width: number, height: number]
|
||||
| Float64Array
|
||||
|
||||
/** A rectangle starting at top-left coordinates `[x, y, width, height]` that will not be modified */
|
||||
export type ReadOnlyRect =
|
||||
| readonly [x: number, y: number, width: number, height: number]
|
||||
| ReadOnlyTypedArray<Float64Array>
|
||||
|
||||
export type ReadOnlyTypedArray<T extends Float64Array> = Omit<
|
||||
Readonly<T>,
|
||||
'fill' | 'copyWithin' | 'reverse' | 'set' | 'sort' | 'subarray'
|
||||
>
|
||||
|
||||
/** A 2D vector */
|
||||
export type Vector2 = [x: number, y: number]
|
||||
|
||||
/** A 4D vector */
|
||||
export type Vector4 = [x: number, y: number, z: number, w: number]
|
||||
|
||||
/** Direction as cardinal points */
|
||||
export type Direction = 'top' | 'bottom' | 'left' | 'right'
|
||||
|
||||
/** Resize handle positions (compass points) */
|
||||
export type CompassCorners = 'NE' | 'SE' | 'SW' | 'NW'
|
||||
|
||||
/** A color value for canvas rendering */
|
||||
export type CanvasColour = string | CanvasGradient | CanvasPattern
|
||||
25
src/lib/litegraph/types/ids.ts
Normal file
25
src/lib/litegraph/types/ids.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* ID types for litegraph entities.
|
||||
* These are branded types that can be used without importing runtime modules.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Node ID type.
|
||||
* @remarks Re-exported from LGraphNode for backwards compatibility,
|
||||
* but defined here to avoid circular imports.
|
||||
*/
|
||||
export type NodeId = number | string
|
||||
|
||||
/**
|
||||
* Link ID type.
|
||||
* @remarks Re-exported from LLink for backwards compatibility,
|
||||
* but defined here to avoid circular imports.
|
||||
*/
|
||||
export type LinkId = number
|
||||
|
||||
/**
|
||||
* Reroute ID type.
|
||||
* @remarks Re-exported from Reroute for backwards compatibility,
|
||||
* but defined here to avoid circular imports.
|
||||
*/
|
||||
export type RerouteId = number
|
||||
54
src/lib/litegraph/types/index.ts
Normal file
54
src/lib/litegraph/types/index.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Type-only barrel export for litegraph types.
|
||||
*
|
||||
* This module exports pure type definitions that can be imported
|
||||
* without pulling in any runtime code, helping to avoid circular
|
||||
* dependency issues.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import type { Point, NodeId, INodeSlot } from '@/lib/litegraph/types'
|
||||
* ```
|
||||
*/
|
||||
|
||||
// Geometry types
|
||||
export type {
|
||||
CanvasColour,
|
||||
CompassCorners,
|
||||
Direction,
|
||||
Point,
|
||||
ReadOnlyRect,
|
||||
ReadOnlyTypedArray,
|
||||
Rect,
|
||||
Size,
|
||||
Vector2,
|
||||
Vector4
|
||||
} from './geometry'
|
||||
|
||||
// ID types
|
||||
export type { LinkId, NodeId, RerouteId } from './ids'
|
||||
|
||||
// Slot types
|
||||
export type {
|
||||
HasBoundingRect,
|
||||
INodeFlags,
|
||||
INodeInputSlotBase,
|
||||
INodeOutputSlotBase,
|
||||
INodeSlotBase,
|
||||
ISlotType,
|
||||
IWidgetInputSlotBase,
|
||||
IWidgetLocator
|
||||
} from './slots'
|
||||
|
||||
// Utility types
|
||||
export type {
|
||||
Dictionary,
|
||||
MethodNames,
|
||||
NeverNever,
|
||||
NullableProperties,
|
||||
OptionalProps,
|
||||
PickNevers,
|
||||
RequiredProps,
|
||||
SharedIntersection,
|
||||
WhenNullish
|
||||
} from './utility'
|
||||
113
src/lib/litegraph/types/slots.ts
Normal file
113
src/lib/litegraph/types/slots.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Slot-related types for litegraph.
|
||||
* These have minimal dependencies and can be imported without pulling in runtime code.
|
||||
*/
|
||||
|
||||
import type { LinkDirection, RenderShape } from '../src/types/globalEnums'
|
||||
import type { CanvasColour, Point, ReadOnlyRect } from './geometry'
|
||||
import type { LinkId } from './ids'
|
||||
|
||||
/**
|
||||
* A string that represents a specific data / slot type, e.g. `STRING`.
|
||||
*
|
||||
* Can be comma-delimited to specify multiple allowed types, e.g. `STRING,INT`.
|
||||
*/
|
||||
export type ISlotType = number | string
|
||||
|
||||
/**
|
||||
* Any object that has a {@link boundingRect}.
|
||||
*/
|
||||
export interface HasBoundingRect {
|
||||
/**
|
||||
* A rectangle that represents the outer edges of the item.
|
||||
*
|
||||
* Used for various calculations, such as overlap, selective rendering, and click checks.
|
||||
* For most items, this is cached position & size as `x, y, width, height`.
|
||||
* Some items (such as nodes and slots) may extend above and/or to the left of their {@link pos}.
|
||||
* @readonly
|
||||
* @see {@link move}
|
||||
*/
|
||||
readonly boundingRect: ReadOnlyRect
|
||||
}
|
||||
|
||||
/**
|
||||
* Base slot interface without runtime-dependent properties.
|
||||
* The full INodeSlot in interfaces.ts extends this with runtime properties.
|
||||
*/
|
||||
export interface INodeSlotBase extends HasBoundingRect {
|
||||
/**
|
||||
* The name of the slot in English.
|
||||
* Will be included in the serialized data.
|
||||
*/
|
||||
name: string
|
||||
/**
|
||||
* The localized name of the slot to display in the UI.
|
||||
* Takes higher priority than {@link name} if set.
|
||||
* Will be included in the serialized data.
|
||||
*/
|
||||
localized_name?: string
|
||||
/**
|
||||
* The name of the slot to display in the UI, modified by the user.
|
||||
* Takes higher priority than {@link display_name} if set.
|
||||
* Will be included in the serialized data.
|
||||
*/
|
||||
label?: string
|
||||
|
||||
type: ISlotType
|
||||
dir?: LinkDirection
|
||||
removable?: boolean
|
||||
shape?: RenderShape
|
||||
color_off?: CanvasColour
|
||||
color_on?: CanvasColour
|
||||
locked?: boolean
|
||||
nameLocked?: boolean
|
||||
pos?: Point
|
||||
/** @remarks Automatically calculated; not included in serialisation. */
|
||||
boundingRect: ReadOnlyRect
|
||||
/**
|
||||
* Whether the slot has errors. It is **not** serialized.
|
||||
*/
|
||||
hasErrors?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* A widget that is linked to a slot.
|
||||
*
|
||||
* This is set by the ComfyUI_frontend logic. See
|
||||
* https://github.com/Comfy-Org/ComfyUI_frontend/blob/b80e0e1a3c74040f328c4e344326c969c97f67e0/src/extensions/core/widgetInputs.ts#L659
|
||||
*/
|
||||
export interface IWidgetLocator {
|
||||
name: string
|
||||
type?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Base input slot interface without runtime-dependent properties.
|
||||
*/
|
||||
export interface INodeInputSlotBase extends INodeSlotBase {
|
||||
link: LinkId | null
|
||||
widget?: IWidgetLocator
|
||||
alwaysVisible?: boolean
|
||||
}
|
||||
|
||||
export interface IWidgetInputSlotBase extends INodeInputSlotBase {
|
||||
widget: IWidgetLocator
|
||||
}
|
||||
|
||||
/**
|
||||
* Base output slot interface without runtime-dependent properties.
|
||||
*/
|
||||
export interface INodeOutputSlotBase extends INodeSlotBase {
|
||||
links: LinkId[] | null
|
||||
_data?: unknown
|
||||
slot_index?: number
|
||||
}
|
||||
|
||||
export interface INodeFlags {
|
||||
skip_repeated_outputs?: boolean
|
||||
allow_interaction?: boolean
|
||||
pinned?: boolean
|
||||
collapsed?: boolean
|
||||
/** Configuration setting for {@link LGraphNode.connectInputToOutput} */
|
||||
keepAllLinksOnBypass?: boolean
|
||||
}
|
||||
60
src/lib/litegraph/types/utility.ts
Normal file
60
src/lib/litegraph/types/utility.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* General-purpose TypeScript utility types for litegraph.
|
||||
* These have no dependencies on runtime code.
|
||||
*/
|
||||
|
||||
export type Dictionary<T> = { [key: string]: T }
|
||||
|
||||
/** Allows all properties to be null. The same as `Partial<T>`, but adds null instead of undefined. */
|
||||
export type NullableProperties<T> = {
|
||||
[P in keyof T]: T[P] | null
|
||||
}
|
||||
|
||||
/**
|
||||
* If {@link T} is `null` or `undefined`, evaluates to {@link Result}. Otherwise, evaluates to {@link T}.
|
||||
* Useful for functions that return e.g. `undefined` when a param is nullish.
|
||||
*/
|
||||
export type WhenNullish<T, Result> =
|
||||
| (T & {})
|
||||
| (T extends null ? Result : T extends undefined ? Result : T & {})
|
||||
|
||||
/** A type with each of the {@link Properties} made optional. */
|
||||
export type OptionalProps<T, Properties extends keyof T> = Omit<
|
||||
T,
|
||||
Properties
|
||||
> & { [K in Properties]?: T[K] }
|
||||
|
||||
/** A type with each of the {@link Properties} marked as required. */
|
||||
export type RequiredProps<T, Properties extends keyof T> = Omit<
|
||||
T,
|
||||
Properties
|
||||
> & { [K in Properties]-?: T[K] }
|
||||
|
||||
/** Bitwise AND intersection of two types; returns a new, non-union type that includes only properties that exist on both types. */
|
||||
export type SharedIntersection<T1, T2> = {
|
||||
[P in keyof T1 as P extends keyof T2 ? P : never]: T1[P]
|
||||
} & {
|
||||
[P in keyof T2 as P extends keyof T1 ? P : never]: T2[P]
|
||||
}
|
||||
|
||||
/** Union of property names that are of type Match */
|
||||
type KeysOfType<T, Match> = Exclude<
|
||||
{ [P in keyof T]: T[P] extends Match ? P : never }[keyof T],
|
||||
undefined
|
||||
>
|
||||
|
||||
/** The names of all (optional) methods and functions in T */
|
||||
export type MethodNames<T> = KeysOfType<
|
||||
T,
|
||||
((...args: unknown[]) => unknown) | undefined
|
||||
>
|
||||
|
||||
/** {@link Pick} only properties that evaluate to `never`. */
|
||||
export type PickNevers<T> = {
|
||||
[K in keyof T as T[K] extends never ? K : never]: T[K]
|
||||
}
|
||||
|
||||
/** {@link Omit} all properties that evaluate to `never`. */
|
||||
export type NeverNever<T> = {
|
||||
[K in keyof T as T[K] extends never ? never : K]: T[K]
|
||||
}
|
||||
@@ -24,7 +24,7 @@ export function useSubscriptionActions() {
|
||||
})
|
||||
|
||||
const handleAddApiCredits = () => {
|
||||
dialogService.showTopUpCreditsDialog()
|
||||
void dialogService.showTopUpCreditsDialog()
|
||||
}
|
||||
|
||||
const handleMessageSupport = async () => {
|
||||
|
||||
26
src/platform/telemetry/authTracking.ts
Normal file
26
src/platform/telemetry/authTracking.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { authEventHook, userResolvedHook } from '@/stores/authEventBus'
|
||||
|
||||
import type { TelemetryProvider } from './types'
|
||||
|
||||
export function initAuthTracking(
|
||||
getTelemetry: () => TelemetryProvider | null
|
||||
): void {
|
||||
authEventHook.on((event) => {
|
||||
const telemetry = getTelemetry()
|
||||
if (!telemetry) return
|
||||
|
||||
if (event.type === 'login' || event.type === 'register') {
|
||||
telemetry.trackAuth({
|
||||
method: event.method,
|
||||
is_new_user: event.is_new_user
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
userResolvedHook.on((event) => {
|
||||
const telemetry = getTelemetry()
|
||||
if (!telemetry) return
|
||||
|
||||
telemetry.identify?.(event.userId)
|
||||
})
|
||||
}
|
||||
@@ -16,11 +16,13 @@
|
||||
*/
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
|
||||
import { initAuthTracking } from './authTracking'
|
||||
import { MixpanelTelemetryProvider } from './providers/cloud/MixpanelTelemetryProvider'
|
||||
import type { TelemetryProvider } from './types'
|
||||
|
||||
// Singleton instance
|
||||
let _telemetryProvider: TelemetryProvider | null = null
|
||||
let _authTrackingInitialized = false
|
||||
|
||||
/**
|
||||
* Telemetry factory - conditionally creates provider based on distribution
|
||||
@@ -34,6 +36,11 @@ export function useTelemetry(): TelemetryProvider | null {
|
||||
// Use distribution check for tree-shaking
|
||||
if (isCloud) {
|
||||
_telemetryProvider = new MixpanelTelemetryProvider()
|
||||
|
||||
if (!_authTrackingInitialized) {
|
||||
initAuthTracking(() => _telemetryProvider)
|
||||
_authTrackingInitialized = true
|
||||
}
|
||||
}
|
||||
// For OSS builds, _telemetryProvider stays null
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { OverridedMixpanel } from 'mixpanel-browser'
|
||||
import { watch } from 'vue'
|
||||
|
||||
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
|
||||
import {
|
||||
checkForCompletedTopup as checkTopupUtil,
|
||||
clearTopupTracking as clearTopupUtil,
|
||||
@@ -120,11 +119,6 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
|
||||
loaded: () => {
|
||||
this.isInitialized = true
|
||||
this.flushEventQueue() // flush events that were queued while initializing
|
||||
useCurrentUser().onUserResolved((user) => {
|
||||
if (this.mixpanel && user.id) {
|
||||
this.mixpanel.identify(user.id)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -205,6 +199,12 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
|
||||
)
|
||||
}
|
||||
|
||||
identify(userId: string): void {
|
||||
if (this.mixpanel) {
|
||||
this.mixpanel.identify(userId)
|
||||
}
|
||||
}
|
||||
|
||||
trackSignupOpened(): void {
|
||||
this.trackEvent(TelemetryEvents.USER_SIGN_UP_OPENED)
|
||||
}
|
||||
|
||||
@@ -272,6 +272,9 @@ export interface WorkflowCreatedMetadata {
|
||||
* Core telemetry provider interface
|
||||
*/
|
||||
export interface TelemetryProvider {
|
||||
// User identification (called by auth event hooks)
|
||||
identify?(userId: string): void
|
||||
|
||||
// Authentication flow events
|
||||
trackSignupOpened(): void
|
||||
trackAuth(metadata: AuthMetadata): void
|
||||
|
||||
23
src/platform/telemetry/userIdentityBus.ts
Normal file
23
src/platform/telemetry/userIdentityBus.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { createEventHook } from '@vueuse/core'
|
||||
|
||||
interface UserIdentity {
|
||||
userId: string
|
||||
}
|
||||
|
||||
interface AuthEvent {
|
||||
event: 'login' | 'register' | 'logout'
|
||||
method: 'email' | 'google' | 'github'
|
||||
isNewUser: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Event hook for user identity changes.
|
||||
* Telemetry subscribes to this instead of importing useCurrentUser directly.
|
||||
*/
|
||||
export const userIdentityHook = createEventHook<UserIdentity>()
|
||||
|
||||
/**
|
||||
* Event hook for auth events (login, register, logout).
|
||||
* Telemetry subscribes to track auth events without auth importing telemetry.
|
||||
*/
|
||||
export const authEventHook = createEventHook<AuthEvent>()
|
||||
@@ -11,11 +11,15 @@ vi.mock('@/platform/distribution/types', () => ({ isCloud: false }))
|
||||
vi.mock('@/platform/updates/common/releaseService')
|
||||
vi.mock('@/platform/settings/settingStore')
|
||||
vi.mock('@/stores/systemStatsStore')
|
||||
vi.mock('@vueuse/core', () => ({
|
||||
until: vi.fn(() => Promise.resolve()),
|
||||
useStorage: vi.fn(() => ({ value: {} })),
|
||||
createSharedComposable: vi.fn((fn) => fn)
|
||||
}))
|
||||
vi.mock('@vueuse/core', async (importOriginal) => {
|
||||
const actual = await importOriginal()
|
||||
return {
|
||||
...(actual as object),
|
||||
until: vi.fn(() => Promise.resolve()),
|
||||
useStorage: vi.fn(() => ({ value: {} })),
|
||||
createSharedComposable: vi.fn((fn) => fn)
|
||||
}
|
||||
})
|
||||
|
||||
describe('useReleaseStore', () => {
|
||||
let store: ReturnType<typeof useReleaseStore>
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { markRaw } from 'vue'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
import WorkflowsSidebarTab from '@/components/sidebar/tabs/WorkflowsSidebarTab.vue'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
||||
import type { SidebarTabExtension } from '@/types/extensionTypes'
|
||||
|
||||
const WorkflowsSidebarTab = defineAsyncComponent(
|
||||
() => import('@/components/sidebar/tabs/WorkflowsSidebarTab.vue')
|
||||
)
|
||||
|
||||
export const useWorkflowsSidebarTab = (): SidebarTabExtension => {
|
||||
const settingStore = useSettingStore()
|
||||
const workflowStore = useWorkflowStore()
|
||||
@@ -23,7 +26,7 @@ export const useWorkflowsSidebarTab = (): SidebarTabExtension => {
|
||||
title: 'sideToolbar.workflows',
|
||||
tooltip: 'sideToolbar.workflows',
|
||||
label: 'sideToolbar.labels.workflows',
|
||||
component: markRaw(WorkflowsSidebarTab),
|
||||
component: WorkflowsSidebarTab,
|
||||
type: 'vue'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ import { SYSTEM_NODE_DEFS, useNodeDefStore } from '@/stores/nodeDefStore'
|
||||
import { useSubgraphStore } from '@/stores/subgraphStore'
|
||||
import { useWidgetStore } from '@/stores/widgetStore'
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
import type { IComfyApp } from '@/types/appInterface'
|
||||
import type { ComfyExtension, MissingNodeType } from '@/types/comfy'
|
||||
import { type ExtensionManager } from '@/types/extensionTypes'
|
||||
import type { NodeExecutionId } from '@/types/nodeIdentification'
|
||||
@@ -127,7 +128,7 @@ type Clipspace = {
|
||||
combinedIndex: number
|
||||
}
|
||||
|
||||
export class ComfyApp {
|
||||
export class ComfyApp implements IComfyApp {
|
||||
/**
|
||||
* List of entries to queue
|
||||
*/
|
||||
|
||||
@@ -9,7 +9,8 @@ import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
|
||||
import { api } from './api'
|
||||
import { ComfyApp, app } from './app'
|
||||
import type { ComfyApp } from './app'
|
||||
import { app } from './app'
|
||||
import { ComfyDialog as _ComfyDialog } from './ui/dialog'
|
||||
import { ComfySettingsDialog } from './ui/settings'
|
||||
import { toggleSwitch } from './ui/toggleSwitch'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ComfyApp } from '@/scripts/app'
|
||||
import type { IComfyApp } from '@/types/appInterface'
|
||||
|
||||
import { $el } from '../../ui'
|
||||
import { ComfyButtonGroup } from '../components/buttonGroup'
|
||||
@@ -13,13 +13,13 @@ export { DraggableList } from '@/scripts/ui/draggableList'
|
||||
export { applyTextReplacements, addStylesheet } from '@/scripts/utils'
|
||||
|
||||
export class ComfyAppMenu {
|
||||
app: ComfyApp
|
||||
app: IComfyApp
|
||||
actionsGroup: ComfyButtonGroup
|
||||
settingsGroup: ComfyButtonGroup
|
||||
viewGroup: ComfyButtonGroup
|
||||
element: HTMLElement
|
||||
|
||||
constructor(app: ComfyApp) {
|
||||
constructor(app: IComfyApp) {
|
||||
this.app = app
|
||||
|
||||
// Keep the group as there are custom scripts attaching extra
|
||||
|
||||
@@ -3,14 +3,14 @@ import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import type { SettingParams } from '@/platform/settings/types'
|
||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
import type { Settings } from '@/schemas/apiSchema'
|
||||
import type { ComfyApp } from '@/scripts/app'
|
||||
import type { IComfyApp } from '@/types/appInterface'
|
||||
|
||||
import { ComfyDialog } from './dialog'
|
||||
|
||||
export class ComfySettingsDialog extends ComfyDialog<HTMLDialogElement> {
|
||||
app: ComfyApp
|
||||
app: IComfyApp
|
||||
|
||||
constructor(app: ComfyApp) {
|
||||
constructor(app: IComfyApp) {
|
||||
super()
|
||||
this.app = app
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import { transformInputSpecV1ToV2 } from '@/schemas/nodeDef/migration'
|
||||
import type { InputSpec as InputSpecV2 } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||
import type { InputSpec } from '@/schemas/nodeDefSchema'
|
||||
|
||||
import type { ComfyApp } from './app'
|
||||
import type { IComfyApp } from '@/types/appInterface'
|
||||
import './domWidget'
|
||||
import './errorNodeWidgets'
|
||||
|
||||
@@ -37,7 +37,7 @@ export type ComfyWidgetConstructor = (
|
||||
node: LGraphNode,
|
||||
inputName: string,
|
||||
inputData: InputSpec,
|
||||
app: ComfyApp,
|
||||
app: IComfyApp,
|
||||
widgetName?: string
|
||||
) => { widget: IBaseWidget; minWidth?: number; minHeight?: number }
|
||||
|
||||
|
||||
@@ -1,66 +1,41 @@
|
||||
import { merge } from 'es-toolkit/compat'
|
||||
import type { Component } from 'vue'
|
||||
|
||||
import ApiNodesSignInContent from '@/components/dialog/content/ApiNodesSignInContent.vue'
|
||||
import MissingNodesContent from '@/components/dialog/content/MissingNodesContent.vue'
|
||||
import MissingNodesFooter from '@/components/dialog/content/MissingNodesFooter.vue'
|
||||
import MissingNodesHeader from '@/components/dialog/content/MissingNodesHeader.vue'
|
||||
import ConfirmationDialogContent from '@/components/dialog/content/ConfirmationDialogContent.vue'
|
||||
import ErrorDialogContent from '@/components/dialog/content/ErrorDialogContent.vue'
|
||||
import MissingModelsWarning from '@/components/dialog/content/MissingModelsWarning.vue'
|
||||
import PromptDialogContent from '@/components/dialog/content/PromptDialogContent.vue'
|
||||
import SignInContent from '@/components/dialog/content/SignInContent.vue'
|
||||
import TopUpCreditsDialogContent from '@/components/dialog/content/TopUpCreditsDialogContent.vue'
|
||||
import UpdatePasswordContent from '@/components/dialog/content/UpdatePasswordContent.vue'
|
||||
import ComfyOrgHeader from '@/components/dialog/header/ComfyOrgHeader.vue'
|
||||
import SettingDialogHeader from '@/components/dialog/header/SettingDialogHeader.vue'
|
||||
import type MissingModelsWarning from '@/components/dialog/content/MissingModelsWarning.vue'
|
||||
import type MissingNodesContent from '@/components/dialog/content/MissingNodesContent.vue'
|
||||
import { t } from '@/i18n'
|
||||
import { useTelemetry } from '@/platform/telemetry'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
|
||||
import SettingDialogContent from '@/platform/settings/components/SettingDialogContent.vue'
|
||||
import { useTelemetry } from '@/platform/telemetry'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
import type {
|
||||
DialogComponentProps,
|
||||
ShowDialogOptions
|
||||
} from '@/stores/dialogStore'
|
||||
|
||||
import ImportFailedNodeContent from '@/workbench/extensions/manager/components/manager/ImportFailedNodeContent.vue'
|
||||
import ImportFailedNodeFooter from '@/workbench/extensions/manager/components/manager/ImportFailedNodeFooter.vue'
|
||||
import ImportFailedNodeHeader from '@/workbench/extensions/manager/components/manager/ImportFailedNodeHeader.vue'
|
||||
import NodeConflictDialogContent from '@/workbench/extensions/manager/components/manager/NodeConflictDialogContent.vue'
|
||||
import NodeConflictFooter from '@/workbench/extensions/manager/components/manager/NodeConflictFooter.vue'
|
||||
import NodeConflictHeader from '@/workbench/extensions/manager/components/manager/NodeConflictHeader.vue'
|
||||
import type { ConflictDetectionResult } from '@/workbench/extensions/manager/types/conflictDetectionTypes'
|
||||
import type { ComponentAttrs } from 'vue-component-type-helpers'
|
||||
|
||||
export type ConfirmationDialogType =
|
||||
| 'default'
|
||||
| 'overwrite'
|
||||
| 'overwriteBlueprint'
|
||||
| 'delete'
|
||||
| 'dirtyClose'
|
||||
| 'reinstall'
|
||||
| 'info'
|
||||
|
||||
/**
|
||||
* Minimal interface for execution error dialogs.
|
||||
* Satisfied by both ExecutionErrorWsMessage (WebSocket) and ExecutionError (Jobs API).
|
||||
*/
|
||||
export interface ExecutionErrorDialogInput {
|
||||
exception_type: string
|
||||
exception_message: string
|
||||
node_id: string | number
|
||||
node_type: string
|
||||
traceback: string[]
|
||||
}
|
||||
import type {
|
||||
ConfirmationDialogType,
|
||||
ExecutionErrorDialogInput
|
||||
} from './dialogTypes'
|
||||
|
||||
export const useDialogService = () => {
|
||||
const dialogStore = useDialogStore()
|
||||
|
||||
function showLoadWorkflowWarning(
|
||||
async function showLoadWorkflowWarning(
|
||||
props: ComponentAttrs<typeof MissingNodesContent>
|
||||
) {
|
||||
const [
|
||||
{ default: MissingNodesHeader },
|
||||
{ default: MissingNodesFooter },
|
||||
{ default: MissingNodesContent }
|
||||
] = await Promise.all([
|
||||
import('@/components/dialog/content/MissingNodesHeader.vue'),
|
||||
import('@/components/dialog/content/MissingNodesFooter.vue'),
|
||||
import('@/components/dialog/content/MissingNodesContent.vue')
|
||||
])
|
||||
|
||||
dialogStore.showDialog({
|
||||
key: 'global-missing-nodes',
|
||||
headerComponent: MissingNodesHeader,
|
||||
@@ -84,9 +59,11 @@ export const useDialogService = () => {
|
||||
})
|
||||
}
|
||||
|
||||
function showMissingModelsWarning(
|
||||
async function showMissingModelsWarning(
|
||||
props: ComponentAttrs<typeof MissingModelsWarning>
|
||||
) {
|
||||
const { default: MissingModelsWarning } =
|
||||
await import('@/components/dialog/content/MissingModelsWarning.vue')
|
||||
dialogStore.showDialog({
|
||||
key: 'global-missing-models-warning',
|
||||
component: MissingModelsWarning,
|
||||
@@ -94,7 +71,7 @@ export const useDialogService = () => {
|
||||
})
|
||||
}
|
||||
|
||||
function showSettingsDialog(
|
||||
async function showSettingsDialog(
|
||||
panel?:
|
||||
| 'about'
|
||||
| 'keybinding'
|
||||
@@ -105,6 +82,13 @@ export const useDialogService = () => {
|
||||
| 'subscription'
|
||||
| 'workspace'
|
||||
) {
|
||||
const [
|
||||
{ default: SettingDialogHeader },
|
||||
{ default: SettingDialogContent }
|
||||
] = await Promise.all([
|
||||
import('@/components/dialog/header/SettingDialogHeader.vue'),
|
||||
import('@/platform/settings/components/SettingDialogContent.vue')
|
||||
])
|
||||
const props = panel ? { props: { defaultPanel: panel } } : undefined
|
||||
|
||||
dialogStore.showDialog({
|
||||
@@ -115,7 +99,14 @@ export const useDialogService = () => {
|
||||
})
|
||||
}
|
||||
|
||||
function showAboutDialog() {
|
||||
async function showAboutDialog() {
|
||||
const [
|
||||
{ default: SettingDialogHeader },
|
||||
{ default: SettingDialogContent }
|
||||
] = await Promise.all([
|
||||
import('@/components/dialog/header/SettingDialogHeader.vue'),
|
||||
import('@/platform/settings/components/SettingDialogContent.vue')
|
||||
])
|
||||
dialogStore.showDialog({
|
||||
key: 'global-settings',
|
||||
headerComponent: SettingDialogHeader,
|
||||
@@ -126,7 +117,11 @@ export const useDialogService = () => {
|
||||
})
|
||||
}
|
||||
|
||||
function showExecutionErrorDialog(executionError: ExecutionErrorDialogInput) {
|
||||
async function showExecutionErrorDialog(
|
||||
executionError: ExecutionErrorDialogInput
|
||||
) {
|
||||
const { default: ErrorDialogContent } =
|
||||
await import('@/components/dialog/content/ErrorDialogContent.vue')
|
||||
const props: ComponentAttrs<typeof ErrorDialogContent> = {
|
||||
error: {
|
||||
exceptionType: executionError.exception_type,
|
||||
@@ -174,13 +169,15 @@ export const useDialogService = () => {
|
||||
* @param error The error to show
|
||||
* @param options The options for the dialog
|
||||
*/
|
||||
function showErrorDialog(
|
||||
async function showErrorDialog(
|
||||
error: unknown,
|
||||
options: {
|
||||
title?: string
|
||||
reportType?: string
|
||||
} = {}
|
||||
) {
|
||||
const { default: ErrorDialogContent } =
|
||||
await import('@/components/dialog/content/ErrorDialogContent.vue')
|
||||
const errorProps: {
|
||||
errorMessage: string
|
||||
stackTrace?: string
|
||||
@@ -222,6 +219,11 @@ export const useDialogService = () => {
|
||||
async function showApiNodesSignInDialog(
|
||||
apiNodeNames: string[]
|
||||
): Promise<boolean> {
|
||||
const [{ default: ApiNodesSignInContent }, { default: ComfyOrgHeader }] =
|
||||
await Promise.all([
|
||||
import('@/components/dialog/content/ApiNodesSignInContent.vue'),
|
||||
import('@/components/dialog/header/ComfyOrgHeader.vue')
|
||||
])
|
||||
return new Promise<boolean>((resolve) => {
|
||||
dialogStore.showDialog({
|
||||
key: 'api-nodes-signin',
|
||||
@@ -244,6 +246,11 @@ export const useDialogService = () => {
|
||||
}
|
||||
|
||||
async function showSignInDialog(): Promise<boolean> {
|
||||
const [{ default: SignInContent }, { default: ComfyOrgHeader }] =
|
||||
await Promise.all([
|
||||
import('@/components/dialog/content/SignInContent.vue'),
|
||||
import('@/components/dialog/header/ComfyOrgHeader.vue')
|
||||
])
|
||||
return new Promise<boolean>((resolve) => {
|
||||
dialogStore.showDialog({
|
||||
key: 'global-signin',
|
||||
@@ -274,6 +281,8 @@ export const useDialogService = () => {
|
||||
defaultValue?: string
|
||||
placeholder?: string
|
||||
}): Promise<string | null> {
|
||||
const { default: PromptDialogContent } =
|
||||
await import('@/components/dialog/content/PromptDialogContent.vue')
|
||||
return new Promise((resolve) => {
|
||||
dialogStore.showDialog({
|
||||
key: 'global-prompt',
|
||||
@@ -318,6 +327,8 @@ export const useDialogService = () => {
|
||||
itemList?: string[]
|
||||
hint?: string
|
||||
}): Promise<boolean | null> {
|
||||
const { default: ConfirmationDialogContent } =
|
||||
await import('@/components/dialog/content/ConfirmationDialogContent.vue')
|
||||
return new Promise((resolve) => {
|
||||
const options: ShowDialogOptions = {
|
||||
key: 'global-prompt',
|
||||
@@ -339,12 +350,14 @@ export const useDialogService = () => {
|
||||
})
|
||||
}
|
||||
|
||||
function showTopUpCreditsDialog(options?: {
|
||||
async function showTopUpCreditsDialog(options?: {
|
||||
isInsufficientCredits?: boolean
|
||||
}) {
|
||||
const { isActiveSubscription } = useSubscription()
|
||||
if (!isActiveSubscription.value) return
|
||||
|
||||
const { default: TopUpCreditsDialogContent } =
|
||||
await import('@/components/dialog/content/TopUpCreditsDialogContent.vue')
|
||||
return dialogStore.showDialog({
|
||||
key: 'top-up-credits',
|
||||
component: TopUpCreditsDialogContent,
|
||||
@@ -363,7 +376,12 @@ export const useDialogService = () => {
|
||||
/**
|
||||
* Shows a dialog for updating the current user's password.
|
||||
*/
|
||||
function showUpdatePasswordDialog() {
|
||||
async function showUpdatePasswordDialog() {
|
||||
const [{ default: UpdatePasswordContent }, { default: ComfyOrgHeader }] =
|
||||
await Promise.all([
|
||||
import('@/components/dialog/content/UpdatePasswordContent.vue'),
|
||||
import('@/components/dialog/header/ComfyOrgHeader.vue')
|
||||
])
|
||||
return dialogStore.showDialog({
|
||||
key: 'global-update-password',
|
||||
component: UpdatePasswordContent,
|
||||
@@ -425,7 +443,7 @@ export const useDialogService = () => {
|
||||
})
|
||||
}
|
||||
|
||||
function showImportFailedNodeDialog(
|
||||
async function showImportFailedNodeDialog(
|
||||
options: {
|
||||
conflictedPackages?: ConflictDetectionResult[]
|
||||
dialogComponentProps?: DialogComponentProps
|
||||
@@ -433,6 +451,16 @@ export const useDialogService = () => {
|
||||
) {
|
||||
const { dialogComponentProps, conflictedPackages } = options
|
||||
|
||||
const [
|
||||
{ default: ImportFailedNodeHeader },
|
||||
{ default: ImportFailedNodeFooter },
|
||||
{ default: ImportFailedNodeContent }
|
||||
] = await Promise.all([
|
||||
import('@/workbench/extensions/manager/components/manager/ImportFailedNodeHeader.vue'),
|
||||
import('@/workbench/extensions/manager/components/manager/ImportFailedNodeFooter.vue'),
|
||||
import('@/workbench/extensions/manager/components/manager/ImportFailedNodeContent.vue')
|
||||
])
|
||||
|
||||
return dialogStore.showDialog({
|
||||
key: 'global-import-failed',
|
||||
headerComponent: ImportFailedNodeHeader,
|
||||
@@ -462,7 +490,7 @@ export const useDialogService = () => {
|
||||
})
|
||||
}
|
||||
|
||||
function showNodeConflictDialog(
|
||||
async function showNodeConflictDialog(
|
||||
options: {
|
||||
showAfterWhatsNew?: boolean
|
||||
conflictedPackages?: ConflictDetectionResult[]
|
||||
@@ -479,6 +507,16 @@ export const useDialogService = () => {
|
||||
conflictedPackages
|
||||
} = options
|
||||
|
||||
const [
|
||||
{ default: NodeConflictHeader },
|
||||
{ default: NodeConflictFooter },
|
||||
{ default: NodeConflictDialogContent }
|
||||
] = await Promise.all([
|
||||
import('@/workbench/extensions/manager/components/manager/NodeConflictHeader.vue'),
|
||||
import('@/workbench/extensions/manager/components/manager/NodeConflictFooter.vue'),
|
||||
import('@/workbench/extensions/manager/components/manager/NodeConflictDialogContent.vue')
|
||||
])
|
||||
|
||||
return dialogStore.showDialog({
|
||||
key: 'global-node-conflict',
|
||||
headerComponent: NodeConflictHeader,
|
||||
@@ -628,7 +666,9 @@ export const useDialogService = () => {
|
||||
})
|
||||
}
|
||||
|
||||
function showBillingComingSoonDialog() {
|
||||
async function showBillingComingSoonDialog() {
|
||||
const { default: ConfirmationDialogContent } =
|
||||
await import('@/components/dialog/content/ConfirmationDialogContent.vue')
|
||||
return dialogStore.showDialog({
|
||||
key: 'billing-coming-soon',
|
||||
title: t('subscription.billingComingSoon.title'),
|
||||
|
||||
25
src/services/dialogTypes.ts
Normal file
25
src/services/dialogTypes.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Type definitions for dialog service.
|
||||
* Extracted to break circular dependencies between dialogService and dialog components.
|
||||
*/
|
||||
|
||||
export type ConfirmationDialogType =
|
||||
| 'default'
|
||||
| 'overwrite'
|
||||
| 'overwriteBlueprint'
|
||||
| 'delete'
|
||||
| 'dirtyClose'
|
||||
| 'reinstall'
|
||||
| 'info'
|
||||
|
||||
/**
|
||||
* Minimal interface for execution error dialogs.
|
||||
* Satisfied by both ExecutionErrorWsMessage (WebSocket) and ExecutionError (Jobs API).
|
||||
*/
|
||||
export interface ExecutionErrorDialogInput {
|
||||
exception_type: string
|
||||
exception_message: string
|
||||
node_id: string | number
|
||||
node_type: string
|
||||
traceback: string[]
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
|
||||
import { useErrorHandling } from '@/composables/useErrorHandling'
|
||||
import { legacyMenuCompat } from '@/lib/litegraph/src/contextMenuCompat'
|
||||
import { isCloud, isNightly } from '@/platform/distribution/types'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app } from '@/scripts/app'
|
||||
@@ -13,6 +14,77 @@ import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
|
||||
import type { ComfyExtension } from '@/types/comfy'
|
||||
import type { AuthUserInfo } from '@/types/authTypes'
|
||||
|
||||
type ExtensionModule = { default?: ComfyExtension; extension?: ComfyExtension }
|
||||
|
||||
const coreExtensionModules = import.meta.glob<ExtensionModule>(
|
||||
'../extensions/core/*.ts',
|
||||
{ eager: false }
|
||||
)
|
||||
|
||||
async function loadCoreExtensions(
|
||||
registerExtension: (ext: ComfyExtension) => void
|
||||
) {
|
||||
const loadPromises = Object.entries(coreExtensionModules)
|
||||
.filter(([path]) => !path.endsWith('/index.ts'))
|
||||
.map(async ([path, loader]) => {
|
||||
try {
|
||||
const mod = await loader()
|
||||
const extension = mod.default ?? mod.extension
|
||||
if (extension && typeof extension === 'object' && 'name' in extension) {
|
||||
registerExtension(extension)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Failed to load extension from ${path}:`, e)
|
||||
}
|
||||
})
|
||||
|
||||
await Promise.all(loadPromises)
|
||||
|
||||
if (isCloud) {
|
||||
try {
|
||||
await import('../extensions/core/cloudRemoteConfig')
|
||||
} catch (e) {
|
||||
console.error('Failed to load cloudRemoteConfig:', e)
|
||||
}
|
||||
|
||||
try {
|
||||
await import('../extensions/core/cloudBadges')
|
||||
} catch (e) {
|
||||
console.error('Failed to load cloudBadges:', e)
|
||||
}
|
||||
|
||||
try {
|
||||
await import('../extensions/core/cloudSessionCookie')
|
||||
} catch (e) {
|
||||
console.error('Failed to load cloudSessionCookie:', e)
|
||||
}
|
||||
|
||||
if (window.__CONFIG__?.subscription_required) {
|
||||
try {
|
||||
await import('../extensions/core/cloudSubscription')
|
||||
} catch (e) {
|
||||
console.error('Failed to load cloudSubscription:', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isCloud || isNightly) {
|
||||
try {
|
||||
await import('../extensions/core/cloudFeedbackTopbarButton')
|
||||
} catch (e) {
|
||||
console.error('Failed to load cloudFeedbackTopbarButton:', e)
|
||||
}
|
||||
}
|
||||
|
||||
if (isNightly && !isCloud) {
|
||||
try {
|
||||
await import('../extensions/core/nightlyBadges')
|
||||
} catch (e) {
|
||||
console.error('Failed to load nightlyBadges:', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const useExtensionService = () => {
|
||||
const extensionStore = useExtensionStore()
|
||||
const settingStore = useSettingStore()
|
||||
@@ -33,9 +105,7 @@ export const useExtensionService = () => {
|
||||
|
||||
const extensions = await api.getExtensions()
|
||||
|
||||
// Need to load core extensions first as some custom extensions
|
||||
// may depend on them.
|
||||
await import('../extensions/core/index')
|
||||
await loadCoreExtensions(registerExtension)
|
||||
extensionStore.captureCoreExtensions()
|
||||
await Promise.all(
|
||||
extensions
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import _ from 'es-toolkit/compat'
|
||||
|
||||
import { GET_CONFIG } from '@/lib/litegraph/constants'
|
||||
|
||||
import { downloadFile } from '@/base/common/downloadUtil'
|
||||
import { useSelectedLiteGraphItems } from '@/composables/canvas/useSelectedLiteGraphItems'
|
||||
import { useSubgraphOperations } from '@/composables/graph/useSubgraphOperations'
|
||||
@@ -68,9 +70,6 @@ export interface HasInitialMinSize {
|
||||
_initialMinSize: { width: number; height: number }
|
||||
}
|
||||
|
||||
export const CONFIG = Symbol()
|
||||
export const GET_CONFIG = Symbol()
|
||||
|
||||
/**
|
||||
* Service that augments litegraph with ComfyUI specific functionality.
|
||||
*/
|
||||
|
||||
16
src/stores/authEventBus.ts
Normal file
16
src/stores/authEventBus.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { createEventHook } from '@vueuse/core'
|
||||
|
||||
import type { AuthMetadata } from '@/platform/telemetry/types'
|
||||
|
||||
export interface AuthEvent extends AuthMetadata {
|
||||
type: 'login' | 'register' | 'logout' | 'password_reset'
|
||||
}
|
||||
|
||||
export interface UserResolvedEvent {
|
||||
userId: string
|
||||
email?: string | null
|
||||
displayName?: string | null
|
||||
}
|
||||
|
||||
export const authEventHook = createEventHook<AuthEvent>()
|
||||
export const userResolvedHook = createEventHook<UserResolvedEvent>()
|
||||
@@ -25,8 +25,8 @@ import { getComfyApiBaseUrl } from '@/config/comfyApi'
|
||||
import { t } from '@/i18n'
|
||||
import { WORKSPACE_STORAGE_KEYS } from '@/platform/auth/workspace/workspaceConstants'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { useTelemetry } from '@/platform/telemetry'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { authEventHook, userResolvedHook } from '@/stores/authEventBus'
|
||||
import { useApiKeyAuthStore } from '@/stores/apiKeyAuthStore'
|
||||
import type { AuthHeader } from '@/types/authTypes'
|
||||
import type { operations } from '@/types/comfyRegistryTypes'
|
||||
@@ -323,10 +323,16 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
|
||||
)
|
||||
|
||||
if (isCloud) {
|
||||
useTelemetry()?.trackAuth({
|
||||
void authEventHook.trigger({
|
||||
type: 'login',
|
||||
method: 'email',
|
||||
is_new_user: false
|
||||
})
|
||||
void userResolvedHook.trigger({
|
||||
userId: result.user.uid,
|
||||
email: result.user.email,
|
||||
displayName: result.user.displayName
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -343,10 +349,16 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
|
||||
)
|
||||
|
||||
if (isCloud) {
|
||||
useTelemetry()?.trackAuth({
|
||||
void authEventHook.trigger({
|
||||
type: 'register',
|
||||
method: 'email',
|
||||
is_new_user: true
|
||||
})
|
||||
void userResolvedHook.trigger({
|
||||
userId: result.user.uid,
|
||||
email: result.user.email,
|
||||
displayName: result.user.displayName
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -361,10 +373,16 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
|
||||
if (isCloud) {
|
||||
const additionalUserInfo = getAdditionalUserInfo(result)
|
||||
const isNewUser = additionalUserInfo?.isNewUser ?? false
|
||||
useTelemetry()?.trackAuth({
|
||||
void authEventHook.trigger({
|
||||
type: isNewUser ? 'register' : 'login',
|
||||
method: 'google',
|
||||
is_new_user: isNewUser
|
||||
})
|
||||
void userResolvedHook.trigger({
|
||||
userId: result.user.uid,
|
||||
email: result.user.email,
|
||||
displayName: result.user.displayName
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -379,10 +397,16 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
|
||||
if (isCloud) {
|
||||
const additionalUserInfo = getAdditionalUserInfo(result)
|
||||
const isNewUser = additionalUserInfo?.isNewUser ?? false
|
||||
useTelemetry()?.trackAuth({
|
||||
void authEventHook.trigger({
|
||||
type: isNewUser ? 'register' : 'login',
|
||||
method: 'github',
|
||||
is_new_user: isNewUser
|
||||
})
|
||||
void userResolvedHook.trigger({
|
||||
userId: result.user.uid,
|
||||
email: result.user.email,
|
||||
displayName: result.user.displayName
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
@@ -15,7 +15,7 @@ import type {
|
||||
TaskOutput
|
||||
} from '@/schemas/apiSchema'
|
||||
import { api } from '@/scripts/api'
|
||||
import type { ComfyApp } from '@/scripts/app'
|
||||
import type { IComfyApp } from '@/types/appInterface'
|
||||
import { useExtensionService } from '@/services/extensionService'
|
||||
import { getJobDetail } from '@/services/jobOutputCache'
|
||||
import { useNodeOutputStore } from '@/stores/imagePreviewStore'
|
||||
@@ -408,7 +408,7 @@ export class TaskItemImpl {
|
||||
return new TaskItemImpl(this.job, jobDetail.outputs)
|
||||
}
|
||||
|
||||
public async loadWorkflow(app: ComfyApp) {
|
||||
public async loadWorkflow(app: IComfyApp) {
|
||||
if (!this.isHistory) {
|
||||
return
|
||||
}
|
||||
|
||||
60
src/types/appInterface.ts
Normal file
60
src/types/appInterface.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import type { LGraph, LGraphCanvas } from '@/lib/litegraph/src/litegraph'
|
||||
import type { NodeExecutionOutput } from '@/schemas/apiSchema'
|
||||
import type { ComfyWorkflow } from '@/platform/workflow/management/stores/workflowStore'
|
||||
import type { WorkflowOpenSource } from '@/platform/telemetry/types'
|
||||
import type { ComfyWorkflowJSON } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import type { ComfyNodeDef } from '@/schemas/nodeDefSchema'
|
||||
import type { ComfyExtension } from '@/types/comfy'
|
||||
|
||||
import type { ComfyWidgetConstructor } from '@/scripts/widgets'
|
||||
|
||||
export interface IComfyApp {
|
||||
vueAppReady: boolean
|
||||
rootGraph: LGraph
|
||||
canvas: LGraphCanvas
|
||||
configuringGraph: boolean
|
||||
nodeOutputs: Record<string, NodeExecutionOutput>
|
||||
|
||||
/** @deprecated storageLocation is always 'server' */
|
||||
readonly storageLocation: string
|
||||
/** @deprecated storage migration is no longer needed */
|
||||
readonly isNewUserSession: boolean
|
||||
/** @deprecated Use useExecutionStore().lastExecutionError instead */
|
||||
readonly lastExecutionError: unknown
|
||||
/** @deprecated Use useWidgetStore().widgets instead */
|
||||
readonly widgets: Record<string, ComfyWidgetConstructor>
|
||||
|
||||
getPreviewFormatParam(): string
|
||||
|
||||
loadGraphData(
|
||||
graphData?: ComfyWorkflowJSON,
|
||||
clean?: boolean,
|
||||
restore_view?: boolean,
|
||||
workflow?: string | null | ComfyWorkflow,
|
||||
options?: {
|
||||
showMissingNodesDialog?: boolean
|
||||
showMissingModelsDialog?: boolean
|
||||
checkForRerouteMigration?: boolean
|
||||
openSource?: WorkflowOpenSource
|
||||
}
|
||||
): Promise<void>
|
||||
|
||||
graphToPrompt(graph?: LGraph): Promise<{
|
||||
workflow: ComfyWorkflowJSON
|
||||
output: Record<string, unknown>
|
||||
}>
|
||||
|
||||
queuePrompt(
|
||||
number: number,
|
||||
batchCount?: number,
|
||||
queueNodeIds?: string[]
|
||||
): Promise<boolean>
|
||||
|
||||
clean(): void
|
||||
|
||||
handleFile(file: File, openSource?: WorkflowOpenSource): Promise<void>
|
||||
|
||||
registerExtension(extension: ComfyExtension): void
|
||||
|
||||
registerNodeDef(nodeId: string, nodeDef: ComfyNodeDef): Promise<void>
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import type { SettingParams } from '@/platform/settings/types'
|
||||
import type { ComfyWorkflowJSON } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import type { Keybinding } from '@/schemas/keyBindingSchema'
|
||||
import type { ComfyNodeDef } from '@/schemas/nodeDefSchema'
|
||||
import type { ComfyApp } from '@/scripts/app'
|
||||
import type { IComfyApp } from './appInterface'
|
||||
import type { ComfyWidgetConstructor } from '@/scripts/widgets'
|
||||
import type { ComfyCommand } from '@/stores/commandStore'
|
||||
import type { AuthUserInfo } from '@/types/authTypes'
|
||||
@@ -136,12 +136,12 @@ export interface ComfyExtension {
|
||||
* Allows any initialisation, e.g. loading resources. Called after the canvas is created but before nodes are added
|
||||
* @param app The ComfyUI app instance
|
||||
*/
|
||||
init?(app: ComfyApp): Promise<void> | void
|
||||
init?(app: IComfyApp): Promise<void> | void
|
||||
/**
|
||||
* Allows any additional setup, called after the application is fully set up and running
|
||||
* @param app The ComfyUI app instance
|
||||
*/
|
||||
setup?(app: ComfyApp): Promise<void> | void
|
||||
setup?(app: IComfyApp): Promise<void> | void
|
||||
/**
|
||||
* Called before nodes are registered with the graph
|
||||
* @param defs The collection of node definitions, add custom ones or edit existing ones
|
||||
@@ -149,7 +149,7 @@ export interface ComfyExtension {
|
||||
*/
|
||||
addCustomNodeDefs?(
|
||||
defs: Record<string, ComfyNodeDef>,
|
||||
app: ComfyApp
|
||||
app: IComfyApp
|
||||
): Promise<void> | void
|
||||
// TODO(huchenlei): We should deprecate the async return value of
|
||||
// getCustomWidgets.
|
||||
@@ -158,7 +158,7 @@ export interface ComfyExtension {
|
||||
* @param app The ComfyUI app instance
|
||||
* @returns An array of {[widget name]: widget data}
|
||||
*/
|
||||
getCustomWidgets?(app: ComfyApp): Promise<Widgets> | Widgets
|
||||
getCustomWidgets?(app: IComfyApp): Promise<Widgets> | Widgets
|
||||
|
||||
/**
|
||||
* Allows the extension to add additional commands to the selection toolbox
|
||||
@@ -190,7 +190,7 @@ export interface ComfyExtension {
|
||||
beforeRegisterNodeDef?(
|
||||
nodeType: typeof LGraphNode,
|
||||
nodeData: ComfyNodeDef,
|
||||
app: ComfyApp
|
||||
app: IComfyApp
|
||||
): Promise<void> | void
|
||||
|
||||
/**
|
||||
@@ -200,7 +200,7 @@ export interface ComfyExtension {
|
||||
* @param defs The node definitions
|
||||
* @param app The ComfyUI app instance
|
||||
*/
|
||||
beforeRegisterVueAppNodeDefs?(defs: ComfyNodeDef[], app: ComfyApp): void
|
||||
beforeRegisterVueAppNodeDefs?(defs: ComfyNodeDef[], app: IComfyApp): void
|
||||
|
||||
/**
|
||||
* Allows the extension to register additional nodes with LGraph after standard nodes are added.
|
||||
@@ -208,7 +208,7 @@ export interface ComfyExtension {
|
||||
*
|
||||
* @param app The ComfyUI app instance
|
||||
*/
|
||||
registerCustomNodes?(app: ComfyApp): Promise<void> | void
|
||||
registerCustomNodes?(app: IComfyApp): Promise<void> | void
|
||||
/**
|
||||
* Allows the extension to modify a node that has been reloaded onto the graph.
|
||||
* If you break something in the backend and want to patch workflows in the frontend
|
||||
@@ -216,13 +216,13 @@ export interface ComfyExtension {
|
||||
* @param node The node that has been loaded
|
||||
* @param app The ComfyUI app instance
|
||||
*/
|
||||
loadedGraphNode?(node: LGraphNode, app: ComfyApp): void
|
||||
loadedGraphNode?(node: LGraphNode, app: IComfyApp): void
|
||||
/**
|
||||
* Allows the extension to run code after the constructor of the node
|
||||
* @param node The node that has been created
|
||||
* @param app The ComfyUI app instance
|
||||
*/
|
||||
nodeCreated?(node: LGraphNode, app: ComfyApp): void
|
||||
nodeCreated?(node: LGraphNode, app: IComfyApp): void
|
||||
|
||||
/**
|
||||
* Allows the extension to modify the graph data before it is configured.
|
||||
@@ -247,7 +247,7 @@ export interface ComfyExtension {
|
||||
* Extensions can register at any time and will receive the latest value immediately.
|
||||
* This is an experimental API and may be changed or removed in the future.
|
||||
*/
|
||||
onAuthUserResolved?(user: AuthUserInfo, app: ComfyApp): Promise<void> | void
|
||||
onAuthUserResolved?(user: AuthUserInfo, app: IComfyApp): Promise<void> | void
|
||||
|
||||
/**
|
||||
* Fired whenever the auth token is refreshed.
|
||||
|
||||
@@ -13,7 +13,7 @@ import type {
|
||||
UserData,
|
||||
UserDataFullInfo
|
||||
} from '@/schemas/apiSchema'
|
||||
import type { ComfyApp } from '@/scripts/app'
|
||||
import type { IComfyApp } from './appInterface'
|
||||
|
||||
import type {
|
||||
BottomPanelExtension,
|
||||
@@ -27,6 +27,7 @@ import type {
|
||||
export type { ComfyExtension } from './comfy'
|
||||
export type { ComfyApi } from '@/scripts/api'
|
||||
export type { ComfyApp } from '@/scripts/app'
|
||||
export type { IComfyApp } from './appInterface'
|
||||
export type { ComfyNodeDef } from '@/schemas/nodeDefSchema'
|
||||
export type { InputSpec } from '@/schemas/nodeDefSchema'
|
||||
export type {
|
||||
@@ -78,7 +79,7 @@ interface AppReadiness {
|
||||
declare global {
|
||||
interface Window {
|
||||
/** For use by extensions and in the browser console. Where possible, import `app` from '@/scripts/app' instead. */
|
||||
app?: ComfyApp
|
||||
app?: IComfyApp
|
||||
|
||||
/** For use by extensions and in the browser console. Where possible, import `app` and access via `app.graph` instead. */
|
||||
graph?: unknown
|
||||
|
||||
@@ -46,9 +46,11 @@ vi.mock('@/stores/workspace/colorPaletteStore', () => ({
|
||||
}))
|
||||
}))
|
||||
|
||||
vi.mock('@vueuse/core', async () => {
|
||||
vi.mock('@vueuse/core', async (importOriginal) => {
|
||||
const actual = await importOriginal()
|
||||
const { ref } = await import('vue')
|
||||
return {
|
||||
...(actual as object),
|
||||
whenever: vi.fn(),
|
||||
useStorage: vi.fn((_key, defaultValue) => {
|
||||
return ref(defaultValue)
|
||||
|
||||
@@ -33,7 +33,7 @@ function createImportFailedDialog() {
|
||||
onClose?: () => void
|
||||
) => {
|
||||
if (conflictedPackages && conflictedPackages.length > 0) {
|
||||
showImportFailedNodeDialog({
|
||||
void showImportFailedNodeDialog({
|
||||
conflictedPackages,
|
||||
dialogComponentProps: {
|
||||
onClose
|
||||
|
||||
@@ -153,7 +153,7 @@ export function useManagerState() {
|
||||
|
||||
switch (state) {
|
||||
case ManagerUIState.DISABLED:
|
||||
dialogService.showSettingsDialog('extension')
|
||||
void dialogService.showSettingsDialog('extension')
|
||||
break
|
||||
|
||||
case ManagerUIState.LEGACY_UI: {
|
||||
@@ -173,7 +173,7 @@ export function useManagerState() {
|
||||
}
|
||||
// Fallback to extensions panel if not showing toast
|
||||
if (options?.showToastOnLegacyError === false) {
|
||||
dialogService.showSettingsDialog('extension')
|
||||
void dialogService.showSettingsDialog('extension')
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
Reference in New Issue
Block a user