Compare commits

..

22 Commits

Author SHA1 Message Date
snomiao
aa2fa05887 Add dependency-cruiser configuration and graph
- Add dependency-cruiser config for analyzing dependencies
- Generate dependency graph visualization
- Update package dependencies

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-15 09:43:02 +00:00
snomiao
883400ec77 fix-vue-tsc 2025-09-15 08:15:48 +00:00
snomiao
5d082b7f37 refactor litegraph to solve circular dep 2025-09-15 08:01:39 +00:00
snomiao
768e49bfdd Merge branch 'main' into sno-fix-playwright-babel-config 2025-09-15 06:58:58 +00:00
snomiao
5b034a97bf fix(.gitignore): update core dump entry to remove leading dot for proper ignoring 2025-09-13 17:15:52 +00:00
snomiao
a9a17cc5c8 fix(api.ts): correct globalThis reference for location to ensure compatibility across environments 2025-09-13 17:15:34 +00:00
snomiao
40a13ea1cf fix(api.ts): correct globalThis reference for location to ensure compatibility across environments 2025-09-13 17:10:37 +00:00
snomiao
414935594c chore: update import statements to use 'type' for type-only imports for better clarity and performance
refactor(collect-i18n-node-defs): clean up comments for clarity on API endpoint behavior
2025-09-13 14:41:40 +00:00
snomiao
7d28bb6919 [refactor] Add type imports across codebase for verbatim module syntax
- Update all Vue components to use type imports
- Update all TypeScript files to use type imports
- Update test files to use type imports
- Ensure compliance with TypeScript verbatim module syntax

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-13 14:12:33 +00:00
snomiao
84fe17e7da [fix] Enable verbatim module syntax in TypeScript configuration 2025-09-13 13:18:04 +00:00
snomiao
f4c157d3c8 restore 2025-09-13 11:49:30 +00:00
snomiao
76bd481666 Merge branch 'sno-fix-playwright-babel-config' of github.com:Comfy-Org/ComfyUI_frontend into sno-fix-playwright-babel-config 2025-09-13 10:52:27 +00:00
snomiao
74af367810 no-mock-object_info 2025-09-13 10:46:55 +00:00
GitHub Action
35e56a441c [auto-fix] Apply ESLint and Prettier fixes 2025-09-13 10:27:34 +00:00
snomiao
0ae6e26052 [fix] Update Playwright and knip configurations
- Fix ComfyPage imports and exports in browser tests
- Update knip config to properly handle Playwright fixtures

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-13 09:47:58 +00:00
snomiao
7306c6f04f fix: Improve i18n collection script setup
- Move setup-browser-globals import to top to fix location undefined error
- Skip user setup for i18n tests to avoid duplicate user conflicts
- Run i18n tests serially with workers=1 to prevent race conditions
- Add unique test-based usernames for better test isolation
2025-09-13 09:24:20 +00:00
snomiao
ead43312f8 feat: Add Happy DOM location mocking for Playwright tests
- Enhanced setup-browser-globals.js with configurable URL and full location mock
- Created LocationMock helper class for dynamic location mocking in tests
- Integrated LocationMock into ComfyPage fixture with optional setup
- Added example test file demonstrating location mock usage
- Support for mocking location.assign, location.replace, and location.reload methods
2025-09-13 09:14:55 +00:00
snomiao
29d22454f4 Merge branch 'main' into sno-fix-playwright-babel-config 2025-09-13 18:09:53 +09:00
snomiao
f697717b4d feat(collect-i18n-node-defs): add mock for object_info API endpoint to allow script execution without backend
fix(collect-i18n-node-defs): create empty locale files if no node definitions are collected to prevent build failures
2025-09-13 07:48:52 +00:00
snomiao
4ea2987502 fix: Convert babel plugin to CommonJS format for Playwright compatibility
- Renamed babel-plugin-stub-vue-imports.js to .cjs extension
- Changed from ES module exports to CommonJS module.exports
- Updated playwright.i18n.config.ts to use correct file path with __dirname
- Added import.meta.url handling for ES module compatibility

This fixes the module resolution errors when running pnpm collect-i18n
2025-09-12 08:47:43 +00:00
snomiao
6c5f17a0aa chore(i18n.yaml): update job condition to include sno-fix-playwright- branches for locale updates 2025-09-12 08:26:50 +00:00
snomiao
540b6f3d26 fix: Add babel configuration for Playwright to handle TypeScript declare fields
- Configure babel plugins for TypeScript with allowDeclareFields option
- Add module resolver for @ alias to src directory
- Create custom babel plugin to stub Vue/CSS imports
- Add browser globals polyfill using happy-dom for Node.js context
- Update playwright.i18n.config.ts with babel configuration

This enables collect-i18n tests to run with proper TypeScript and module transformations.
2025-09-12 08:16:37 +00:00
512 changed files with 48505 additions and 9671 deletions

395
.dependency-cruiser.cjs Normal file
View File

@@ -0,0 +1,395 @@
/** @type {import('dependency-cruiser').IConfiguration} */
module.exports = {
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 pretty common to have a type import be a type only import
// _and_ (e.g.) a devDependency - don't consider type-only dependency
// types 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 sole 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: '^(src)',
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're using 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']
},
/* Which modules to exclude */
// exclude : {
// /* path: an array of regular expressions in strings to match against */
// path: '',
// },
/* Which modules to exclusively include (array of regular expressions in strings)
dependency-cruiser will skip everything not matching this pattern
*/
// includeOnly : [''],
/* List of module systems to cruise.
When left out dependency-cruiser will fall back to the list of _all_
module systems it knows of. It's the default because it's the safe option
It might come at a performance penalty, though.
moduleSystems: ['amd', 'cjs', 'es6', 'tsd']
As in practice only commonjs ('cjs') and ecmascript modules ('es6')
are widely used, you can limit the moduleSystems to those.
*/
// moduleSystems: ['cjs', 'es6'],
/*
false: don't look at JSDoc imports (the default)
true: dependency-cruiser will detect dependencies in JSDoc-style
import statements. Implies "parser": "tsc", so the dependency-cruiser
will use the typescript parser for JavaScript files.
For this to work the typescript compiler will need to be installed in the
same spot as you're running dependency-cruiser from.
*/
// detectJSDocImports: true,
/* prefix for links in html and svg output (e.g. 'https://github.com/you/yourrepo/blob/main/'
to open it on your online repo or `vscode://file/${process.cwd()}/` to
open it in visual studio code),
*/
// prefix: `vscode://file/${process.cwd()}/`,
/* 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,
/* list of extensions to scan that aren't javascript or compile-to-javascript.
Empty by default. Only put extensions in here that you want to take into
account that are _not_ parsable.
*/
// extraExtensionsToScan: [".json", ".jpg", ".png", ".svg", ".webp"],
/* 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: false,
/* if true leave symlinks untouched, otherwise use the realpath */
// preserveSymlinks: false,
/* TypeScript project file ('tsconfig.json') to use for
(1) compilation and
(2) resolution (e.g. with the paths property)
The (optional) fileName attribute specifies which file to take (relative to
dependency-cruiser's current working directory). When not provided
defaults to './tsconfig.json'.
*/
tsConfig: {
fileName: 'tsconfig.json'
},
/* Webpack configuration to use to get resolve options from.
The (optional) fileName attribute specifies which file to take (relative
to dependency-cruiser's current working directory. When not provided defaults
to './webpack.conf.js'.
The (optional) `env` and `arguments` attributes contain the parameters
to be passed if your webpack config is a function and takes them (see
webpack documentation for details)
*/
// webpackConfig: {
// fileName: 'webpack.config.js',
// env: {},
// arguments: {}
// },
/* Babel config ('.babelrc', '.babelrc.json', '.babelrc.json5', ...) to use
for compilation
*/
// babelConfig: {
// fileName: '.babelrc',
// },
/* List of strings you have in use in addition to cjs/ es6 requires
& imports to declare module dependencies. Use this e.g. if you've
re-declared require, use a require-wrapper or use window.require as
a hack.
*/
// exoticRequireStrings: [],
/* options to pass on to enhanced-resolve, the package dependency-cruiser
uses to resolve module references to disk. The values below should be
suitable for most situations
If you use webpack: you can also set these in webpack.conf.js. The set
there will override the ones specified here.
*/
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"],
/* The extensions, by default are the same as the ones dependency-cruiser
can access (run `npx depcruise --info` to see which ones that are in
_your_ environment). If that list is larger than you need you can pass
the extensions you actually use (e.g. [".js", ".jsx"]). This can speed
up module resolution, which is the most expensive step.
*/
// extensions: [".js", ".jsx", ".ts", ".tsx", ".d.ts"],
/* What to consider a 'main' field in package.json */
mainFields: ["module", "main", "types", "typings"],
/* A list of alias fields in package.jsons
See [this specification](https://github.com/defunctzombie/package-browser-field-spec) and
the webpack [resolve.alias](https://webpack.js.org/configuration/resolve/#resolvealiasfields)
documentation.
Defaults to an empty array (= don't use alias fields).
*/
// aliasFields: ["browser"],
},
/* skipAnalysisNotInRules will make dependency-cruiser execute
analysis strictly necessary for checking the rule set only.
See https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#skipanalysisnotinrules
for details
*/
skipAnalysisNotInRules: true,
reporterOptions: {
dot: {
/* pattern of modules that can be consolidated in the detailed
graphical dependency graph. The default pattern in this configuration
collapses everything in node_modules to one folder deep so you see
the external modules, but their innards.
*/
collapsePattern: 'node_modules/(?:@[^/]+/[^/]+|[^/]+)',
/* Options to tweak the appearance of your graph.See
https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#reporteroptions
for details and some examples. If you don't specify a theme
dependency-cruiser falls back to a built-in one.
*/
// theme: {
// graph: {
// /* splines: "ortho" gives straight lines, but is slow on big graphs
// splines: "true" gives bezier curves (fast, not as nice as ortho)
// */
// splines: "true"
// },
// }
},
archi: {
/* pattern of modules that can be consolidated in the high level
graphical dependency graph. If you use the high level graphical
dependency graph reporter (`archi`) you probably want to tweak
this collapsePattern to your situation.
*/
collapsePattern: '^(?:packages|src|lib(s?)|app(s?)|bin|test(s?)|spec(s?))/[^/]+|node_modules/(?:@[^/]+/[^/]+|[^/]+)',
/* Options to tweak the appearance of your graph. If you don't specify a
theme for 'archi' dependency-cruiser will use the one specified in the
dot section above and otherwise use the default one.
*/
// theme: { },
},
"text": {
"highlightFocused": true
},
}
}
};
// generated: dependency-cruiser@17.0.1 on 2025-09-15T09:33:27.618Z

View File

@@ -7,21 +7,3 @@ c53f197de2a3e0fa66b16dedc65c131235c1c4b6
# Reorganize renderer components into domain-driven folder structure
c8a83a9caede7bdb5f8598c5492b07d08c339d49
# Domain-driven design (DDD) refactors - September 2025
# These commits reorganized the codebase into domain-driven architecture
# [refactor] Improve renderer domain organization (#5552)
6349ceee6c0a57fc7992e85635def9b6e22eaeb2
# [refactor] Improve settings domain organization (#5550)
4c8c4a1ad4f53354f700a33ea1b95262aeda2719
# [refactor] Improve workflow domain organization (#5584)
ca312fd1eab540cc4ddc0e3d244d38b3858574f0
# [refactor] Move thumbnail functionality to renderer/core domain (#5586)
e3bb29ceb8174b8bbca9e48ec7d42cd540f40efa
# [refactor] Improve updates/notifications domain organization (#5590)
27ab355f9c73415dc39f4d3f512b02308f847801

View File

@@ -7,11 +7,10 @@ on:
pull_request:
branches: [ main ]
types: [opened, synchronize, reopened]
jobs:
update-locales:
# Branch detection: Only run for manual dispatch or version-bump-* branches from main repo
if: github.event_name == 'workflow_dispatch' || (github.event.pull_request.head.repo.full_name == github.repository && startsWith(github.head_ref, 'version-bump-'))
if: github.event_name == 'workflow_dispatch' || (github.event.pull_request.head.repo.full_name == github.repository && (startsWith(github.head_ref, 'version-bump-') || startsWith(github.head_ref, 'sno-fix-playwright-')))
runs-on: ubuntu-latest
steps:
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v3

View File

@@ -1,139 +0,0 @@
name: Publish Frontend Types
on:
workflow_dispatch:
inputs:
version:
description: 'Version to publish (e.g., 1.26.7)'
required: true
type: string
dist_tag:
description: 'npm dist-tag to use'
required: true
default: latest
type: string
ref:
description: 'Git ref to checkout (commit SHA, tag, or branch)'
required: false
type: string
workflow_call:
inputs:
version:
required: true
type: string
dist_tag:
required: false
type: string
default: latest
ref:
required: false
type: string
concurrency:
group: publish-frontend-types-${{ github.workflow }}-${{ inputs.version }}-${{ inputs.dist_tag }}
cancel-in-progress: false
jobs:
publish_types_manual:
name: Publish @comfyorg/comfyui-frontend-types
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Validate inputs
shell: bash
run: |
set -euo pipefail
VERSION="${{ inputs.version }}"
SEMVER_REGEX='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(\.(0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*))?(\+([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$'
if [[ ! "$VERSION" =~ $SEMVER_REGEX ]]; then
echo "::error title=Invalid version::Version '$VERSION' must follow semantic versioning (x.y.z[-suffix][+build])" >&2
exit 1
fi
- name: Determine ref to checkout
id: resolve_ref
shell: bash
run: |
set -euo pipefail
REF="${{ inputs.ref }}"
VERSION="${{ inputs.version }}"
if [ -n "$REF" ]; then
if ! git check-ref-format --allow-onelevel "$REF"; then
echo "::error title=Invalid ref::Ref '$REF' fails git check-ref-format validation." >&2
exit 1
fi
echo "ref=$REF" >> "$GITHUB_OUTPUT"
else
echo "ref=refs/tags/v$VERSION" >> "$GITHUB_OUTPUT"
fi
- name: Checkout repository
uses: actions/checkout@v5
with:
ref: ${{ steps.resolve_ref.outputs.ref }}
fetch-depth: 1
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: 'lts/*'
cache: 'pnpm'
registry-url: https://registry.npmjs.org
- name: Install dependencies
run: pnpm install --frozen-lockfile
env:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
- name: Build types
run: pnpm build:types
- name: Verify version matches input
id: verify
shell: bash
run: |
PKG_VERSION=$(node -p "require('./package.json').version")
TYPES_PKG_VERSION=$(node -p "require('./dist/package.json').version")
if [ "$PKG_VERSION" != "${{ inputs.version }}" ]; then
echo "Error: package.json version $PKG_VERSION does not match input ${{ inputs.version }}" >&2
exit 1
fi
if [ "$TYPES_PKG_VERSION" != "${{ inputs.version }}" ]; then
echo "Error: dist/package.json version $TYPES_PKG_VERSION does not match input ${{ inputs.version }}" >&2
exit 1
fi
echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT
- name: Check if version already on npm
id: check_npm
shell: bash
run: |
set -euo pipefail
NAME=$(node -p "require('./dist/package.json').name")
VER="${{ steps.verify.outputs.version }}"
STATUS=0
OUTPUT=$(npm view "${NAME}@${VER}" --json 2>&1) || STATUS=$?
if [ "$STATUS" -eq 0 ]; then
echo "exists=true" >> "$GITHUB_OUTPUT"
echo "::warning title=Already published::${NAME}@${VER} already exists on npm. Skipping publish."
else
if echo "$OUTPUT" | grep -q "E404"; then
echo "exists=false" >> "$GITHUB_OUTPUT"
else
echo "::error title=Registry lookup failed::$OUTPUT" >&2
exit "$STATUS"
fi
fi
- name: Publish package
if: steps.check_npm.outputs.exists == 'false'
run: pnpm publish --access public --tag "${{ inputs.dist_tag }}" --no-git-checks
working-directory: dist
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -18,7 +18,7 @@ jobs:
is_prerelease: ${{ steps.check_prerelease.outputs.is_prerelease }}
steps:
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
@@ -73,7 +73,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Download dist artifact
uses: actions/download-artifact@v4
with:
@@ -98,7 +98,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Download dist artifact
uses: actions/download-artifact@v4
with:
@@ -126,8 +126,34 @@ jobs:
publish_types:
needs: build
uses: ./.github/workflows/publish-frontend-types.yaml
with:
version: ${{ needs.build.outputs.version }}
ref: ${{ github.event.pull_request.merge_commit_sha }}
secrets: inherit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- uses: actions/setup-node@v4
with:
node-version: 'lts/*'
cache: 'pnpm'
registry-url: https://registry.npmjs.org
- name: Cache tool outputs
uses: actions/cache@v4
with:
path: |
.cache
tsconfig.tsbuildinfo
dist
key: types-tools-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
types-tools-cache-${{ runner.os }}-
- run: pnpm install --frozen-lockfile
- run: pnpm build:types
- name: Publish package
run: pnpm publish --access public
working-directory: dist
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

4
.gitignore vendored
View File

@@ -51,7 +51,7 @@ tests-ui/workflows/examples
/blob-report/
/playwright/.cache/
browser_tests/**/*-win32.png
browser_tests/local/
browser-tests/local/
.env
@@ -73,7 +73,7 @@ templates_repo/
vite.config.mts.timestamp-*.mjs
# Linux core dumps
./core
core
*storybook.log
storybook-static

View File

@@ -0,0 +1,32 @@
module.exports = function(babel) {
const { types: t } = babel;
return {
visitor: {
ImportDeclaration(path) {
const source = path.node.source.value;
// Handle Vue files
if (source.endsWith('.vue')) {
const specifiers = path.node.specifiers;
if (specifiers.length > 0 && specifiers[0].type === 'ImportDefaultSpecifier') {
const name = specifiers[0].local.name;
// Replace with a variable declaration
path.replaceWith(
t.variableDeclaration('const', [
t.variableDeclarator(
t.identifier(name),
t.objectExpression([])
)
])
);
}
}
// Handle CSS files - just remove the import
else if (source.endsWith('.css') || source.endsWith('.scss') || source.endsWith('.less')) {
path.remove();
}
}
}
};
};

24
babel.config.json Normal file
View File

@@ -0,0 +1,24 @@
{
"presets": [
"@babel/preset-env",
[
"@babel/preset-typescript",
{
"allowDeclareFields": true,
"onlyRemoveTypeImports": true
}
]
],
"plugins": [
[
"babel-plugin-module-resolver",
{
"root": ["./"],
"alias": {
"@": "./src"
}
}
],
"@vue/babel-plugin-jsx"
]
}

View File

@@ -5,11 +5,12 @@ import dotenv from 'dotenv'
import * as fs from 'fs'
import type { LGraphNode } from '../../src/lib/litegraph/src/litegraph'
import type { NodeId } from '../../src/platform/workflow/validation/schemas/workflowSchema'
import type { NodeId } from '../../src/schemas/comfyWorkflowSchema'
import type { KeyCombo } from '../../src/schemas/keyBindingSchema'
import type { useWorkspaceStore } from '../../src/stores/workspaceStore'
import { NodeBadgeMode } from '../../src/types/nodeSource'
import { ComfyActionbar } from '../helpers/actionbar'
import { LocationMock } from '../helpers/locationMock'
import { ComfyTemplates } from '../helpers/templates'
import { ComfyMouse } from './ComfyMouse'
import { VueNodeHelpers } from './VueNodeHelpers'
@@ -145,6 +146,7 @@ export class ComfyPage {
public readonly templates: ComfyTemplates
public readonly settingDialog: SettingDialog
public readonly confirmDialog: ConfirmDialog
public readonly locationMock: LocationMock
public readonly vueNodes: VueNodeHelpers
/** Worker index to test user ID */
@@ -174,6 +176,7 @@ export class ComfyPage {
this.templates = new ComfyTemplates(page)
this.settingDialog = new SettingDialog(page, this)
this.confirmDialog = new ConfirmDialog(page)
this.locationMock = new LocationMock(page)
this.vueNodes = new VueNodeHelpers(page)
}
@@ -275,11 +278,20 @@ export class ComfyPage {
async setup({
clearStorage = true,
mockReleases = true
mockReleases = true,
mockLocation = false
}: {
clearStorage?: boolean
mockReleases?: boolean
mockLocation?: boolean | Parameters<LocationMock['setupLocationMock']>[0]
} = {}) {
// Setup location mock if requested
if (mockLocation) {
const config =
typeof mockLocation === 'boolean' ? undefined : mockLocation
await this.locationMock.setupLocationMock(config)
}
await this.goto()
// Mock release endpoint to prevent changelog popups
@@ -1636,28 +1648,39 @@ export const comfyPageFixture = base.extend<{
comfyPage: async ({ page, request }, use, testInfo) => {
const comfyPage = new ComfyPage(page, request)
const { parallelIndex } = testInfo
const username = `playwright-test-${parallelIndex}`
const userId = await comfyPage.setupUser(username)
comfyPage.userIds[parallelIndex] = userId
const { parallelIndex, workerIndex, title } = testInfo
// Skip user setup for i18n collection tests
const isI18nTest = testInfo.file?.includes('collect-i18n')
let userId: string | undefined
if (!isI18nTest) {
// Use a combination of workerIndex and test title hash for unique usernames
const testHash = title.replace(/[^a-zA-Z0-9]/g, '').substring(0, 8)
const username = `playwright-test-${workerIndex}-${testHash}`
userId = await comfyPage.setupUser(username)
comfyPage.userIds[parallelIndex] = userId!
}
try {
await comfyPage.setupSettings({
'Comfy.UseNewMenu': 'Disabled',
// Hide canvas menu/info/selection toolbox by default.
'Comfy.Graph.CanvasInfo': false,
'Comfy.Graph.CanvasMenu': false,
'Comfy.Canvas.SelectionToolbox': false,
// Hide all badges by default.
'Comfy.NodeBadge.NodeIdBadgeMode': NodeBadgeMode.None,
'Comfy.NodeBadge.NodeSourceBadgeMode': NodeBadgeMode.None,
// Disable tooltips by default to avoid flakiness.
'Comfy.EnableTooltips': false,
'Comfy.userId': userId,
// Set tutorial completed to true to avoid loading the tutorial workflow.
'Comfy.TutorialCompleted': true,
'Comfy.SnapToGrid.GridSize': testComfySnapToGridGridSize
})
if (!isI18nTest && userId) {
await comfyPage.setupSettings({
'Comfy.UseNewMenu': 'Disabled',
// Hide canvas menu/info/selection toolbox by default.
'Comfy.Graph.CanvasInfo': false,
'Comfy.Graph.CanvasMenu': false,
'Comfy.Canvas.SelectionToolbox': false,
// Hide all badges by default.
'Comfy.NodeBadge.NodeIdBadgeMode': NodeBadgeMode.None,
'Comfy.NodeBadge.NodeSourceBadgeMode': NodeBadgeMode.None,
// Disable tooltips by default to avoid flakiness.
'Comfy.EnableTooltips': false,
'Comfy.userId': userId,
// Set tutorial completed to true to avoid loading the tutorial workflow.
'Comfy.TutorialCompleted': true,
'Comfy.SnapToGrid.GridSize': testComfySnapToGridGridSize
})
}
} catch (e) {
console.error(e)
}
@@ -1676,6 +1699,7 @@ const makeMatcher = function <T>(
type: string
) {
return async function (
this: any,
node: NodeReference,
options?: { timeout?: number; intervals?: number[] }
) {

View File

@@ -1,6 +1,6 @@
import type { Page } from '@playwright/test'
import type { NodeId } from '../../../src/platform/workflow/validation/schemas/workflowSchema'
import type { NodeId } from '../../../src/schemas/comfyWorkflowSchema'
import { ManageGroupNode } from '../../helpers/manageGroupNode'
import type { ComfyPage } from '../ComfyPage'
import type { Position, Size } from '../types'

View File

@@ -0,0 +1,144 @@
import type { Page } from '@playwright/test'
/**
* Mock location object for testing navigation and URL manipulation
*/
export class LocationMock {
constructor(private page: Page) {}
/**
* Mock the location object in the page context
* @param mockConfig Configuration for the mock location
*/
async setupLocationMock(mockConfig?: {
href?: string
origin?: string
pathname?: string
search?: string
hash?: string
hostname?: string
port?: string
protocol?: string
}) {
await this.page.addInitScript((config) => {
const defaultUrl = config?.href || window.location.href
const url = new URL(defaultUrl)
// Create a mock location object
const mockLocation = {
href: config?.href || url.href,
origin: config?.origin || url.origin,
protocol: config?.protocol || url.protocol,
host: url.host,
hostname: config?.hostname || url.hostname,
port: config?.port || url.port,
pathname: config?.pathname || url.pathname,
search: config?.search || url.search,
hash: config?.hash || url.hash,
assign: (newUrl: string) => {
console.log(`[Mock] location.assign called with: ${newUrl}`)
mockLocation.href = newUrl
// Trigger navigation event if needed
window.dispatchEvent(new Event('popstate'))
},
replace: (newUrl: string) => {
console.log(`[Mock] location.replace called with: ${newUrl}`)
mockLocation.href = newUrl
// Trigger navigation event if needed
window.dispatchEvent(new Event('popstate'))
},
reload: () => {
console.log('[Mock] location.reload called')
// Trigger reload event if needed
window.dispatchEvent(new Event('beforeunload'))
},
toString: () => mockLocation.href
}
// Override window.location
Object.defineProperty(window, 'location', {
value: mockLocation,
writable: true,
configurable: true
})
// Also override document.location
Object.defineProperty(document, 'location', {
value: mockLocation,
writable: true,
configurable: true
})
}, mockConfig)
}
/**
* Update the mock location during test execution
*/
async updateLocation(
updates: Partial<{
href: string
pathname: string
search: string
hash: string
}>
) {
await this.page.evaluate((updates) => {
const location = window.location as any
Object.keys(updates).forEach((key) => {
if (location[key] !== undefined) {
location[key] = updates[key as keyof typeof updates]
}
})
}, updates)
}
/**
* Get the current mock location values
*/
async getLocation() {
return await this.page.evaluate(() => {
const loc = window.location
return {
href: loc.href,
origin: loc.origin,
protocol: loc.protocol,
host: loc.host,
hostname: loc.hostname,
port: loc.port,
pathname: loc.pathname,
search: loc.search,
hash: loc.hash
}
})
}
/**
* Simulate navigation to a new URL
*/
async navigateTo(url: string) {
await this.page.evaluate((url) => {
const location = window.location as any
location.assign(url)
}, url)
}
/**
* Simulate location.replace
*/
async replaceTo(url: string) {
await this.page.evaluate((url) => {
const location = window.location as any
location.replace(url)
}, url)
}
/**
* Simulate location.reload
*/
async reload() {
await this.page.evaluate(() => {
const location = window.location as any
location.reload()
})
}
}

View File

@@ -4,7 +4,7 @@ import path from 'path'
import {
TemplateInfo,
WorkflowTemplates
} from '../../src/platform/workflow/templates/types/template'
} from '../../src/types/workflowTemplateTypes'
export class ComfyTemplates {
readonly content: Locator

View File

@@ -0,0 +1,103 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from './fixtures/ComfyPage'
import { LocationMock } from './helpers/locationMock'
test.describe('Location Mock Example', () => {
test('should mock location object', async ({ page, comfyPage }) => {
const locationMock = new LocationMock(page)
// Setup location mock before navigating to the page
await locationMock.setupLocationMock({
href: 'http://example.com/test',
pathname: '/test',
search: '?query=value',
hash: '#section'
})
// Navigate to your app
await comfyPage.goto()
// Verify the mock is working
const location = await locationMock.getLocation()
expect(location.pathname).toBe('/test')
expect(location.search).toBe('?query=value')
expect(location.hash).toBe('#section')
// Test navigation
await locationMock.navigateTo('http://example.com/new-page')
const newLocation = await locationMock.getLocation()
expect(newLocation.href).toBe('http://example.com/new-page')
// Test updating specific properties
await locationMock.updateLocation({
pathname: '/updated-path',
search: '?new=param'
})
const updatedLocation = await locationMock.getLocation()
expect(updatedLocation.pathname).toBe('/updated-path')
expect(updatedLocation.search).toBe('?new=param')
})
test('should handle location methods', async ({ page, comfyPage }) => {
const locationMock = new LocationMock(page)
await locationMock.setupLocationMock({
href: 'http://localhost:5173/'
})
await comfyPage.goto()
// Test location.assign
await page.evaluate(() => {
window.location.assign('/new-route')
})
// Check console for mock output
const consoleMessages: string[] = []
page.on('console', (msg) => {
if (msg.text().includes('[Mock]')) {
consoleMessages.push(msg.text())
}
})
await locationMock.navigateTo('/another-route')
await locationMock.replaceTo('/replaced-route')
await locationMock.reload()
// Verify mock methods were called
expect(
consoleMessages.some((msg) => msg.includes('location.assign'))
).toBeTruthy()
expect(
consoleMessages.some((msg) => msg.includes('location.replace'))
).toBeTruthy()
expect(
consoleMessages.some((msg) => msg.includes('location.reload'))
).toBeTruthy()
})
test('should work with Happy DOM globals', async ({ page, comfyPage }) => {
// Set environment variable for Happy DOM URL
process.env.HAPPY_DOM_URL = 'http://custom-domain.com/'
const locationMock = new LocationMock(page)
await locationMock.setupLocationMock()
await comfyPage.goto()
// Verify location is mocked correctly
const location = await page.evaluate(() => ({
href: window.location.href,
origin: window.location.origin,
canAssign: typeof window.location.assign === 'function',
canReplace: typeof window.location.replace === 'function',
canReload: typeof window.location.reload === 'function'
}))
expect(location.canAssign).toBeTruthy()
expect(location.canReplace).toBeTruthy()
expect(location.canReload).toBeTruthy()
})
})

View File

@@ -1,6 +1,6 @@
import { expect } from '@playwright/test'
import { SettingParams } from '../../src/platform/settings/types'
import { SettingParams } from '../../src/types/settingTypes'
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
test.describe('Topbar commands', () => {
@@ -247,7 +247,7 @@ test.describe('Topbar commands', () => {
test.describe('Dialog', () => {
test('Should allow showing a prompt dialog', async ({ comfyPage }) => {
await comfyPage.page.evaluate(() => {
void window['app'].extensionManager.dialog
window['app'].extensionManager.dialog
.prompt({
title: 'Test Prompt',
message: 'Test Prompt Message'
@@ -267,7 +267,7 @@ test.describe('Topbar commands', () => {
comfyPage
}) => {
await comfyPage.page.evaluate(() => {
void window['app'].extensionManager.dialog
window['app'].extensionManager.dialog
.confirm({
title: 'Test Confirm',
message: 'Test Confirm Message'
@@ -284,7 +284,7 @@ test.describe('Topbar commands', () => {
test('Should allow dismissing a dialog', async ({ comfyPage }) => {
await comfyPage.page.evaluate(() => {
window['value'] = 'foo'
void window['app'].extensionManager.dialog
window['app'].extensionManager.dialog
.confirm({
title: 'Test Confirm',
message: 'Test Confirm Message'

View File

@@ -1012,8 +1012,6 @@ test.describe('Canvas Navigation', () => {
test('Shift + mouse wheel should pan canvas horizontally', async ({
comfyPage
}) => {
await comfyPage.setSetting('Comfy.Canvas.MouseWheelScroll', 'panning')
await comfyPage.page.click('canvas')
await comfyPage.nextFrame()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 98 KiB

View File

@@ -1,5 +1,5 @@
import path from 'path'
import { Plugin } from 'vite'
import { type Plugin } from 'vite'
interface ShimResult {
code: string

View File

@@ -1,7 +1,7 @@
import glob from 'fast-glob'
import fs from 'fs-extra'
import { dirname, join } from 'node:path'
import { HtmlTagDescriptor, Plugin, normalizePath } from 'vite'
import { type HtmlTagDescriptor, type Plugin, normalizePath } from 'vite'
interface ImportMapSource {
name: string

39351
dependency-graph.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 4.1 MiB

View File

@@ -5,14 +5,13 @@ import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
import storybook from 'eslint-plugin-storybook'
import unusedImports from 'eslint-plugin-unused-imports'
import pluginVue from 'eslint-plugin-vue'
import { defineConfig } from 'eslint/config'
import globals from 'globals'
import tseslint from 'typescript-eslint'
import vueParser from 'vue-eslint-parser'
const extraFileExtensions = ['.vue']
export default defineConfig([
export default [
{
files: ['src/**/*.{js,mjs,cjs,ts,vue}']
},
{
ignores: [
'src/scripts/*',
@@ -25,49 +24,35 @@ export default defineConfig([
]
},
{
files: ['./**/*.{ts,mts}'],
languageOptions: {
globals: {
...globals.browser,
__COMFYUI_FRONTEND_VERSION__: 'readonly'
},
parser: tseslint.parser,
parserOptions: {
parser: tseslint.parser,
projectService: true,
tsConfigRootDir: import.meta.dirname,
project: ['./tsconfig.json', './tsconfig.eslint.json'],
ecmaVersion: 2020,
sourceType: 'module',
extraFileExtensions
}
}
},
{
files: ['./**/*.vue'],
languageOptions: {
globals: {
...globals.browser,
__COMFYUI_FRONTEND_VERSION__: 'readonly'
},
parser: vueParser,
parserOptions: {
parser: tseslint.parser,
projectService: true,
tsConfigRootDir: import.meta.dirname,
ecmaVersion: 2020,
sourceType: 'module',
extraFileExtensions
extraFileExtensions: ['.vue']
}
}
},
pluginJs.configs.recommended,
tseslint.configs.recommended,
pluginVue.configs['flat/recommended'],
...tseslint.configs.recommended,
...pluginVue.configs['flat/recommended'],
eslintPluginPrettierRecommended,
storybook.configs['flat/recommended'],
{
files: ['src/**/*.vue'],
languageOptions: {
parserOptions: {
parser: tseslint.parser
}
}
},
{
plugins: {
'unused-imports': unusedImports,
// @ts-expect-error Bad types in the plugin
'@intlify/vue-i18n': pluginI18n
},
rules: {
@@ -75,8 +60,6 @@ export default defineConfig([
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/prefer-as-const': 'off',
'@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/no-import-type-side-effects': 'error',
'unused-imports/no-unused-imports': 'error',
'vue/no-v-html': 'off',
// Enforce dark-theme: instead of dark: prefix
@@ -84,7 +67,6 @@ export default defineConfig([
'vue/multi-word-component-names': 'off', // TODO: fix
'vue/no-template-shadow': 'off', // TODO: fix
'vue/one-component-per-file': 'off', // TODO: fix
'vue/require-default-prop': 'off', // TODO: fix -- this one is very worthwhile
// Restrict deprecated PrimeVue components
'no-restricted-imports': [
'error',
@@ -153,5 +135,6 @@ export default defineConfig([
}
]
}
}
])
},
...storybook.configs['flat/recommended']
]

View File

@@ -18,17 +18,23 @@ const config: KnipConfig = {
'@primeuix/utils',
'@primevue/icons',
// Dev
'@trivago/prettier-plugin-sort-imports'
'@trivago/prettier-plugin-sort-imports',
// Babel dependencies used in playwright.i18n.config.ts
'@babel/plugin-transform-modules-commonjs',
'@babel/plugin-transform-typescript',
'babel-helper-vue-jsx-merge-props',
'babel-plugin-syntax-jsx',
'babel-plugin-transform-import-ignore',
'babel-plugin-transform-vue-jsx',
'babel-preset-env',
'babel-preset-typescript-vue3'
],
ignore: [
// Auto generated manager types
'src/types/generatedManagerTypes.ts',
'src/types/comfyRegistryTypes.ts',
// Used by a custom node (that should move off of this)
'src/scripts/ui/components/splitButton.ts',
// Staged for for use with subgraph widget promotion
'src/lib/litegraph/src/widgets/DisconnectedWidget.ts',
'src/core/graph/operations/types.ts'
'src/scripts/ui/components/splitButton.ts'
],
compilers: {
// https://github.com/webpro-nl/knip/issues/1008#issuecomment-3207756199

View File

@@ -3,13 +3,13 @@ export default {
'./**/*.{ts,tsx,vue,mts}': (stagedFiles) => [
...formatAndEslint(stagedFiles),
'pnpm typecheck'
'vue-tsc --noEmit'
]
}
function formatAndEslint(fileNames) {
return [
`pnpm exec eslint --cache --fix ${fileNames.join(' ')}`,
`pnpm exec prettier --cache --write ${fileNames.join(' ')}`
`eslint --fix ${fileNames.join(' ')}`,
`prettier --write ${fileNames.join(' ')}`
]
}

View File

@@ -1,7 +1,7 @@
{
"name": "@comfyorg/comfyui-frontend",
"private": true,
"version": "1.27.5",
"version": "1.27.3",
"type": "module",
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
"homepage": "https://comfy.org",
@@ -14,9 +14,9 @@
"build:types": "nx build --config vite.types.config.mts && node scripts/prepare-types.js",
"zipdist": "node scripts/zipdist.js",
"typecheck": "vue-tsc --noEmit",
"format": "prettier --write './**/*.{js,ts,tsx,vue,mts}' --cache --list-different",
"format": "prettier --write './**/*.{js,ts,tsx,vue,mts}' --cache",
"format:check": "prettier --check './**/*.{js,ts,tsx,vue,mts}' --cache",
"format:no-cache": "prettier --write './**/*.{js,ts,tsx,vue,mts}' --list-different",
"format:no-cache": "prettier --write './**/*.{js,ts,tsx,vue,mts}'",
"format:check:no-cache": "prettier --check './**/*.{js,ts,tsx,vue,mts}'",
"test:browser": "npx nx e2e",
"test:unit": "nx run test tests-ui/tests",
@@ -32,16 +32,21 @@
"knip": "knip --cache",
"knip:no-cache": "knip",
"locale": "lobe-i18n locale",
"collect-i18n": "npx playwright test --config=playwright.i18n.config.ts",
"collect-i18n": "playwright test --config=playwright.i18n.config.ts",
"json-schema": "tsx scripts/generate-json-schema.ts",
"storybook": "nx storybook -p 6006",
"build-storybook": "storybook build"
},
"devDependencies": {
"@eslint/js": "^9.35.0",
"@babel/core": "^7.28.4",
"@babel/plugin-transform-modules-commonjs": "^7.27.1",
"@babel/plugin-transform-typescript": "^7.28.0",
"@babel/preset-env": "^7.28.3",
"@babel/preset-typescript": "^7.27.1",
"@eslint/js": "^9.8.0",
"@iconify-json/lucide": "^1.2.66",
"@iconify/tailwind": "^1.2.0",
"@intlify/eslint-plugin-vue-i18n": "^4.1.0",
"@intlify/eslint-plugin-vue-i18n": "^3.2.0",
"@lobehub/i18n-cli": "^1.25.1",
"@nx/eslint": "21.4.1",
"@nx/playwright": "21.4.1",
@@ -62,13 +67,22 @@
"@vitejs/plugin-vue": "^5.1.4",
"@vitest/coverage-v8": "^3.2.4",
"@vitest/ui": "^3.0.0",
"@vue/babel-plugin-jsx": "^1.5.0",
"@vue/test-utils": "^2.4.6",
"babel-helper-vue-jsx-merge-props": "^2.0.3",
"babel-plugin-module-resolver": "^5.0.2",
"babel-plugin-syntax-jsx": "^6.18.0",
"babel-plugin-transform-import-ignore": "^1.1.0",
"babel-plugin-transform-vue-jsx": "^3.7.0",
"babel-preset-env": "^1.7.0",
"babel-preset-typescript-vue3": "^2.1.1",
"dependency-cruiser": "^17.0.1",
"eslint": "^9.34.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-storybook": "^9.1.6",
"eslint-plugin-unused-imports": "^4.2.0",
"eslint-plugin-vue": "^10.4.0",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-prettier": "^5.2.6",
"eslint-plugin-storybook": "^9.1.1",
"eslint-plugin-unused-imports": "^4.1.4",
"eslint-plugin-vue": "^9.27.0",
"fs-extra": "^11.2.0",
"globals": "^15.9.0",
"happy-dom": "^15.11.0",
@@ -79,30 +93,29 @@
"lint-staged": "^15.2.7",
"nx": "21.4.1",
"prettier": "^3.3.2",
"storybook": "^9.1.6",
"storybook": "^9.1.1",
"tailwindcss": "^4.1.12",
"tailwindcss-primeui": "^0.6.1",
"tsx": "^4.15.6",
"tw-animate-css": "^1.3.8",
"typescript": "^5.4.5",
"typescript-eslint": "^8.44.0",
"typescript-eslint": "^8.42.0",
"unplugin-icons": "^0.22.0",
"unplugin-vue-components": "^0.28.0",
"uuid": "^11.1.0",
"vite": "^5.4.19",
"vite-plugin-dts": "^4.5.4",
"vite-plugin-dts": "^4.3.0",
"vite-plugin-html": "^3.2.2",
"vite-plugin-vue-devtools": "^7.7.6",
"vitest": "^3.2.4",
"vue-eslint-parser": "^10.2.0",
"vue-tsc": "^3.0.7",
"vue-tsc": "^2.1.10",
"zip-dir": "^2.0.0",
"zod-to-json-schema": "^3.24.1"
},
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
"@comfyorg/comfyui-electron-types": "0.4.73-0",
"@comfyorg/comfyui-electron-types": "^0.4.72",
"@iconify/json": "^2.2.380",
"@primeuix/forms": "0.0.2",
"@primeuix/styled": "0.3.2",

View File

@@ -1,6 +1,10 @@
import { defineConfig } from '@playwright/test'
import path from 'path'
import { fileURLToPath } from 'url'
export default defineConfig({
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const config: any = defineConfig({
testDir: './scripts',
use: {
baseURL: 'http://localhost:5173',
@@ -8,5 +12,41 @@ export default defineConfig({
},
reporter: 'list',
timeout: 60000,
testMatch: /collect-i18n-.*\.ts/
workers: 1, // Run tests serially to avoid duplicate user creation
testMatch: /collect-i18n-.*\.ts/,
// Start dev server before running tests
webServer: {
command: 'pnpm dev',
url: 'http://localhost:5173',
reuseExistingServer: true,
timeout: 60000
}
})
// Configure babel plugins for TypeScript with declare fields and module resolution
config['@playwright/test'] = {
babelPlugins: [
// Stub Vue and CSS imports first to prevent parsing errors
[path.join(__dirname, 'babel-plugin-stub-vue-imports.cjs')],
// Module resolver to handle @ alias
[
'babel-plugin-module-resolver',
{
root: ['./'],
alias: {
'@': './src'
}
}
],
// Then TypeScript transformation with declare field support
[
'@babel/plugin-transform-typescript',
{
allowDeclareFields: true,
onlyRemoveTypeImports: true
}
]
]
}
export default config

2757
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,9 +3,10 @@ import * as fs from 'fs'
import { comfyPageFixture as test } from '../browser_tests/fixtures/ComfyPage'
import { CORE_MENU_COMMANDS } from '../src/constants/coreMenuCommands'
import { SERVER_CONFIG_ITEMS } from '../src/constants/serverConfig'
import type { FormItem, SettingParams } from '../src/platform/settings/types'
import type { ComfyCommandImpl } from '../src/stores/commandStore'
import type { FormItem, SettingParams } from '../src/types/settingTypes'
import { formatCamelCase, normalizeI18nKey } from '../src/utils/formatUtil'
import './setup-browser-globals.js'
const localePath = './src/locales/en/main.json'
const commandsPath = './src/locales/en/commands.json'
@@ -47,15 +48,28 @@ test('collect-i18n-general', async ({ comfyPage }) => {
Array.from(allLabels).map((label) => [normalizeI18nKey(label), label])
)
const allCommandsLocale = Object.fromEntries(
commands.map((command) => [
// Load existing commands to preserve Desktop commands
const existingCommands = JSON.parse(fs.readFileSync(commandsPath, 'utf-8'))
// Filter out Desktop commands from existing commands
const desktopCommands = Object.fromEntries(
Object.entries(existingCommands).filter(([key]) =>
key.startsWith('Comfy-Desktop')
)
)
const allCommandsLocale = Object.fromEntries([
// Keep Desktop commands that aren't in the current collection
...Object.entries(desktopCommands),
// Add/update commands from current collection
...commands.map((command) => [
normalizeI18nKey(command.id),
{
label: command.label,
tooltip: command.tooltip
}
])
)
])
// Settings
const settings = await comfyPage.page.evaluate(() => {
@@ -78,8 +92,21 @@ test('collect-i18n-general', async ({ comfyPage }) => {
}))
})
const allSettingsLocale = Object.fromEntries(
settings.map((setting) => [
// Load existing settings to preserve Desktop settings
const existingSettings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'))
// Filter out Desktop settings from existing settings
const desktopSettings = Object.fromEntries(
Object.entries(existingSettings).filter(([key]) =>
key.startsWith('Comfy-Desktop')
)
)
const allSettingsLocale = Object.fromEntries([
// Keep Desktop settings that aren't in the current collection
...Object.entries(desktopSettings),
// Add/update settings from current collection
...settings.map((setting) => [
normalizeI18nKey(setting.id),
{
name: setting.name,
@@ -98,7 +125,7 @@ test('collect-i18n-general', async ({ comfyPage }) => {
: undefined
}
])
)
])
const allSettingCategoriesLocale = Object.fromEntries(
settings

View File

@@ -1,3 +1,4 @@
// Setup browser globals before any other imports that might use them
import * as fs from 'fs'
import { comfyPageFixture as test } from '../browser_tests/fixtures/ComfyPage'
@@ -5,18 +6,21 @@ import type { ComfyNodeDef } from '../src/schemas/nodeDefSchema'
import type { ComfyApi } from '../src/scripts/api'
import { ComfyNodeDefImpl } from '../src/stores/nodeDefStore'
import { normalizeI18nKey } from '../src/utils/formatUtil'
import './setup-browser-globals.js'
const localePath = './src/locales/en/main.json'
const nodeDefsPath = './src/locales/en/nodeDefs.json'
test('collect-i18n-node-defs', async ({ comfyPage }) => {
// Mock view route
comfyPage.page.route('**/view**', async (route) => {
await comfyPage.page.route('**/view**', async (route) => {
await route.fulfill({
body: JSON.stringify({})
})
})
// Note: Don't mock the object_info API endpoint - let it hit the actual backend
const nodeDefs: ComfyNodeDefImpl[] = (
Object.values(
await comfyPage.page.evaluate(async () => {
@@ -31,6 +35,27 @@ test('collect-i18n-node-defs', async ({ comfyPage }) => {
console.log(`Collected ${nodeDefs.length} node definitions`)
// If no node definitions were collected (e.g., running without backend),
// create empty locale files to avoid build failures
if (nodeDefs.length === 0) {
console.warn('No node definitions found - creating empty locale files')
const locale = JSON.parse(fs.readFileSync(localePath, 'utf-8'))
fs.writeFileSync(
localePath,
JSON.stringify(
{
...locale,
dataTypes: {},
nodeCategories: {}
},
null,
2
)
)
fs.writeFileSync(nodeDefsPath, JSON.stringify({}, null, 2))
return
}
const allDataTypesLocale = Object.fromEntries(
nodeDefs
.flatMap((nodeDef) => {

View File

@@ -5,7 +5,7 @@ import { zodToJsonSchema } from 'zod-to-json-schema'
import {
zComfyWorkflow,
zComfyWorkflow1
} from '../src/platform/workflow/validation/schemas/workflowSchema'
} from '../src/schemas/comfyWorkflowSchema'
import { zComfyNodeDef as zComfyNodeDefV2 } from '../src/schemas/nodeDef/nodeDefSchemaV2'
import { zComfyNodeDef as zComfyNodeDefV1 } from '../src/schemas/nodeDefSchema'

View File

@@ -0,0 +1,98 @@
// Polyfill browser globals for Node.js context during test imports
import { Window } from 'happy-dom'
// Define build-time constants
if (typeof globalThis.__USE_PROD_CONFIG__ === 'undefined') {
globalThis.__USE_PROD_CONFIG__ = false
}
// Create a happy-dom window instance with configurable URL
const defaultUrl =
(typeof globalThis.process !== 'undefined' &&
globalThis.process.env?.HAPPY_DOM_URL) ||
'http://localhost:5173/'
const window = new Window({
url: defaultUrl,
width: 1024,
height: 768
})
// Mock location with additional properties for testing
const mockLocation = {
...window.location,
href: defaultUrl,
origin: new URL(defaultUrl).origin,
protocol: new URL(defaultUrl).protocol,
host: new URL(defaultUrl).host,
hostname: new URL(defaultUrl).hostname,
port: new URL(defaultUrl).port,
pathname: new URL(defaultUrl).pathname,
search: new URL(defaultUrl).search,
hash: new URL(defaultUrl).hash,
assign: (url) => {
console.log(`[Mock] location.assign called with: ${url}`)
mockLocation.href = url
},
replace: (url) => {
console.log(`[Mock] location.replace called with: ${url}`)
mockLocation.href = url
},
reload: () => {
console.log('[Mock] location.reload called')
}
}
// Expose DOM globals (only set if not already defined)
if (!globalThis.window) globalThis.window = window
if (!globalThis.document) globalThis.document = window.document
if (!globalThis.location) globalThis.location = mockLocation
if (!globalThis.navigator) {
try {
globalThis.navigator = window.navigator
} catch (e) {
// navigator might be read-only in some environments
}
}
if (!globalThis.HTMLElement) globalThis.HTMLElement = window.HTMLElement
if (!globalThis.Element) globalThis.Element = window.Element
if (!globalThis.Node) globalThis.Node = window.Node
if (!globalThis.NodeList) globalThis.NodeList = window.NodeList
if (!globalThis.DOMParser) globalThis.DOMParser = window.DOMParser
if (!globalThis.XMLSerializer) globalThis.XMLSerializer = window.XMLSerializer
if (!globalThis.localStorage) globalThis.localStorage = window.localStorage
if (!globalThis.sessionStorage)
globalThis.sessionStorage = window.sessionStorage
if (!globalThis.CustomEvent) globalThis.CustomEvent = window.CustomEvent
if (!globalThis.Event) globalThis.Event = window.Event
if (!globalThis.MouseEvent) globalThis.MouseEvent = window.MouseEvent
if (!globalThis.KeyboardEvent) globalThis.KeyboardEvent = window.KeyboardEvent
if (!globalThis.getComputedStyle)
globalThis.getComputedStyle = window.getComputedStyle
if (!globalThis.requestAnimationFrame)
globalThis.requestAnimationFrame = window.requestAnimationFrame
if (!globalThis.cancelAnimationFrame)
globalThis.cancelAnimationFrame = window.cancelAnimationFrame
// Add ResizeObserver polyfill
if (!globalThis.ResizeObserver) {
globalThis.ResizeObserver = class ResizeObserver {
constructor(callback) {
this.callback = callback
}
observe() {}
unobserve() {}
disconnect() {}
}
}
// Add IntersectionObserver polyfill
if (!globalThis.IntersectionObserver) {
globalThis.IntersectionObserver = class IntersectionObserver {
constructor(callback) {
this.callback = callback
}
observe() {}
unobserve() {}
disconnect() {}
}
}

130
scripts/verbatim.mjs Normal file
View File

@@ -0,0 +1,130 @@
#!/usr/bin/env bun
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
// const __dirname = path.dirname(fileURLToPath(import.meta.url));
const __dirname = process.cwd()
// Parse the tsc.log file to get all errors
async function parseErrors(file) {
const logContent = await fs.readFile(file, 'utf-8');
const lines = logContent.split('\n').filter(line => line.includes('error TS1484'));
const errors = [];
for (const line of lines) {
// Match the format: filepath(line,col): error TS1484: 'TypeName' is a type
// Note: Some lines may have a number prefix followed by →
const match = line.match(/(?:\d+→)?(.+?)\((\d+),(\d+)\): error TS1484: '(.+?)' is a type/);
if (match) {
const [, filePath, lineNum, colNum, typeName] = match;
errors.push({
filePath: path.join(__dirname, filePath.trim()),
lineNum: parseInt(lineNum),
colNum: parseInt(colNum),
typeName
});
}
}
return errors;
}
// Group errors by file
function groupByFile(errors) {
const grouped = {};
for (const error of errors) {
if (!grouped[error.filePath]) {
grouped[error.filePath] = [];
}
grouped[error.filePath].push(error);
}
return grouped;
}
// Process a single file
async function processFile(filePath, errors) {
try {
const content = await fs.readFile(filePath, 'utf-8');
const lines = content.split('\n');
// Sort errors by line and column in reverse order to avoid position shifts
errors.sort((a, b) => {
if (a.lineNum !== b.lineNum) {
return b.lineNum - a.lineNum;
}
return b.colNum - a.colNum;
});
// Process each error
for (const error of errors) {
const lineIndex = error.lineNum - 1;
const line = lines[lineIndex];
if (!line) continue;
// Skip if already has 'type' keyword before this position
const beforePos = line.substring(0, error.colNum - 1);
if (beforePos.includes('import type') || beforePos.endsWith('{ type ') || beforePos.endsWith(', type ')) {
continue;
}
// Insert "type " at the exact column position (column is 1-based)
const insertPos = error.colNum - 1;
lines[lineIndex] = line.substring(0, insertPos) + 'type ' + line.substring(insertPos);
}
await fs.writeFile(filePath, lines.join('\n'));
console.log(`✓ Fixed ${errors.length} type imports in ${path.relative(__dirname, filePath)}`);
return true;
} catch (error) {
console.error(`✗ Error processing ${filePath}:`, error.message);
return false;
}
}
// Main function
async function main() {
console.log('🔧 Fixing TypeScript type-only imports...\n');
const logFile = path.join(__dirname, 'tsc.log');
if (!await fs.readFile(logFile, 'utf-8').catch(() => null)) {
console.error('Unable to read tsc.log');
console.error('Run this command to generate type errors and rerun this script again:');
console.error('pnpm typecheck > tsc.log');
return;
}
console.log('Parsing tsc.log for type import errors...\n');
const errors = await parseErrors(logFile);
console.log(`Found ${errors.length} type import errors\n`);
const grouped = groupByFile(errors);
const files = Object.keys(grouped);
console.log(`Processing ${files.length} files...\n`);
let successCount = 0;
let failCount = 0;
for (const filePath of files) {
const success = await processFile(filePath, grouped[filePath]);
if (success) {
successCount++;
} else {
failCount++;
}
}
console.log('\n📊 Summary:');
console.log(`✓ Successfully processed: ${successCount} files`);
if (failCount > 0) {
console.log(`✗ Failed to process: ${failCount} files`);
}
console.log('\n✨ Refactoring complete!');
console.log('Run "pnpm typecheck" to verify the fixes.');
}
// Run the script
main().catch(console.error);

View File

@@ -1,17 +0,0 @@
/* Inter Font Family */
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-latin-normal.woff2') format('woff2');
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-latin-italic.woff2') format('woff2');
font-weight: 100 900;
font-style: italic;
font-display: swap;
}

View File

@@ -1,6 +1,5 @@
@layer theme, base, primevue, components, utilities;
@import './fonts.css';
@import 'tailwindcss/theme' layer(theme);
@import 'tailwindcss/utilities' layer(utilities);
@import 'tw-animate-css';
@@ -53,18 +52,15 @@
--text-xxs: 0.625rem;
--text-xxs--line-height: calc(1 / 0.625);
/* Font Families */
--font-inter: 'Inter', sans-serif;
/* Palette Colors */
--color-charcoal-100: #55565e;
--color-charcoal-200: #494a50;
--color-charcoal-300: #3c3d42;
--color-charcoal-400: #313235;
--color-charcoal-500: #2d2e32;
--color-charcoal-600: #262729;
--color-charcoal-700: #202121;
--color-charcoal-800: #171718;
--color-charcoal-100: #171718;
--color-charcoal-200: #202121;
--color-charcoal-300: #262729;
--color-charcoal-400: #2d2e32;
--color-charcoal-500: #313235;
--color-charcoal-600: #3c3d42;
--color-charcoal-700: #494a50;
--color-charcoal-800: #55565e;
--color-stone-100: #444444;
--color-stone-200: #828282;
@@ -103,12 +99,11 @@
--color-danger-100: #c02323;
--color-danger-200: #d62952;
--color-bypass: #6a246a;
--color-error: #962a2a;
--color-blue-selection: rgb(from var(--color-blue-100) r g b / 0.3);
--color-node-hover-100: rgb(from var(--color-charcoal-100) r g b/ 0.15);
--color-node-hover-200: rgb(from var(--color-charcoal-100) r g b/ 0.1);
--color-blue-selection: rgb( from var(--color-blue-100) r g b / 0.3);
--color-node-hover-100: rgb( from var(--color-charcoal-800) r g b/ 0.15);
--color-node-hover-200: rgb(from var(--color-charcoal-800) r g b/ 0.1);
--color-modal-tag: rgb(from var(--color-gray-400) r g b/ 0.4);
/* PrimeVue pulled colors */
@@ -121,10 +116,10 @@
}
@theme inline {
--color-node-component-surface: var(--color-charcoal-600);
--color-node-component-surface: var(--color-charcoal-300);
--color-node-component-surface-highlight: var(--color-slate-100);
--color-node-component-surface-hovered: var(--color-charcoal-400);
--color-node-component-surface-selected: var(--color-charcoal-200);
--color-node-component-surface-hovered: var(--color-charcoal-500);
--color-node-component-surface-selected: var(--color-charcoal-700);
--color-node-stroke: var(--color-stone-100);
}
@@ -136,7 +131,7 @@
@utility scrollbar-hide {
scrollbar-width: none;
&::-webkit-scrollbar {
&::-webkit-scrollbar {
width: 1px;
}
&::-webkit-scrollbar-thumb {

View File

@@ -50,7 +50,7 @@ import Splitter from 'primevue/splitter'
import SplitterPanel from 'primevue/splitterpanel'
import { computed } from 'vue'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useSettingStore } from '@/stores/settingStore'
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore'

View File

@@ -21,11 +21,10 @@
<script setup lang="ts">
import Button from 'primevue/button'
import type { CSSProperties } from 'vue'
import { computed, watchEffect } from 'vue'
import { type CSSProperties, computed, watchEffect } from 'vue'
import { useSettingStore } from '@/platform/settings/settingStore'
import { app } from '@/scripts/app'
import { useSettingStore } from '@/stores/settingStore'
import { useWorkspaceStore } from '@/stores/workspaceStore'
import { showNativeSystemMenu } from '@/utils/envUtil'

View File

@@ -37,8 +37,8 @@ import { storeToRefs } from 'pinia'
import InputNumber from 'primevue/inputnumber'
import { computed } from 'vue'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useQueueSettingsStore } from '@/stores/queueStore'
import { useSettingStore } from '@/stores/settingStore'
const queueSettingsStore = useQueueSettingsStore()
const { batchCount } = storeToRefs(queueSettingsStore)

View File

@@ -22,10 +22,17 @@ import {
} from '@vueuse/core'
import { clamp } from 'es-toolkit/compat'
import Panel from 'primevue/panel'
import type { Ref } from 'vue'
import { computed, inject, nextTick, onMounted, ref, watch } from 'vue'
import {
type Ref,
computed,
inject,
nextTick,
onMounted,
ref,
watch
} from 'vue'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useSettingStore } from '@/stores/settingStore'
import ComfyQueueButton from './ComfyQueueButton.vue'

View File

@@ -3,38 +3,13 @@
<div class="p-terminal rounded-none h-full w-full p-2">
<div ref="terminalEl" class="h-full terminal-host" />
</div>
<Button
v-tooltip.left="{
value: tooltipText,
showDelay: 300
}"
icon="pi pi-copy"
severity="secondary"
size="small"
:class="
cn('absolute top-2 right-8 transition-opacity', {
'opacity-0 pointer-events-none select-none': !isHovered
})
"
:aria-label="tooltipText"
@click="handleCopy"
/>
</div>
</template>
<script setup lang="ts">
import { useElementHover, useEventListener } from '@vueuse/core'
import type { IDisposable } from '@xterm/xterm'
import Button from 'primevue/button'
import type { Ref } from 'vue'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { type Ref, onUnmounted, ref } from 'vue'
import { useTerminal } from '@/composables/bottomPanelTabs/useTerminal'
import { electronAPI, isElectron } from '@/utils/envUtil'
import { cn } from '@/utils/tailwindUtil'
const { t } = useI18n()
const emit = defineEmits<{
created: [ReturnType<typeof useTerminal>, Ref<HTMLElement | undefined>]
@@ -42,59 +17,9 @@ const emit = defineEmits<{
}>()
const terminalEl = ref<HTMLElement | undefined>()
const rootEl = ref<HTMLElement | undefined>()
const hasSelection = ref(false)
emit('created', useTerminal(terminalEl), rootEl)
const isHovered = useElementHover(rootEl)
const terminalData = useTerminal(terminalEl)
emit('created', terminalData, ref(rootEl))
const { terminal } = terminalData
let selectionDisposable: IDisposable | undefined
const tooltipText = computed(() => {
return hasSelection.value
? t('serverStart.copySelectionTooltip')
: t('serverStart.copyAllTooltip')
})
const handleCopy = async () => {
const existingSelection = terminal.getSelection()
const shouldSelectAll = !existingSelection
if (shouldSelectAll) terminal.selectAll()
const selectedText = shouldSelectAll
? terminal.getSelection()
: existingSelection
if (selectedText) {
await navigator.clipboard.writeText(selectedText)
if (shouldSelectAll) {
terminal.clearSelection()
}
}
}
const showContextMenu = (event: MouseEvent) => {
event.preventDefault()
electronAPI()?.showContextMenu({ type: 'text' })
}
if (isElectron()) {
useEventListener(terminalEl, 'contextmenu', showContextMenu)
}
onMounted(() => {
selectionDisposable = terminal.onSelectionChange(() => {
hasSelection.value = terminal.hasSelection()
})
})
onUnmounted(() => {
selectionDisposable?.dispose()
emit('unmounted')
})
onUnmounted(() => emit('unmounted'))
</script>
<style scoped>

View File

@@ -3,9 +3,8 @@
</template>
<script setup lang="ts">
import type { IDisposable } from '@xterm/xterm'
import type { Ref } from 'vue'
import { onMounted, onUnmounted } from 'vue'
import { type IDisposable } from '@xterm/xterm'
import { type Ref, onMounted, onUnmounted } from 'vue'
import type { useTerminal } from '@/composables/bottomPanelTabs/useTerminal'
import { electronAPI } from '@/utils/envUtil'

View File

@@ -15,11 +15,14 @@
import { until } from '@vueuse/core'
import { storeToRefs } from 'pinia'
import ProgressSpinner from 'primevue/progressspinner'
import type { Ref } from 'vue'
import { onMounted, onUnmounted, ref } from 'vue'
import { type Ref, onMounted, onUnmounted, ref } from 'vue'
import type { useTerminal } from '@/composables/bottomPanelTabs/useTerminal'
import type { LogEntry, LogsWsMessage, TerminalSize } from '@/schemas/apiSchema'
import {
type LogEntry,
type LogsWsMessage,
type TerminalSize
} from '@/schemas/apiSchema'
import { api } from '@/scripts/api'
import { useExecutionStore } from '@/stores/executionStore'

View File

@@ -38,10 +38,10 @@ import { computed, onUpdated, ref, watch } from 'vue'
import SubgraphBreadcrumbItem from '@/components/breadcrumb/SubgraphBreadcrumbItem.vue'
import { useOverflowObserver } from '@/composables/element/useOverflowObserver'
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore'
import { useSubgraphStore } from '@/stores/subgraphStore'
import { useWorkflowStore } from '@/stores/workflowStore'
import { forEachSubgraphNode } from '@/utils/graphTraversalUtil'
const MIN_WIDTH = 28

View File

@@ -47,21 +47,18 @@
<script setup lang="ts">
import InputText from 'primevue/inputtext'
import type { MenuState } from 'primevue/menu'
import Menu from 'primevue/menu'
import type { MenuState } from 'primevue/menu'
import type { MenuItem } from 'primevue/menuitem'
import Tag from 'primevue/tag'
import { computed, nextTick, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useWorkflowService } from '@/platform/workflow/core/services/workflowService'
import {
ComfyWorkflow,
useWorkflowStore
} from '@/platform/workflow/management/stores/workflowStore'
import { useDialogService } from '@/services/dialogService'
import { useWorkflowService } from '@/services/workflowService'
import { useCommandStore } from '@/stores/commandStore'
import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore'
import { ComfyWorkflow, useWorkflowStore } from '@/stores/workflowStore'
import { appendJsonExt } from '@/utils/formatUtil'
interface Props {

View File

@@ -36,8 +36,8 @@ import Button from 'primevue/button'
import InputText from 'primevue/inputtext'
import { ref } from 'vue'
import { useToastStore } from '@/platform/updates/common/toastStore'
import { api } from '@/scripts/api'
import { useToastStore } from '@/stores/toastStore'
const modelValue = defineModel<string>()

View File

@@ -7,7 +7,7 @@
<InputText
v-else
ref="inputRef"
v-model:model-value="inputValue"
v-model:modelValue="inputValue"
v-focus
type="text"
size="small"

View File

@@ -17,7 +17,7 @@
<script setup lang="ts">
import { onBeforeUnmount } from 'vue'
import type { CustomExtension, VueExtension } from '@/types/extensionTypes'
import { type CustomExtension, type VueExtension } from '@/types/extensionTypes'
const props = defineProps<{
extension: VueExtension | CustomExtension

View File

@@ -21,7 +21,7 @@
<component
:is="markRaw(getFormComponent(props.item))"
:id="props.id"
v-model:model-value="formValue"
v-model:modelValue="formValue"
:aria-labelledby="`${props.id}-label`"
v-bind="getFormAttrs(props.item)"
/>
@@ -44,7 +44,7 @@ import FormRadioGroup from '@/components/common/FormRadioGroup.vue'
import InputKnob from '@/components/common/InputKnob.vue'
import InputSlider from '@/components/common/InputSlider.vue'
import UrlInput from '@/components/common/UrlInput.vue'
import type { FormItem } from '@/platform/settings/types'
import { type FormItem } from '@/types/settingTypes'
const formValue = defineModel<any>('formValue')
const props = defineProps<{

View File

@@ -4,7 +4,7 @@ import RadioButton from 'primevue/radiobutton'
import { beforeAll, describe, expect, it } from 'vitest'
import { createApp } from 'vue'
import type { SettingOption } from '@/platform/settings/types'
import type { SettingOption } from '@/types/settingTypes'
import FormRadioGroup from './FormRadioGroup.vue'

View File

@@ -24,7 +24,7 @@
import RadioButton from 'primevue/radiobutton'
import { computed } from 'vue'
import type { SettingOption } from '@/platform/settings/types'
import type { SettingOption } from '@/types/settingTypes'
const props = defineProps<{
modelValue: any

View File

@@ -32,7 +32,7 @@
import Button from 'primevue/button'
import ProgressSpinner from 'primevue/progressspinner'
import type { PrimeVueSeverity } from '@/types/primeVueTypes'
import { type PrimeVueSeverity } from '@/types/primeVueTypes'
const {
disabled,

View File

@@ -1,7 +1,7 @@
<template>
<Tree
v-model:expanded-keys="expandedKeys"
v-model:selection-keys="selectionKeys"
v-model:expandedKeys="expandedKeys"
v-model:selectionKeys="selectionKeys"
class="tree-explorer py-0 px-2 2xl:px-4"
:class="props.class"
:value="renderedRoot.children"

View File

@@ -9,8 +9,10 @@ import { createI18n } from 'vue-i18n'
import EditableText from '@/components/common/EditableText.vue'
import TreeExplorerTreeNode from '@/components/common/TreeExplorerTreeNode.vue'
import type { RenderedTreeExplorerNode } from '@/types/treeExplorerTypes'
import { InjectKeyHandleEditLabelFunction } from '@/types/treeExplorerTypes'
import {
InjectKeyHandleEditLabelFunction,
type RenderedTreeExplorerNode
} from '@/types/treeExplorerTypes'
// Create a mock i18n instance
const i18n = createI18n({

View File

@@ -12,8 +12,8 @@
<script setup lang="ts">
import { onBeforeUnmount, onMounted } from 'vue'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
import { useSettingStore } from '@/stores/settingStore'
import { useWorkflowStore } from '@/stores/workflowStore'
const settingStore = useSettingStore()
const workflowStore = useWorkflowStore()

View File

@@ -94,9 +94,9 @@ import Message from 'primevue/message'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useSettingStore } from '@/platform/settings/settingStore'
import type { ConfirmationDialogType } from '@/services/dialogService'
import { useDialogStore } from '@/stores/dialogStore'
import { useSettingStore } from '@/stores/settingStore'
const props = defineProps<{
message: string

View File

@@ -60,9 +60,9 @@ import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
import MissingCoreNodesMessage from '@/components/dialog/content/MissingCoreNodesMessage.vue'
import { useMissingNodes } from '@/composables/nodePack/useMissingNodes'
import { useManagerState } from '@/composables/useManagerState'
import { useToastStore } from '@/platform/updates/common/toastStore'
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
import { useDialogStore } from '@/stores/dialogStore'
import { useToastStore } from '@/stores/toastStore'
import type { MissingNodeType } from '@/types/comfy'
import { ManagerTab } from '@/types/comfyManagerTypes'

View File

@@ -1,5 +1,4 @@
import type { VueWrapper } from '@vue/test-utils'
import { mount } from '@vue/test-utils'
import { VueWrapper, mount } from '@vue/test-utils'
import { createPinia } from 'pinia'
import Button from 'primevue/button'
import PrimeVue from 'primevue/config'

View File

@@ -39,7 +39,7 @@ import { useI18n } from 'vue-i18n'
import ElectronFileDownload from '@/components/common/ElectronFileDownload.vue'
import FileDownload from '@/components/common/FileDownload.vue'
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useSettingStore } from '@/stores/settingStore'
import { isElectron } from '@/utils/envUtil'
// TODO: Read this from server internal API rather than hardcoding here

View File

@@ -2,7 +2,7 @@
<div class="settings-container">
<ScrollPanel class="settings-sidebar shrink-0 p-2 w-48 2xl:w-64">
<SearchBox
v-model:model-value="searchQuery"
v-model:modelValue="searchQuery"
class="settings-search-box w-full mb-2"
:placeholder="$t('g.searchSettings') + '...'"
:debounce-time="128"
@@ -66,17 +66,18 @@ import Tabs from 'primevue/tabs'
import { computed, watch } from 'vue'
import SearchBox from '@/components/common/SearchBox.vue'
import CurrentUserMessage from '@/components/dialog/content/setting/CurrentUserMessage.vue'
import PanelTemplate from '@/components/dialog/content/setting/PanelTemplate.vue'
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
import ColorPaletteMessage from '@/platform/settings/components/ColorPaletteMessage.vue'
import SettingsPanel from '@/platform/settings/components/SettingsPanel.vue'
import { useSettingSearch } from '@/platform/settings/composables/useSettingSearch'
import { useSettingUI } from '@/platform/settings/composables/useSettingUI'
import type { SettingTreeNode } from '@/platform/settings/settingStore'
import type { ISettingGroup, SettingParams } from '@/platform/settings/types'
import { useSettingSearch } from '@/composables/setting/useSettingSearch'
import { useSettingUI } from '@/composables/setting/useSettingUI'
import { type SettingTreeNode } from '@/stores/settingStore'
import { type ISettingGroup, type SettingParams } from '@/types/settingTypes'
import { flattenTree } from '@/utils/treeUtil'
import ColorPaletteMessage from './setting/ColorPaletteMessage.vue'
import CurrentUserMessage from './setting/CurrentUserMessage.vue'
import PanelTemplate from './setting/PanelTemplate.vue'
import SettingsPanel from './setting/SettingsPanel.vue'
const { defaultPanel } = defineProps<{
defaultPanel?:
| 'about'

View File

@@ -148,7 +148,7 @@ import { useI18n } from 'vue-i18n'
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
import { COMFY_PLATFORM_BASE_URL } from '@/config/comfyApi'
import type { SignInData, SignUpData } from '@/schemas/signInSchema'
import { type SignInData, type SignUpData } from '@/schemas/signInSchema'
import { isInChina } from '@/utils/networkUtil'
import ApiKeyForm from './signin/ApiKeyForm.vue'

View File

@@ -17,8 +17,7 @@
</template>
<script setup lang="ts">
import type { FormSubmitEvent } from '@primevue/forms'
import { Form } from '@primevue/forms'
import { Form, type FormSubmitEvent } from '@primevue/forms'
import { zodResolver } from '@primevue/forms/resolvers/zod'
import Button from 'primevue/button'
import { ref } from 'vue'

View File

@@ -16,7 +16,7 @@
<div class="flex flex-1 relative overflow-hidden">
<ManagerNavSidebar
v-if="isSideNavOpen"
v-model:selected-tab="selectedTab"
v-model:selectedTab="selectedTab"
:tabs="tabs"
/>
<div
@@ -57,9 +57,9 @@
</IconButton>
</div>
<RegistrySearchBar
v-model:search-query="searchQuery"
v-model:search-mode="searchMode"
v-model:sort-field="sortField"
v-model:searchQuery="searchQuery"
v-model:searchMode="searchMode"
v-model:sortField="sortField"
:search-results="searchResults"
:suggestions="suggestions"
:is-missing-tab="isMissingTab"
@@ -160,7 +160,7 @@ import { useComfyManagerStore } from '@/stores/comfyManagerStore'
import { useComfyRegistryStore } from '@/stores/comfyRegistryStore'
import type { TabItem } from '@/types/comfyManagerTypes'
import { ManagerTab } from '@/types/comfyManagerTypes'
import type { components } from '@/types/comfyRegistryTypes'
import { type components } from '@/types/comfyRegistryTypes'
const { initialTab } = defineProps<{
initialTab?: ManagerTab

View File

@@ -169,9 +169,9 @@ import { useI18n } from 'vue-i18n'
import ContentDivider from '@/components/common/ContentDivider.vue'
import { useConflictDetection } from '@/composables/useConflictDetection'
import type {
ConflictDetail,
ConflictDetectionResult
import {
type ConflictDetail,
type ConflictDetectionResult
} from '@/types/conflictDetectionTypes'
import { getConflictMessage } from '@/utils/conflictMessageUtil'

View File

@@ -19,7 +19,7 @@
import Message from 'primevue/message'
import { computed, inject } from 'vue'
import type { components } from '@/types/comfyRegistryTypes'
import { type components } from '@/types/comfyRegistryTypes'
import { ImportFailedKey } from '@/types/importFailedTypes'
type PackVersionStatus = components['schemas']['NodeVersionStatus']

View File

@@ -1,5 +1,4 @@
import type { VueWrapper } from '@vue/test-utils'
import { mount } from '@vue/test-utils'
import { VueWrapper, mount } from '@vue/test-utils'
import { createPinia } from 'pinia'
import PrimeVue from 'primevue/config'
import Tooltip from 'primevue/tooltip'

View File

@@ -1,5 +1,4 @@
import type { VueWrapper } from '@vue/test-utils'
import { mount } from '@vue/test-utils'
import { VueWrapper, mount } from '@vue/test-utils'
import { createPinia } from 'pinia'
import Button from 'primevue/button'
import PrimeVue from 'primevue/config'

View File

@@ -93,8 +93,8 @@ import VerifiedIcon from '@/components/icons/VerifiedIcon.vue'
import { useConflictDetection } from '@/composables/useConflictDetection'
import { useComfyRegistryService } from '@/services/comfyRegistryService'
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
import type { components } from '@/types/comfyRegistryTypes'
import type { components as ManagerComponents } from '@/types/generatedManagerTypes'
import { type components } from '@/types/comfyRegistryTypes'
import { type components as ManagerComponents } from '@/types/generatedManagerTypes'
import { getJoinedConflictMessages } from '@/utils/conflictMessageUtil'
import { isSemVer } from '@/utils/formatUtil'

View File

@@ -1,5 +1,4 @@
import type { VueWrapper } from '@vue/test-utils'
import { mount } from '@vue/test-utils'
import { VueWrapper, mount } from '@vue/test-utils'
import { createPinia } from 'pinia'
import PrimeVue from 'primevue/config'
import ToggleSwitch from 'primevue/toggleswitch'

View File

@@ -39,7 +39,7 @@ import { useDialogService } from '@/services/dialogService'
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
import { useConflictDetectionStore } from '@/stores/conflictDetectionStore'
import type { components } from '@/types/comfyRegistryTypes'
import type { components as ManagerComponents } from '@/types/generatedManagerTypes'
import { type components as ManagerComponents } from '@/types/generatedManagerTypes'
const TOGGLE_DEBOUNCE_MS = 256

View File

@@ -31,11 +31,13 @@ import { useConflictDetection } from '@/composables/useConflictDetection'
import { t } from '@/i18n'
import { useDialogService } from '@/services/dialogService'
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
import type { ButtonSize } from '@/types/buttonTypes'
import { type ButtonSize } from '@/types/buttonTypes'
import type { components } from '@/types/comfyRegistryTypes'
import type { ConflictDetectionResult } from '@/types/conflictDetectionTypes'
import type { ConflictDetail } from '@/types/conflictDetectionTypes'
import type { components as ManagerComponents } from '@/types/generatedManagerTypes'
import {
type ConflictDetail,
type ConflictDetectionResult
} from '@/types/conflictDetectionTypes'
import { type components as ManagerComponents } from '@/types/generatedManagerTypes'
type NodePack = components['schemas']['Node']

View File

@@ -17,9 +17,9 @@
<script setup lang="ts">
import IconTextButton from '@/components/button/IconTextButton.vue'
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
import type { ButtonSize } from '@/types/buttonTypes'
import { type ButtonSize } from '@/types/buttonTypes'
import type { components } from '@/types/comfyRegistryTypes'
import type { components as ManagerComponents } from '@/types/generatedManagerTypes'
import { type components as ManagerComponents } from '@/types/generatedManagerTypes'
type NodePack = components['schemas']['Node']

View File

@@ -75,7 +75,7 @@ import { useImportFailedDetection } from '@/composables/useImportFailedDetection
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
import { useConflictDetectionStore } from '@/stores/conflictDetectionStore'
import { IsInstallingKey } from '@/types/comfyManagerTypes'
import type { components } from '@/types/comfyRegistryTypes'
import { type components } from '@/types/comfyRegistryTypes'
import type { ConflictDetectionResult } from '@/types/conflictDetectionTypes'
import { ImportFailedKey } from '@/types/importFailedTypes'

View File

@@ -50,7 +50,7 @@ import PackUninstallButton from '@/components/dialog/content/manager/button/Pack
import PackIcon from '@/components/dialog/content/manager/packIcon/PackIcon.vue'
import { useConflictDetection } from '@/composables/useConflictDetection'
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
import type { components } from '@/types/comfyRegistryTypes'
import { type components } from '@/types/comfyRegistryTypes'
import type { ConflictDetail } from '@/types/conflictDetectionTypes'
import { ImportFailedKey } from '@/types/importFailedTypes'

View File

@@ -67,7 +67,7 @@ import { usePacksSelection } from '@/composables/nodePack/usePacksSelection'
import { usePacksStatus } from '@/composables/nodePack/usePacksStatus'
import { useConflictDetection } from '@/composables/useConflictDetection'
import { useComfyRegistryStore } from '@/stores/comfyRegistryStore'
import type { components } from '@/types/comfyRegistryTypes'
import { type components } from '@/types/comfyRegistryTypes'
import type { ConflictDetail } from '@/types/conflictDetectionTypes'
import { ImportFailedKey } from '@/types/importFailedTypes'

View File

@@ -48,7 +48,7 @@ import { computed, inject, ref, watchEffect } from 'vue'
import DescriptionTabPanel from '@/components/dialog/content/manager/infoPanel/tabs/DescriptionTabPanel.vue'
import NodesTabPanel from '@/components/dialog/content/manager/infoPanel/tabs/NodesTabPanel.vue'
import WarningTabPanel from '@/components/dialog/content/manager/infoPanel/tabs/WarningTabPanel.vue'
import type { components } from '@/types/comfyRegistryTypes'
import { type components } from '@/types/comfyRegistryTypes'
import type { ConflictDetectionResult } from '@/types/conflictDetectionTypes'
import { ImportFailedKey } from '@/types/importFailedTypes'

View File

@@ -8,7 +8,7 @@
</template>
<script setup lang="ts">
const { value = 'N/A', label } = defineProps<{
const { value = 'N/A', label = 'N/A' } = defineProps<{
label: string
value?: string | number
}>()

View File

@@ -3,7 +3,7 @@ import { describe, expect, it } from 'vitest'
import { createI18n } from 'vue-i18n'
import enMessages from '@/locales/en/main.json' with { type: 'json' }
import type { components } from '@/types/comfyRegistryTypes'
import { type components } from '@/types/comfyRegistryTypes'
import DescriptionTabPanel from './DescriptionTabPanel.vue'

View File

@@ -29,7 +29,7 @@ import { useI18n } from 'vue-i18n'
import InfoTextSection, {
type TextSection
} from '@/components/dialog/content/manager/infoPanel/InfoTextSection.vue'
import type { components } from '@/types/comfyRegistryTypes'
import { type components } from '@/types/comfyRegistryTypes'
import { isValidUrl } from '@/utils/formatUtil'
const { t } = useI18n()

View File

@@ -34,7 +34,7 @@ import { computed, ref, shallowRef, useId } from 'vue'
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
import NodePreview from '@/components/node/NodePreview.vue'
import { useComfyRegistryStore } from '@/stores/comfyRegistryStore'
import type { components, operations } from '@/types/comfyRegistryTypes'
import { type components, type operations } from '@/types/comfyRegistryTypes'
import { registryToFrontendV2NodeDef } from '@/utils/mapperUtil'
type ListComfyNodesResponse =

View File

@@ -29,8 +29,8 @@ import { computed } from 'vue'
import { useImportFailedDetection } from '@/composables/useImportFailedDetection'
import { t } from '@/i18n'
import type { components } from '@/types/comfyRegistryTypes'
import type { ConflictDetectionResult } from '@/types/conflictDetectionTypes'
import { type components } from '@/types/comfyRegistryTypes'
import { type ConflictDetectionResult } from '@/types/conflictDetectionTypes'
import { getConflictMessage } from '@/utils/conflictMessageUtil'
const { nodePack, conflictResult } = defineProps<{

View File

@@ -37,7 +37,7 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import type { components } from '@/types/comfyRegistryTypes'
import { type components } from '@/types/comfyRegistryTypes'
const DEFAULT_BANNER = '/assets/images/fallback-gradient-avatar.svg'

View File

@@ -37,7 +37,7 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import type { components } from '@/types/comfyRegistryTypes'
import { type components } from '@/types/comfyRegistryTypes'
const DEFAULT_BANNER = '/assets/images/fallback-gradient-avatar.svg'

View File

@@ -19,7 +19,7 @@
<script setup lang="ts">
import PackIcon from '@/components/dialog/content/manager/packIcon/PackIcon.vue'
import type { components } from '@/types/comfyRegistryTypes'
import { type components } from '@/types/comfyRegistryTypes'
const {
nodePacks,

View File

@@ -41,12 +41,12 @@
<div class="flex mt-3 text-sm">
<div class="flex gap-6 ml-1">
<SearchFilterDropdown
v-model:model-value="searchMode"
v-model:modelValue="searchMode"
:options="filterOptions"
:label="$t('g.filter')"
/>
<SearchFilterDropdown
v-model:model-value="sortField"
v-model:modelValue="sortField"
:options="availableSortOptions"
:label="$t('g.sort')"
/>
@@ -62,8 +62,9 @@
<script setup lang="ts">
import { stubTrue } from 'es-toolkit/compat'
import type { AutoCompleteOptionSelectEvent } from 'primevue/autocomplete'
import AutoComplete from 'primevue/autocomplete'
import AutoComplete, {
type AutoCompleteOptionSelectEvent
} from 'primevue/autocomplete'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
@@ -76,7 +77,7 @@ import {
type SearchOption,
SortableAlgoliaField
} from '@/types/comfyManagerTypes'
import type { components } from '@/types/comfyRegistryTypes'
import { type components } from '@/types/comfyRegistryTypes'
import type {
QuerySuggestion,
SearchMode,

View File

@@ -1,5 +1,4 @@
import type { VueWrapper } from '@vue/test-utils'
import { mount } from '@vue/test-utils'
import { VueWrapper, mount } from '@vue/test-utils'
import { createPinia } from 'pinia'
import PrimeVue from 'primevue/config'
import { describe, expect, it } from 'vitest'

View File

@@ -43,8 +43,8 @@ import Button from 'primevue/button'
import Message from 'primevue/message'
import Select from 'primevue/select'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useColorPaletteService } from '@/services/colorPaletteService'
import { useSettingStore } from '@/stores/settingStore'
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
const settingStore = useSettingStore()

View File

@@ -92,9 +92,10 @@ import ToggleSwitch from 'primevue/toggleswitch'
import { computed, onMounted, ref } from 'vue'
import SearchBox from '@/components/common/SearchBox.vue'
import PanelTemplate from '@/components/dialog/content/setting/PanelTemplate.vue'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useExtensionStore } from '@/stores/extensionStore'
import { useSettingStore } from '@/stores/settingStore'
import PanelTemplate from './PanelTemplate.vue'
const filterTypes = ['All', 'Core', 'Custom']
const filterType = ref('All')

View File

@@ -54,7 +54,7 @@
<div v-for="item in items" :key="item.name" class="mb-4">
<FormItem
:id="item.id"
v-model:form-value="item.value"
v-model:formValue="item.value"
:item="translateItem(item)"
:label-class="{
'text-highlight': item.initialValue !== item.value
@@ -74,14 +74,15 @@ import { watch } from 'vue'
import { useI18n } from 'vue-i18n'
import FormItem from '@/components/common/FormItem.vue'
import PanelTemplate from '@/components/dialog/content/setting/PanelTemplate.vue'
import { useCopyToClipboard } from '@/composables/useCopyToClipboard'
import type { ServerConfig } from '@/constants/serverConfig'
import { useSettingStore } from '@/platform/settings/settingStore'
import type { FormItem as FormItemType } from '@/platform/settings/types'
import { useServerConfigStore } from '@/stores/serverConfigStore'
import { useSettingStore } from '@/stores/settingStore'
import type { FormItem as FormItemType } from '@/types/settingTypes'
import { electronAPI } from '@/utils/envUtil'
import PanelTemplate from './PanelTemplate.vue'
const settingStore = useSettingStore()
const serverConfigStore = useServerConfigStore()
const {

View File

@@ -19,8 +19,8 @@
<script setup lang="ts">
import Divider from 'primevue/divider'
import SettingItem from '@/platform/settings/components/SettingItem.vue'
import type { SettingParams } from '@/platform/settings/types'
import SettingItem from '@/components/dialog/content/setting/SettingItem.vue'
import { type SettingParams } from '@/types/settingTypes'
import { normalizeI18nKey } from '@/utils/formatUtil'
defineProps<{

View File

@@ -6,7 +6,7 @@ import Tooltip from 'primevue/tooltip'
import { describe, expect, it, vi } from 'vitest'
import { createI18n } from 'vue-i18n'
import SettingItem from '@/platform/settings/components/SettingItem.vue'
import SettingItem from './SettingItem.vue'
const i18n = createI18n({
legacy: false,

View File

@@ -29,8 +29,8 @@ import { useI18n } from 'vue-i18n'
import FormItem from '@/components/common/FormItem.vue'
import { st } from '@/i18n'
import { useSettingStore } from '@/platform/settings/settingStore'
import type { SettingOption, SettingParams } from '@/platform/settings/types'
import { useSettingStore } from '@/stores/settingStore'
import type { SettingOption, SettingParams } from '@/types/settingTypes'
import { normalizeI18nKey } from '@/utils/formatUtil'
const props = defineProps<{

View File

@@ -17,8 +17,9 @@
<script setup lang="ts">
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
import SettingGroup from '@/platform/settings/components/SettingGroup.vue'
import type { ISettingGroup } from '@/platform/settings/types'
import { type ISettingGroup } from '@/types/settingTypes'
import SettingGroup from './SettingGroup.vue'
const props = defineProps<{
settingGroups: ISettingGroup[]

View File

@@ -96,8 +96,8 @@ import Message from 'primevue/message'
import ProgressSpinner from 'primevue/progressspinner'
import { computed, ref } from 'vue'
import type { AuditLog } from '@/services/customerEventsService'
import {
type AuditLog,
EventType,
useCustomerEventsService
} from '@/services/customerEventsService'

View File

@@ -13,7 +13,7 @@
import Tag from 'primevue/tag'
import { computed } from 'vue'
import type { KeyComboImpl } from '@/stores/keybindingStore'
import { KeyComboImpl } from '@/stores/keybindingStore'
const { keyCombo, isModified = false } = defineProps<{
keyCombo: KeyComboImpl

View File

@@ -79,8 +79,7 @@
</template>
<script setup lang="ts">
import type { FormSubmitEvent } from '@primevue/forms'
import { Form } from '@primevue/forms'
import { Form, type FormSubmitEvent } from '@primevue/forms'
import { zodResolver } from '@primevue/forms/resolvers/zod'
import Button from 'primevue/button'
import InputText from 'primevue/inputtext'

View File

@@ -1,6 +1,5 @@
import { Form } from '@primevue/forms'
import type { VueWrapper } from '@vue/test-utils'
import { mount } from '@vue/test-utils'
import { VueWrapper, mount } from '@vue/test-utils'
import Button from 'primevue/button'
import PrimeVue from 'primevue/config'
import InputText from 'primevue/inputtext'

View File

@@ -71,8 +71,7 @@
</template>
<script setup lang="ts">
import type { FormSubmitEvent } from '@primevue/forms'
import { Form } from '@primevue/forms'
import { Form, type FormSubmitEvent } from '@primevue/forms'
import { zodResolver } from '@primevue/forms/resolvers/zod'
import Button from 'primevue/button'
import InputText from 'primevue/inputtext'

View File

@@ -59,8 +59,7 @@
</template>
<script setup lang="ts">
import type { FormSubmitEvent } from '@primevue/forms'
import { Form, FormField } from '@primevue/forms'
import { Form, FormField, type FormSubmitEvent } from '@primevue/forms'
import { zodResolver } from '@primevue/forms/resolvers/zod'
import Button from 'primevue/button'
import Checkbox from 'primevue/checkbox'

View File

@@ -75,16 +75,16 @@ import { useI18n } from 'vue-i18n'
import DotSpinner from '@/components/common/DotSpinner.vue'
import { useConflictDetection } from '@/composables/useConflictDetection'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useWorkflowService } from '@/platform/workflow/core/services/workflowService'
import { api } from '@/scripts/api'
import { useComfyManagerService } from '@/services/comfyManagerService'
import { useWorkflowService } from '@/services/workflowService'
import {
useComfyManagerStore,
useManagerProgressDialogStore
} from '@/stores/comfyManagerStore'
import { useCommandStore } from '@/stores/commandStore'
import { useDialogStore } from '@/stores/dialogStore'
import { useSettingStore } from '@/stores/settingStore'
const { t } = useI18n()
const dialogStore = useDialogStore()

Some files were not shown because too many files have changed in this diff Show More