Merge remote-tracking branch 'origin/main' into feat/new-workflow-templates

This commit is contained in:
Johnpaul
2025-09-17 03:27:15 +01:00
27 changed files with 461 additions and 596 deletions

View File

@@ -5,13 +5,14 @@ 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'
export default [
{
files: ['src/**/*.{js,mjs,cjs,ts,vue}']
},
const extraFileExtensions = ['.vue']
export default defineConfig([
{
ignores: [
'src/scripts/*',
@@ -24,35 +25,49 @@ export default [
]
},
{
files: ['./**/*.{ts,mts}'],
languageOptions: {
globals: {
...globals.browser,
__COMFYUI_FRONTEND_VERSION__: 'readonly'
},
parser: tseslint.parser,
parserOptions: {
project: ['./tsconfig.json', './tsconfig.eslint.json'],
parser: tseslint.parser,
projectService: true,
tsConfigRootDir: import.meta.dirname,
ecmaVersion: 2020,
sourceType: 'module',
extraFileExtensions: ['.vue']
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
}
}
},
pluginJs.configs.recommended,
...tseslint.configs.recommended,
...pluginVue.configs['flat/recommended'],
tseslint.configs.recommended,
pluginVue.configs['flat/recommended'],
eslintPluginPrettierRecommended,
{
files: ['src/**/*.vue'],
languageOptions: {
parserOptions: {
parser: tseslint.parser
}
}
},
storybook.configs['flat/recommended'],
{
plugins: {
'unused-imports': unusedImports,
// @ts-expect-error Bad types in the plugin
'@intlify/vue-i18n': pluginI18n
},
rules: {
@@ -67,6 +82,7 @@ export default [
'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',
@@ -135,6 +151,5 @@ export default [
}
]
}
},
...storybook.configs['flat/recommended']
]
}
])

View File

@@ -25,7 +25,9 @@ const config: KnipConfig = {
'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'
'src/scripts/ui/components/splitButton.ts',
// Staged for for use with subgraph widget promotion
'src/lib/litegraph/src/widgets/DisconnectedWidget.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),
'vue-tsc --noEmit'
'pnpm typecheck'
]
}
function formatAndEslint(fileNames) {
return [
`eslint --fix ${fileNames.join(' ')}`,
`prettier --write ${fileNames.join(' ')}`
`pnpm exec eslint --cache --fix ${fileNames.join(' ')}`,
`pnpm exec prettier --cache --write ${fileNames.join(' ')}`
]
}

View File

@@ -38,10 +38,10 @@
"build-storybook": "storybook build"
},
"devDependencies": {
"@eslint/js": "^9.8.0",
"@eslint/js": "^9.35.0",
"@iconify-json/lucide": "^1.2.66",
"@iconify/tailwind": "^1.2.0",
"@intlify/eslint-plugin-vue-i18n": "^3.2.0",
"@intlify/eslint-plugin-vue-i18n": "^4.1.0",
"@lobehub/i18n-cli": "^1.25.1",
"@nx/eslint": "21.4.1",
"@nx/playwright": "21.4.1",
@@ -64,11 +64,11 @@
"@vitest/ui": "^3.0.0",
"@vue/test-utils": "^2.4.6",
"eslint": "^9.34.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",
"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",
"fs-extra": "^11.2.0",
"globals": "^15.9.0",
"happy-dom": "^15.11.0",
@@ -79,13 +79,13 @@
"lint-staged": "^15.2.7",
"nx": "21.4.1",
"prettier": "^3.3.2",
"storybook": "^9.1.1",
"storybook": "^9.1.6",
"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.42.0",
"typescript-eslint": "^8.44.0",
"unplugin-icons": "^0.22.0",
"unplugin-vue-components": "^0.28.0",
"uuid": "^11.1.0",
@@ -94,7 +94,8 @@
"vite-plugin-html": "^3.2.2",
"vite-plugin-vue-devtools": "^7.7.6",
"vitest": "^3.2.4",
"vue-tsc": "^2.1.10",
"vue-eslint-parser": "^10.2.0",
"vue-tsc": "^3.0.7",
"zip-dir": "^2.0.0",
"zod-to-json-schema": "^3.24.1"
},

810
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -46,7 +46,7 @@ const hasSelection = ref(false)
const isHovered = useElementHover(rootEl)
const terminalData = useTerminal(terminalEl)
emit('created', terminalData, rootEl)
emit('created', terminalData, ref(rootEl))
const { terminal } = terminalData
let selectionDisposable: IDisposable | undefined

View File

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

View File

@@ -21,7 +21,7 @@
<component
:is="markRaw(getFormComponent(props.item))"
:id="props.id"
v-model:modelValue="formValue"
v-model:model-value="formValue"
:aria-labelledby="`${props.id}-label`"
v-bind="getFormAttrs(props.item)"
/>

View File

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

View File

@@ -16,7 +16,7 @@
<div class="flex flex-1 relative overflow-hidden">
<ManagerNavSidebar
v-if="isSideNavOpen"
v-model:selectedTab="selectedTab"
v-model:selected-tab="selectedTab"
:tabs="tabs"
/>
<div
@@ -57,9 +57,9 @@
</IconButton>
</div>
<RegistrySearchBar
v-model:searchQuery="searchQuery"
v-model:searchMode="searchMode"
v-model:sortField="sortField"
v-model:search-query="searchQuery"
v-model:search-mode="searchMode"
v-model:sort-field="sortField"
:search-results="searchResults"
:suggestions="suggestions"
:is-missing-tab="isMissingTab"

View File

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

View File

@@ -41,12 +41,12 @@
<div class="flex mt-3 text-sm">
<div class="flex gap-6 ml-1">
<SearchFilterDropdown
v-model:modelValue="searchMode"
v-model:model-value="searchMode"
:options="filterOptions"
:label="$t('g.filter')"
/>
<SearchFilterDropdown
v-model:modelValue="sortField"
v-model:model-value="sortField"
:options="availableSortOptions"
:label="$t('g.sort')"
/>

View File

@@ -4,7 +4,7 @@
class="px-4 py-2 flex items-center"
>
<TabMenu
v-model:activeIndex="activeTabIndex"
v-model:active-index="activeTabIndex"
:model="tabs"
class="w-full border-none"
:pt="{

View File

@@ -34,7 +34,7 @@ const updateWidgets = () => {
const widget = widgetState.widget
// Early exit for non-visible widgets
if (!widget.isVisible()) {
if (!widget.isVisible() || !widgetState.active) {
widgetState.visible = false
continue
}

View File

@@ -59,7 +59,7 @@ import ResponseBlurb from '@/components/graph/widgets/chatHistory/ResponseBlurb.
import { ComponentWidget } from '@/scripts/domWidget'
import { linkifyHtml, nl2br } from '@/utils/formatUtil'
const { widget, history = '[]' } = defineProps<{
const { widget, history } = defineProps<{
widget?: ComponentWidget<string>
history: string
}>()

View File

@@ -21,7 +21,7 @@
</template>
<template #header>
<SearchBox
v-model:modelValue="searchQuery"
v-model:model-value="searchQuery"
class="model-lib-search-box p-2 2xl:p-4"
:placeholder="$t('g.searchModels') + '...'"
@search="handleSearch"
@@ -31,7 +31,7 @@
<ElectronDownloadItems v-if="isElectron()" />
<TreeExplorer
v-model:expandedKeys="expandedKeys"
v-model:expanded-keys="expandedKeys"
class="model-lib-tree-explorer"
:root="renderedRoot"
>

View File

@@ -78,7 +78,7 @@
<template #header>
<div>
<SearchBox
v-model:modelValue="searchQuery"
v-model:model-value="searchQuery"
class="node-lib-search-box p-2 2xl:p-4"
:placeholder="$t('g.searchNodes') + '...'"
filter-icon="pi pi-filter"
@@ -106,7 +106,7 @@
class="m-2"
/>
<TreeExplorer
v-model:expandedKeys="expandedKeys"
v-model:expanded-keys="expandedKeys"
class="node-lib-tree-explorer"
:root="renderedRoot"
>

View File

@@ -86,7 +86,7 @@
<ConfirmPopup />
<ContextMenu ref="menu" :model="menuItems" />
<ResultGallery
v-model:activeIndex="galleryActiveIndex"
v-model:active-index="galleryActiveIndex"
:all-gallery-items="allGalleryItems"
/>
</template>

View File

@@ -14,7 +14,7 @@
</template>
<template #header>
<SearchBox
v-model:modelValue="searchQuery"
v-model:model-value="searchQuery"
class="workflows-search-box p-2 2xl:p-4"
:placeholder="$t('g.searchWorkflows') + '...'"
@search="handleSearch"
@@ -32,7 +32,7 @@
class="ml-2"
/>
<TreeExplorer
v-model:expandedKeys="dummyExpandedKeys"
v-model:expanded-keys="dummyExpandedKeys"
:root="renderTreeNode(openWorkflowsTree, WorkflowTreeType.Open)"
:selection-keys="selectionKeys"
>
@@ -74,7 +74,7 @@
class="ml-2"
/>
<TreeExplorer
v-model:expandedKeys="dummyExpandedKeys"
v-model:expanded-keys="dummyExpandedKeys"
:root="
renderTreeNode(
bookmarkedWorkflowsTree,
@@ -96,7 +96,7 @@
/>
<TreeExplorer
v-if="workflowStore.persistedWorkflows.length > 0"
v-model:expandedKeys="expandedKeys"
v-model:expanded-keys="expandedKeys"
:root="renderTreeNode(workflowsTree, WorkflowTreeType.Browse)"
:selection-keys="selectionKeys"
>
@@ -114,7 +114,7 @@
</div>
<div v-else class="comfyui-workflows-search-panel">
<TreeExplorer
v-model:expandedKeys="expandedKeys"
v-model:expanded-keys="expandedKeys"
:root="renderTreeNode(filteredRoot, WorkflowTreeType.Browse)"
>
<template #node="{ node }">

View File

@@ -45,7 +45,7 @@ export class ComboWidget
return typeof this.value === 'number' ? String(this.value) : this.value
}
#getValues(node: LGraphNode): Values {
private getValues(node: LGraphNode): Values {
const { values } = this.options
if (values == null) throw new Error('[ComboWidget]: values is required')
@@ -57,7 +57,7 @@ export class ComboWidget
* @param increment `true` if checking the use of the increment button, `false` for decrement
* @returns `true` if the value is at the given index, otherwise `false`.
*/
#canUseButton(increment: boolean): boolean {
private canUseButton(increment: boolean): boolean {
const { values } = this.options
// If using legacy duck-typed method, false is the most permissive return value
if (typeof values === 'function') return false
@@ -78,23 +78,23 @@ export class ComboWidget
* Handles edge case where the value is both the first and last item in the list.
*/
override canIncrement(): boolean {
return this.#canUseButton(true)
return this.canUseButton(true)
}
override canDecrement(): boolean {
return this.#canUseButton(false)
return this.canUseButton(false)
}
override incrementValue(options: WidgetEventOptions): void {
this.#tryChangeValue(1, options)
this.tryChangeValue(1, options)
}
override decrementValue(options: WidgetEventOptions): void {
this.#tryChangeValue(-1, options)
this.tryChangeValue(-1, options)
}
#tryChangeValue(delta: number, options: WidgetEventOptions): void {
const values = this.#getValues(options.node)
private tryChangeValue(delta: number, options: WidgetEventOptions): void {
const values = this.getValues(options.node)
const indexedValues = toArray(values)
// avoids double click event
@@ -128,7 +128,7 @@ export class ComboWidget
if (x > width - 40) return this.incrementValue({ e, node, canvas })
// Otherwise, show dropdown menu
const values = this.#getValues(node)
const values = this.getValues(node)
const values_list = toArray(values)
// Handle center click - show dropdown menu

View File

@@ -0,0 +1,38 @@
import { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
import type { IButtonWidget } from '@/lib/litegraph/src/types/widgets'
import { BaseWidget, type DrawWidgetOptions } from './BaseWidget'
class DisconnectedWidget extends BaseWidget<IButtonWidget> {
constructor(widget: IButtonWidget) {
super(widget, new LGraphNode('DisconnectedPlaceholder'))
this.disabled = true
}
override drawWidget(
ctx: CanvasRenderingContext2D,
{ width, showText = true }: DrawWidgetOptions
) {
ctx.save()
this.drawWidgetShape(ctx, { width, showText })
if (showText) {
this.drawTruncatingText({ ctx, width, leftPadding: 0, rightPadding: 0 })
}
ctx.restore()
}
override onClick() {}
override get _displayValue() {
return 'Disconnected'
}
}
const conf: IButtonWidget = {
type: 'button',
value: undefined,
name: 'Disconnected',
options: {},
y: 0,
clicked: false
}
export const disconnectedWidget = new DisconnectedWidget(conf)

View File

@@ -54,7 +54,7 @@
<div v-for="item in items" :key="item.name" class="mb-4">
<FormItem
:id="item.id"
v-model:formValue="item.value"
v-model:form-value="item.value"
:item="translateItem(item)"
:label-class="{
'text-highlight': item.initialValue !== item.value

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:modelValue="searchQuery"
v-model:model-value="searchQuery"
class="settings-search-box w-full mb-2"
:placeholder="$t('g.searchSettings') + '...'"
:debounce-time="128"

View File

@@ -1,7 +1,7 @@
<template>
<div class="flex flex-col gap-1">
<Galleria
v-model:activeIndex="activeIndex"
v-model:active-index="activeIndex"
:value="galleryImages"
v-bind="filteredProps"
:show-thumbnails="showThumbnails"

View File

@@ -35,6 +35,7 @@ import {
isComboInputSpecV1,
isComboInputSpecV2
} from '@/schemas/nodeDefSchema'
import { type BaseDOMWidget, DOMWidgetImpl } from '@/scripts/domWidget'
import { getFromWebmFile } from '@/scripts/metadata/ebml'
import { getGltfBinaryMetadata } from '@/scripts/metadata/gltf'
import { getFromIsobmffFile } from '@/scripts/metadata/isobmff'
@@ -837,22 +838,29 @@ export class ComfyApp {
this.canvas.canvas.addEventListener<'litegraph:set-graph'>(
'litegraph:set-graph',
(e) => {
// Assertion: Not yet defined in litegraph.
const { newGraph } = e.detail
const nodeSet = new Set(newGraph.nodes)
const widgetStore = useDomWidgetStore()
// Assertions: UnwrapRef
for (const { widget } of widgetStore.activeWidgetStates) {
if (!nodeSet.has(widget.node)) {
widgetStore.deactivateWidget(widget.id)
}
}
const activeWidgets: Record<
string,
BaseDOMWidget<object | string>
> = Object.fromEntries(
newGraph.nodes
.flatMap((node) => node.widgets ?? [])
.filter((w) => w instanceof DOMWidgetImpl)
.map((w) => [w.id, w])
)
for (const { widget } of widgetStore.inactiveWidgetStates) {
if (nodeSet.has(widget.node)) {
widgetStore.activateWidget(widget.id)
for (const [
widgetId,
widgetState
] of widgetStore.widgetStates.entries()) {
if (widgetId in activeWidgets) {
widgetState.active = true
widgetState.widget = activeWidgets[widgetId]
} else {
widgetState.active = false
}
}
}

View File

@@ -37,8 +37,8 @@
</StepPanel>
<StepPanel v-slot="{ activateCallback }" value="1">
<InstallLocationPicker
v-model:installPath="installPath"
v-model:pathError="pathError"
v-model:install-path="installPath"
v-model:path-error="pathError"
/>
<div class="flex pt-6 justify-between">
<Button
@@ -58,8 +58,8 @@
</StepPanel>
<StepPanel v-slot="{ activateCallback }" value="2">
<MigrationPicker
v-model:sourcePath="migrationSourcePath"
v-model:migrationItemIds="migrationItemIds"
v-model:source-path="migrationSourcePath"
v-model:migration-item-ids="migrationItemIds"
/>
<div class="flex pt-6 justify-between">
<Button
@@ -78,13 +78,13 @@
</StepPanel>
<StepPanel v-slot="{ activateCallback }" value="3">
<DesktopSettingsConfiguration
v-model:autoUpdate="autoUpdate"
v-model:allowMetrics="allowMetrics"
v-model:auto-update="autoUpdate"
v-model:allow-metrics="allowMetrics"
/>
<MirrorsConfiguration
v-model:pythonMirror="pythonMirror"
v-model:pypiMirror="pypiMirror"
v-model:torchMirror="torchMirror"
v-model:python-mirror="pythonMirror"
v-model:pypi-mirror="pypiMirror"
v-model:torch-mirror="torchMirror"
:device="device"
class="mt-6"
/>

View File

@@ -33,6 +33,7 @@
"src/types/**/*.d.ts",
"tests-ui/**/*",
"global.d.ts",
"eslint.config.ts",
"vite.config.mts",
".storybook/**/*"
]