mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-07-09 08:38:13 +00:00
Compare commits
16 Commits
v1.16.6
...
export-vue
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84a675de88 | ||
|
|
e45f5bdebb | ||
|
|
c270e7734a | ||
|
|
8d7a21e008 | ||
|
|
29e63baca6 | ||
|
|
b22713daf0 | ||
|
|
c8b8953e0a | ||
|
|
731ce8599d | ||
|
|
ec8e55c1c1 | ||
|
|
04d38f2538 | ||
|
|
1c41db75f8 | ||
|
|
c7a7397000 | ||
|
|
e660e1d678 | ||
|
|
fb19752389 | ||
|
|
d098d6ae4e | ||
|
|
e4a5355f58 |
@@ -1,4 +1,5 @@
|
||||
import pluginJs from '@eslint/js'
|
||||
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
|
||||
import unusedImports from 'eslint-plugin-unused-imports'
|
||||
import pluginVue from 'eslint-plugin-vue'
|
||||
import globals from 'globals'
|
||||
@@ -33,6 +34,7 @@ export default [
|
||||
pluginJs.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
...pluginVue.configs['flat/recommended'],
|
||||
eslintPluginPrettierRecommended,
|
||||
{
|
||||
files: ['src/**/*.vue'],
|
||||
languageOptions: {
|
||||
@@ -50,7 +52,8 @@ export default [
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'@typescript-eslint/prefer-as-const': 'off',
|
||||
'unused-imports/no-unused-imports': 'error'
|
||||
'unused-imports/no-unused-imports': 'error',
|
||||
'vue/no-v-html': 'off'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
4
global.d.ts
vendored
4
global.d.ts
vendored
@@ -15,3 +15,7 @@ interface Navigator {
|
||||
visible: boolean
|
||||
}
|
||||
}
|
||||
|
||||
interface Window {
|
||||
Vue: typeof import('vue')
|
||||
}
|
||||
|
||||
117
package-lock.json
generated
117
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"version": "1.16.6",
|
||||
"version": "1.16.7",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"version": "1.16.6",
|
||||
"version": "1.16.7",
|
||||
"license": "GPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@alloc/quick-lru": "^5.2.0",
|
||||
@@ -61,6 +61,8 @@
|
||||
"autoprefixer": "^10.4.19",
|
||||
"chalk": "^5.3.0",
|
||||
"eslint": "^9.12.0",
|
||||
"eslint-config-prettier": "^10.1.2",
|
||||
"eslint-plugin-prettier": "^5.2.6",
|
||||
"eslint-plugin-unused-imports": "^4.1.4",
|
||||
"eslint-plugin-vue": "^9.27.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
@@ -77,7 +79,7 @@
|
||||
"typescript-eslint": "^8.0.0",
|
||||
"unplugin-icons": "^0.19.3",
|
||||
"unplugin-vue-components": "^0.27.4",
|
||||
"vite": "^5.4.17",
|
||||
"vite": "^5.4.18",
|
||||
"vite-plugin-dts": "^4.3.0",
|
||||
"vitest": "^2.0.0",
|
||||
"vue-tsc": "^2.1.10",
|
||||
@@ -1791,6 +1793,19 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@pkgr/core": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.2.tgz",
|
||||
"integrity": "sha512-25L86MyPvnlQoX2MTIV2OiUcb6vJ6aRbFa9pbwByn95INKD5mFH2smgjDhq+fwJoqAgvgbdJLj6Tz7V9X5CFAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/unts"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.1.tgz",
|
||||
@@ -5620,6 +5635,50 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-config-prettier": {
|
||||
"version": "10.1.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.2.tgz",
|
||||
"integrity": "sha512-Epgp/EofAUeEpIdZkW60MHKvPyru1ruQJxPL+WIycnaPApuseK0Zpkrh/FwL9oIpQvIhJwV7ptOy0DWUjTlCiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"eslint-config-prettier": "bin/cli.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-prettier": {
|
||||
"version": "5.2.6",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.6.tgz",
|
||||
"integrity": "sha512-mUcf7QG2Tjk7H055Jk0lGBjbgDnfrvqjhXh9t2xLMSCjZVcw9Rb1V6sVNXO0th3jgeO7zllWPTNRil3JW94TnQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prettier-linter-helpers": "^1.0.0",
|
||||
"synckit": "^0.11.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/eslint-plugin-prettier"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/eslint": ">=8.0.0",
|
||||
"eslint": ">=8.0.0",
|
||||
"eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0",
|
||||
"prettier": ">=3.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/eslint": {
|
||||
"optional": true
|
||||
},
|
||||
"eslint-config-prettier": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-unused-imports": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.1.4.tgz",
|
||||
@@ -6078,6 +6137,13 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-diff": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
|
||||
"integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
|
||||
@@ -10275,6 +10341,19 @@
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier-linter-helpers": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
|
||||
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-diff": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pretty-format": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
|
||||
@@ -11663,6 +11742,23 @@
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/synckit": {
|
||||
"version": "0.11.3",
|
||||
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.3.tgz",
|
||||
"integrity": "sha512-szhWDqNNI9etJUvbZ1/cx1StnZx8yMmFxme48SwR4dty4ioSY50KEZlpv0qAfgc1fpRzuh9hBXEzoCpJ779dLg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@pkgr/core": "^0.2.1",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/synckit"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.4.4",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz",
|
||||
@@ -11954,10 +12050,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
|
||||
"integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==",
|
||||
"dev": true
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/tsx": {
|
||||
"version": "4.16.2",
|
||||
@@ -12559,9 +12656,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.4.17",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.17.tgz",
|
||||
"integrity": "sha512-5+VqZryDj4wgCs55o9Lp+p8GE78TLVg0lasCH5xFZ4jacZjtqZa6JUw9/p0WeAojaOfncSM6v77InkFPGnvPvg==",
|
||||
"version": "5.4.18",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.18.tgz",
|
||||
"integrity": "sha512-1oDcnEp3lVyHCuQ2YFelM4Alm2o91xNoMncRm1U7S+JdYfYOvbiGZ3/CxGttrOu2M/KcGz7cRC2DoNUA6urmMA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"private": true,
|
||||
"version": "1.16.6",
|
||||
"version": "1.16.7",
|
||||
"type": "module",
|
||||
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
|
||||
"homepage": "https://comfy.org",
|
||||
@@ -44,6 +44,8 @@
|
||||
"autoprefixer": "^10.4.19",
|
||||
"chalk": "^5.3.0",
|
||||
"eslint": "^9.12.0",
|
||||
"eslint-config-prettier": "^10.1.2",
|
||||
"eslint-plugin-prettier": "^5.2.6",
|
||||
"eslint-plugin-unused-imports": "^4.1.4",
|
||||
"eslint-plugin-vue": "^9.27.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
@@ -60,7 +62,7 @@
|
||||
"typescript-eslint": "^8.0.0",
|
||||
"unplugin-icons": "^0.19.3",
|
||||
"unplugin-vue-components": "^0.27.4",
|
||||
"vite": "^5.4.17",
|
||||
"vite": "^5.4.18",
|
||||
"vite-plugin-dts": "^4.3.0",
|
||||
"vitest": "^2.0.0",
|
||||
"vue-tsc": "^2.1.10",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="flex flex-col h-full">
|
||||
<Tabs v-model:value="bottomPanelStore.activeBottomPanelTabId">
|
||||
<TabList pt:tabList="border-none">
|
||||
<TabList pt:tab-list="border-none">
|
||||
<div class="w-full flex justify-between">
|
||||
<div class="tabs-container">
|
||||
<Tab
|
||||
|
||||
@@ -293,6 +293,10 @@ watch(activeCategory, (_, oldValue) => {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.settings-content {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.settings-container {
|
||||
flex-direction: column;
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
'w-full': fullWidth,
|
||||
'w-min-content': !fullWidth
|
||||
}"
|
||||
:disabled="isInstalling"
|
||||
:disabled="loading"
|
||||
v-bind="$attrs"
|
||||
@click="onClick"
|
||||
>
|
||||
<span class="py-2.5 px-3">
|
||||
<template v-if="isInstalling">
|
||||
<template v-if="loading">
|
||||
{{ loadingMessage ?? $t('g.loading') }}
|
||||
</template>
|
||||
<template v-else>
|
||||
@@ -23,9 +23,6 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import Button from 'primevue/button'
|
||||
import { inject, ref } from 'vue'
|
||||
|
||||
import { IsInstallingKey } from '@/types/comfyManagerTypes'
|
||||
|
||||
const {
|
||||
label,
|
||||
@@ -33,6 +30,7 @@ const {
|
||||
fullWidth = false
|
||||
} = defineProps<{
|
||||
label: string
|
||||
loading?: boolean
|
||||
loadingMessage?: string
|
||||
fullWidth?: boolean
|
||||
}>()
|
||||
@@ -45,10 +43,7 @@ defineOptions({
|
||||
inheritAttrs: false
|
||||
})
|
||||
|
||||
const isInstalling = inject(IsInstallingKey, ref(false))
|
||||
|
||||
const onClick = (): void => {
|
||||
isInstalling.value = true
|
||||
emit('action')
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
nodePacks.length > 1 ? $t('manager.installSelected') : $t('g.install')
|
||||
"
|
||||
severity="secondary"
|
||||
:loading="isInstalling"
|
||||
:loading-message="$t('g.installing')"
|
||||
@action="installAllPacks"
|
||||
@click="onClick"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -31,6 +33,10 @@ const { nodePacks } = defineProps<{
|
||||
|
||||
const isInstalling = inject(IsInstallingKey, ref(false))
|
||||
|
||||
const onClick = (): void => {
|
||||
isInstalling.value = true
|
||||
}
|
||||
|
||||
const managerStore = useComfyManagerStore()
|
||||
|
||||
const createPayload = (installItem: NodePack) => {
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
|
||||
import PackInstallButton from '@/components/dialog/content/manager/button/PackInstallButton.vue'
|
||||
@@ -46,7 +46,14 @@ const { nodePacks } = defineProps<{
|
||||
|
||||
const managerStore = useComfyManagerStore()
|
||||
|
||||
const isAllInstalled = computed(() =>
|
||||
nodePacks.every((nodePack) => managerStore.isPackInstalled(nodePack.id))
|
||||
const isAllInstalled = ref(false)
|
||||
watch(
|
||||
[() => nodePacks, () => managerStore.installedPacks],
|
||||
() => {
|
||||
isAllInstalled.value = nodePacks.every((nodePack) =>
|
||||
managerStore.isPackInstalled(nodePack.id)
|
||||
)
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -27,6 +27,14 @@
|
||||
class="p-button-text"
|
||||
@click="editKeybinding(slotProps.data)"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-replay"
|
||||
class="p-button-text p-button-warn"
|
||||
:disabled="
|
||||
!keybindingStore.isCommandKeybindingModified(slotProps.data.id)
|
||||
"
|
||||
@click="resetKeybinding(slotProps.data)"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-trash"
|
||||
class="p-button-text p-button-danger"
|
||||
@@ -63,6 +71,13 @@
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="source" :header="$t('g.source')">
|
||||
<template #body="slotProps">
|
||||
<span class="overflow-hidden text-ellipsis">{{
|
||||
slotProps.data.source || '-'
|
||||
}}</span>
|
||||
</template>
|
||||
</Column>
|
||||
</DataTable>
|
||||
|
||||
<Dialog
|
||||
@@ -152,6 +167,7 @@ interface ICommandData {
|
||||
id: string
|
||||
keybinding: KeybindingImpl | null
|
||||
label: string
|
||||
source?: string
|
||||
}
|
||||
|
||||
const commandsData = computed<ICommandData[]>(() => {
|
||||
@@ -161,7 +177,8 @@ const commandsData = computed<ICommandData[]>(() => {
|
||||
`commands.${normalizeI18nKey(command.id)}.label`,
|
||||
command.label ?? ''
|
||||
),
|
||||
keybinding: keybindingStore.getKeybindingByCommandId(command.id)
|
||||
keybinding: keybindingStore.getKeybindingByCommandId(command.id),
|
||||
source: command.source
|
||||
}))
|
||||
})
|
||||
|
||||
@@ -254,6 +271,16 @@ async function saveKeybinding() {
|
||||
cancelEdit()
|
||||
}
|
||||
|
||||
async function resetKeybinding(commandData: ICommandData) {
|
||||
if (keybindingStore.resetKeybindingForCommand(commandData.id)) {
|
||||
await keybindingService.persistUserKeybindings()
|
||||
} else {
|
||||
console.warn(
|
||||
`No changes made when resetting keybinding for command: ${commandData.id}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const toast = useToast()
|
||||
async function resetAllKeybindings() {
|
||||
keybindingStore.resetAllKeybindings()
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
<template>
|
||||
<Button
|
||||
v-tooltip="{ value: props.tooltip, showDelay: 300, hideDelay: 300 }"
|
||||
:class="props.class"
|
||||
v-tooltip="{ value: tooltip, showDelay: 300, hideDelay: 300 }"
|
||||
text
|
||||
:pt="{
|
||||
root: {
|
||||
class: `side-bar-button ${
|
||||
props.selected
|
||||
selected
|
||||
? 'p-button-primary side-bar-button-selected'
|
||||
: 'p-button-secondary'
|
||||
}`,
|
||||
'aria-label': props.tooltip
|
||||
'aria-label': tooltip
|
||||
}
|
||||
}"
|
||||
@click="emit('click', $event)"
|
||||
>
|
||||
<template #icon>
|
||||
<OverlayBadge v-if="shouldShowBadge" :value="overlayValue">
|
||||
<i :class="props.icon + ' side-bar-button-icon'" />
|
||||
<i :class="icon + ' side-bar-button-icon'" />
|
||||
</OverlayBadge>
|
||||
<i v-else :class="props.icon + ' side-bar-button-icon'" />
|
||||
<i v-else :class="icon + ' side-bar-button-icon'" />
|
||||
</template>
|
||||
</Button>
|
||||
</template>
|
||||
@@ -27,32 +26,25 @@
|
||||
<script setup lang="ts">
|
||||
import Button from 'primevue/button'
|
||||
import OverlayBadge from 'primevue/overlaybadge'
|
||||
import { PropType, computed } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
|
||||
// Add this line to import PropsType
|
||||
const {
|
||||
icon = '',
|
||||
selected = false,
|
||||
tooltip = '',
|
||||
iconBadge = ''
|
||||
} = defineProps<{
|
||||
icon?: string
|
||||
selected?: boolean
|
||||
tooltip?: string
|
||||
iconBadge?: string | (() => string | null)
|
||||
}>()
|
||||
|
||||
const props = defineProps({
|
||||
icon: String,
|
||||
selected: Boolean,
|
||||
tooltip: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
class: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
iconBadge: {
|
||||
type: [String, Function] as PropType<string | (() => string | null)>,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['click'])
|
||||
const emit = defineEmits<{
|
||||
(e: 'click', event: MouseEvent): void
|
||||
}>()
|
||||
const overlayValue = computed(() =>
|
||||
typeof props.iconBadge === 'function'
|
||||
? props.iconBadge() || ''
|
||||
: props.iconBadge
|
||||
typeof iconBadge === 'function' ? iconBadge() ?? '' : iconBadge
|
||||
)
|
||||
const shouldShowBadge = computed(() => !!overlayValue.value)
|
||||
</script>
|
||||
|
||||
@@ -38,12 +38,15 @@
|
||||
>
|
||||
<template #node="{ node }">
|
||||
<TreeExplorerTreeNode :node="node">
|
||||
<template #before-label="{ node }">
|
||||
<span v-if="node.data.isModified || !node.data.isPersisted"
|
||||
<template #before-label="{ node: treeNode }">
|
||||
<span
|
||||
v-if="
|
||||
treeNode.data?.isModified || !treeNode.data?.isPersisted
|
||||
"
|
||||
>*</span
|
||||
>
|
||||
</template>
|
||||
<template #actions="{ node }">
|
||||
<template #actions="{ node: treeNode }">
|
||||
<Button
|
||||
class="close-workflow-button"
|
||||
icon="pi pi-times"
|
||||
@@ -52,7 +55,9 @@
|
||||
workspaceStore.shiftDown ? 'danger' : 'secondary'
|
||||
"
|
||||
size="small"
|
||||
@click.stop="handleCloseWorkflow(node.data)"
|
||||
@click.stop="
|
||||
handleCloseWorkflow(treeNode.data as ComfyWorkflow)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</TreeExplorerTreeNode>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<TreeExplorerTreeNode :node="node">
|
||||
<template #actions="{ node }">
|
||||
<template #actions>
|
||||
<Button
|
||||
:icon="isBookmarked ? 'pi pi-bookmark-fill' : 'pi pi-bookmark'"
|
||||
text
|
||||
severity="secondary"
|
||||
size="small"
|
||||
@click.stop="workflowBookmarkStore.toggleBookmarked(node.data.path)"
|
||||
@click.stop="handleBookmarkClick"
|
||||
/>
|
||||
</template>
|
||||
</TreeExplorerTreeNode>
|
||||
@@ -28,4 +28,10 @@ const workflowBookmarkStore = useWorkflowBookmarkStore()
|
||||
const isBookmarked = computed(
|
||||
() => node.data && workflowBookmarkStore.isBookmarked(node.data.path)
|
||||
)
|
||||
|
||||
const handleBookmarkClick = async () => {
|
||||
if (node.data) {
|
||||
await workflowBookmarkStore.toggleBookmarked(node.data.path)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
class: 'p-0 w-full',
|
||||
onwheel: handleWheel
|
||||
}"
|
||||
pt:barX="h-1"
|
||||
pt:bar-x="h-1"
|
||||
>
|
||||
<SelectButton
|
||||
class="workflow-tabs bg-transparent"
|
||||
|
||||
@@ -55,7 +55,9 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
})
|
||||
}
|
||||
|
||||
return [
|
||||
const commonProps = { source: 'System' }
|
||||
|
||||
const commands = [
|
||||
{
|
||||
id: 'Comfy.NewBlankWorkflow',
|
||||
icon: 'pi pi-plus',
|
||||
@@ -616,4 +618,6 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
return commands.map((command) => ({ ...command, ...commonProps }))
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useEventListener, whenever } from '@vueuse/core'
|
||||
import { computed, readonly, ref } from 'vue'
|
||||
|
||||
import { api } from '@/scripts/api'
|
||||
import { ManagerWsQueueStatus } from '@/types/comfyManagerTypes'
|
||||
|
||||
type QueuedTask<T> = {
|
||||
task: () => Promise<T>
|
||||
@@ -10,11 +11,6 @@ type QueuedTask<T> = {
|
||||
|
||||
const MANAGER_WS_MSG_TYPE = 'cm-queue-status'
|
||||
|
||||
enum ManagerWsQueueStatus {
|
||||
DONE = 'done',
|
||||
IN_PROGRESS = 'in_progress'
|
||||
}
|
||||
|
||||
export const useManagerQueue = () => {
|
||||
const clientQueueItems = ref<QueuedTask<unknown>[]>([])
|
||||
const clientQueueLength = computed(() => clientQueueItems.value.length)
|
||||
|
||||
@@ -35,6 +35,7 @@ function addMultilineWidget(
|
||||
widget.callback?.(widget.value)
|
||||
})
|
||||
|
||||
// Allow middle mouse button panning
|
||||
inputEl.addEventListener('pointerdown', (event: PointerEvent) => {
|
||||
if (event.button === 1) {
|
||||
app.canvas.processMouseDown(event)
|
||||
@@ -53,6 +54,40 @@ function addMultilineWidget(
|
||||
}
|
||||
})
|
||||
|
||||
/** Timer reference. `null` when the timer completes. */
|
||||
let ignoreEventsTimer: ReturnType<typeof setTimeout> | null = null
|
||||
/** Total number of events ignored since the timer started. */
|
||||
let ignoredEvents = 0
|
||||
|
||||
// Pass wheel events to the canvas when appropriate
|
||||
inputEl.addEventListener('wheel', (event: WheelEvent) => {
|
||||
if (!Object.is(event.deltaX, -0)) return
|
||||
|
||||
// If the textarea has focus, require more effort to activate pass-through
|
||||
const multiplier = document.activeElement === inputEl ? 2 : 1
|
||||
const maxScrollHeight = inputEl.scrollHeight - inputEl.clientHeight
|
||||
|
||||
if (
|
||||
(event.deltaY < 0 && inputEl.scrollTop === 0) ||
|
||||
(event.deltaY > 0 && inputEl.scrollTop === maxScrollHeight)
|
||||
) {
|
||||
// Attempting to scroll past the end of the textarea
|
||||
if (!ignoreEventsTimer || ignoredEvents > 25 * multiplier) {
|
||||
app.canvas.processMouseWheel(event)
|
||||
} else {
|
||||
ignoredEvents++
|
||||
}
|
||||
} else if (event.deltaY !== 0) {
|
||||
// Start timer whenever a successful scroll occurs
|
||||
ignoredEvents = 0
|
||||
if (ignoreEventsTimer) clearTimeout(ignoreEventsTimer)
|
||||
|
||||
ignoreEventsTimer = setTimeout(() => {
|
||||
ignoreEventsTimer = null
|
||||
}, 800 * multiplier)
|
||||
}
|
||||
})
|
||||
|
||||
return widget
|
||||
}
|
||||
|
||||
|
||||
@@ -153,7 +153,8 @@ useExtensionService().registerExtension({
|
||||
image: `threed/${data.name} [temp]`,
|
||||
mask: `threed/${dataMask.name} [temp]`,
|
||||
normal: `threed/${dataNormal.name} [temp]`,
|
||||
lineart: `threed/${dataLineart.name} [temp]`
|
||||
lineart: `threed/${dataLineart.name} [temp]`,
|
||||
camera_info: node.properties['Camera Info']
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -293,7 +294,8 @@ useExtensionService().registerExtension({
|
||||
return {
|
||||
image: `threed/${data.name} [temp]`,
|
||||
mask: `threed/${dataMask.name} [temp]`,
|
||||
normal: `threed/${dataNormal.name} [temp]`
|
||||
normal: `threed/${dataNormal.name} [temp]`,
|
||||
camera_info: node.properties['Camera Info']
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -350,7 +352,7 @@ useExtensionService().registerExtension({
|
||||
node.onExecuted = function (message: any) {
|
||||
onExecuted?.apply(this, arguments as any)
|
||||
|
||||
let filePath = message.model_file[0]
|
||||
let filePath = message.result[0]
|
||||
|
||||
if (!filePath) {
|
||||
const msg = t('toastMessages.unableToGetModelFilePath')
|
||||
@@ -360,6 +362,8 @@ useExtensionService().registerExtension({
|
||||
|
||||
const load3d = useLoad3dService().getLoad3d(node)
|
||||
|
||||
let cameraState = message.result[1]
|
||||
|
||||
const modelWidget = node.widgets?.find(
|
||||
(w: IWidget) => w.name === 'model_file'
|
||||
)
|
||||
@@ -369,7 +373,7 @@ useExtensionService().registerExtension({
|
||||
|
||||
const config = new Load3DConfiguration(load3d)
|
||||
|
||||
config.configure('output', modelWidget)
|
||||
config.configure('output', modelWidget, cameraState)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -425,7 +429,7 @@ useExtensionService().registerExtension({
|
||||
node.onExecuted = function (message: any) {
|
||||
onExecuted?.apply(this, arguments as any)
|
||||
|
||||
let filePath = message.model_file[0]
|
||||
let filePath = message.result[0]
|
||||
|
||||
if (!filePath) {
|
||||
const msg = t('toastMessages.unableToGetModelFilePath')
|
||||
@@ -433,6 +437,8 @@ useExtensionService().registerExtension({
|
||||
useToastStore().addAlert(msg)
|
||||
}
|
||||
|
||||
let cameraState = message.result[1]
|
||||
|
||||
const load3d = useLoad3dService().getLoad3d(node)
|
||||
|
||||
const modelWidget = node.widgets?.find(
|
||||
@@ -443,7 +449,7 @@ useExtensionService().registerExtension({
|
||||
|
||||
const config = new Load3DConfiguration(load3d)
|
||||
|
||||
config.configure('output', modelWidget)
|
||||
config.configure('output', modelWidget, cameraState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +94,7 @@
|
||||
"name": "Name",
|
||||
"category": "Category",
|
||||
"sort": "Sort",
|
||||
"source": "Source",
|
||||
"filter": "Filter",
|
||||
"apply": "Apply",
|
||||
"enabled": "Enabled",
|
||||
|
||||
@@ -215,6 +215,7 @@
|
||||
"settings": "Configuraciones",
|
||||
"showReport": "Mostrar informe",
|
||||
"sort": "Ordenar",
|
||||
"source": "Fuente",
|
||||
"status": "Estado",
|
||||
"success": "Éxito",
|
||||
"systemInfo": "Información del sistema",
|
||||
|
||||
@@ -215,6 +215,7 @@
|
||||
"settings": "Paramètres",
|
||||
"showReport": "Afficher le rapport",
|
||||
"sort": "Trier",
|
||||
"source": "Source",
|
||||
"status": "Statut",
|
||||
"success": "Succès",
|
||||
"systemInfo": "Informations système",
|
||||
|
||||
@@ -215,6 +215,7 @@
|
||||
"settings": "設定",
|
||||
"showReport": "レポートを表示",
|
||||
"sort": "並び替え",
|
||||
"source": "ソース",
|
||||
"status": "ステータス",
|
||||
"success": "成功",
|
||||
"systemInfo": "システム情報",
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
"defaultTitle": "오류가 발생했습니다",
|
||||
"extensionFileHint": "다음 스크립트 때문일 수 있습니다",
|
||||
"loadWorkflowTitle": "워크플로우 데이터를 다시 로드하는 중 오류로 인해 로드가 중단되었습니다",
|
||||
"noStackTrace": "스택 추적이 사용할 수 없습니다",
|
||||
"noStackTrace": "스택 추적을 사용할 수 없습니다",
|
||||
"promptExecutionError": "프롬프트 실행 실패"
|
||||
},
|
||||
"g": {
|
||||
@@ -176,7 +176,7 @@
|
||||
"loadWorkflow": "워크플로 로드",
|
||||
"loading": "로딩 중",
|
||||
"logs": "로그",
|
||||
"migrate": "마이그레이트",
|
||||
"migrate": "이전(migrate)",
|
||||
"missing": "누락됨",
|
||||
"name": "이름",
|
||||
"newFolder": "새 폴더",
|
||||
@@ -215,6 +215,7 @@
|
||||
"settings": "설정",
|
||||
"showReport": "보고서 보기",
|
||||
"sort": "정렬",
|
||||
"source": "소스",
|
||||
"status": "상태",
|
||||
"success": "성공",
|
||||
"systemInfo": "시스템 정보",
|
||||
@@ -371,7 +372,7 @@
|
||||
"materialMode": "재질 모드",
|
||||
"materialModes": {
|
||||
"depth": "깊이",
|
||||
"normal": "정상",
|
||||
"normal": "노멀(normal)",
|
||||
"original": "원본",
|
||||
"wireframe": "와이어프레임"
|
||||
},
|
||||
@@ -407,7 +408,7 @@
|
||||
"title": "유지 보수"
|
||||
},
|
||||
"manager": {
|
||||
"changingVersion": "{from}에서 {to}로 버전 변경 중",
|
||||
"changingVersion": "{from}에서 {to}(으)로 버전 변경 중",
|
||||
"createdBy": "작성자",
|
||||
"dependencies": "의존성",
|
||||
"discoverCommunityContent": "커뮤니티에서 만든 노드 팩 및 확장 프로그램을 찾아보세요...",
|
||||
@@ -441,7 +442,7 @@
|
||||
"sort": {
|
||||
"created": "최신",
|
||||
"downloads": "가장 인기 있는",
|
||||
"publisher": "출판사",
|
||||
"publisher": "배포자",
|
||||
"updated": "최근 업데이트"
|
||||
},
|
||||
"status": {
|
||||
@@ -842,8 +843,8 @@
|
||||
"Pointer": "포인터",
|
||||
"Queue": "실행 큐",
|
||||
"QueueButton": "실행 큐 버튼",
|
||||
"Reroute": "재경로",
|
||||
"RerouteBeta": "경로재설정 베타",
|
||||
"Reroute": "경유점",
|
||||
"RerouteBeta": "경유점 (베타)",
|
||||
"Server": "서버",
|
||||
"Server-Config": "서버 구성",
|
||||
"Settings": "설정",
|
||||
@@ -1009,7 +1010,7 @@
|
||||
"fileLoadError": "{fileName}에서 워크플로우를 찾을 수 없습니다",
|
||||
"fileUploadFailed": "파일 업로드에 실패했습니다",
|
||||
"interrupted": "실행이 중단되었습니다",
|
||||
"migrateToLitegraphReroute": "미래 버전에서는 Reroute 노드가 제거됩니다. litegraph-native reroute로 마이그레이트하려면 클릭하세요.",
|
||||
"migrateToLitegraphReroute": "향후 버전에서는 Reroute 노드가 제거됩니다. LiteGraph 에서 자체 제공하는 경유점으로 변환하려면 클릭하세요.",
|
||||
"no3dScene": "텍스처를 적용할 3D 장면이 없습니다",
|
||||
"no3dSceneToExport": "내보낼 3D 장면이 없습니다",
|
||||
"noTemplatesToExport": "내보낼 템플릿이 없습니다",
|
||||
|
||||
@@ -199,7 +199,7 @@
|
||||
"name": "노드 제목을 두 번 클릭하여 편집"
|
||||
},
|
||||
"Comfy_Node_MiddleClickRerouteNode": {
|
||||
"name": "중간 클릭으로 새 경로재설정 노드 생성"
|
||||
"name": "중간 클릭으로 새 경유점 생성"
|
||||
},
|
||||
"Comfy_Node_Opacity": {
|
||||
"name": "노드 불투명도"
|
||||
@@ -345,8 +345,8 @@
|
||||
"name": "툴팁 지연"
|
||||
},
|
||||
"LiteGraph_Reroute_SplineOffset": {
|
||||
"name": "Reroute 스플라인 오프셋",
|
||||
"tooltip": "재경로 중심점에서 베지어 제어점까지의 오프셋"
|
||||
"name": "경유점 스플라인 오프셋",
|
||||
"tooltip": "경유점 중심에서 베지어 제어점까지의 오프셋"
|
||||
},
|
||||
"pysssss_SnapToGrid": {
|
||||
"name": "항상 그리드에 스냅"
|
||||
|
||||
@@ -215,6 +215,7 @@
|
||||
"settings": "Настройки",
|
||||
"showReport": "Показать отчёт",
|
||||
"sort": "Сортировать",
|
||||
"source": "Источник",
|
||||
"status": "Статус",
|
||||
"success": "Успех",
|
||||
"systemInfo": "Информация о системе",
|
||||
|
||||
@@ -215,6 +215,7 @@
|
||||
"settings": "设置",
|
||||
"showReport": "显示报告",
|
||||
"sort": "排序",
|
||||
"source": "来源",
|
||||
"status": "状态",
|
||||
"success": "成功",
|
||||
"systemInfo": "系统信息",
|
||||
|
||||
@@ -9,6 +9,7 @@ import ConfirmationService from 'primevue/confirmationservice'
|
||||
import ToastService from 'primevue/toastservice'
|
||||
import Tooltip from 'primevue/tooltip'
|
||||
import { createApp } from 'vue'
|
||||
import * as Vue from 'vue'
|
||||
|
||||
import '@/assets/css/style.css'
|
||||
import router from '@/router'
|
||||
@@ -59,3 +60,5 @@ app
|
||||
.use(pinia)
|
||||
.use(i18n)
|
||||
.mount('#vue-app')
|
||||
|
||||
window.Vue = Vue
|
||||
|
||||
@@ -332,102 +332,122 @@ const zNodeBadgeMode = z.enum(
|
||||
Object.values(NodeBadgeMode) as [string, ...string[]]
|
||||
)
|
||||
|
||||
const zSettings = z.record(z.any()).and(
|
||||
z
|
||||
.object({
|
||||
'Comfy.ColorPalette': z.string(),
|
||||
'Comfy.CustomColorPalettes': colorPalettesSchema,
|
||||
'Comfy.ConfirmClear': z.boolean(),
|
||||
'Comfy.DevMode': z.boolean(),
|
||||
'Comfy.Workflow.ShowMissingNodesWarning': z.boolean(),
|
||||
'Comfy.Workflow.ShowMissingModelsWarning': z.boolean(),
|
||||
'Comfy.DisableFloatRounding': z.boolean(),
|
||||
'Comfy.DisableSliders': z.boolean(),
|
||||
'Comfy.DOMClippingEnabled': z.boolean(),
|
||||
'Comfy.EditAttention.Delta': z.number(),
|
||||
'Comfy.EnableTooltips': z.boolean(),
|
||||
'Comfy.EnableWorkflowViewRestore': z.boolean(),
|
||||
'Comfy.FloatRoundingPrecision': z.number(),
|
||||
'Comfy.Graph.CanvasInfo': z.boolean(),
|
||||
'Comfy.Graph.CanvasMenu': z.boolean(),
|
||||
'Comfy.Graph.CtrlShiftZoom': z.boolean(),
|
||||
'Comfy.Graph.LinkMarkers': z.nativeEnum(LinkMarkerShape),
|
||||
'Comfy.Graph.ZoomSpeed': z.number(),
|
||||
'Comfy.Group.DoubleClickTitleToEdit': z.boolean(),
|
||||
'Comfy.GroupSelectedNodes.Padding': z.number(),
|
||||
'Comfy.InvertMenuScrolling': z.boolean(),
|
||||
'Comfy.Locale': z.string(),
|
||||
'Comfy.Logging.Enabled': z.boolean(),
|
||||
'Comfy.NodeLibrary.Bookmarks': z.array(z.string()),
|
||||
'Comfy.NodeLibrary.Bookmarks.V2': z.array(z.string()),
|
||||
'Comfy.NodeLibrary.BookmarksCustomization': z.record(
|
||||
z.string(),
|
||||
zBookmarkCustomization
|
||||
),
|
||||
'Comfy.LinkRelease.Action': zLinkReleaseTriggerAction,
|
||||
'Comfy.LinkRelease.ActionShift': zLinkReleaseTriggerAction,
|
||||
'Comfy.ModelLibrary.AutoLoadAll': z.boolean(),
|
||||
'Comfy.ModelLibrary.NameFormat': z.enum(['filename', 'title']),
|
||||
'Comfy.NodeSearchBoxImpl.NodePreview': z.boolean(),
|
||||
'Comfy.NodeSearchBoxImpl': z.enum(['default', 'simple']),
|
||||
'Comfy.NodeSearchBoxImpl.ShowCategory': z.boolean(),
|
||||
'Comfy.NodeSearchBoxImpl.ShowIdName': z.boolean(),
|
||||
'Comfy.NodeSearchBoxImpl.ShowNodeFrequency': z.boolean(),
|
||||
'Comfy.NodeSuggestions.number': z.number(),
|
||||
'Comfy.Node.BypassAllLinksOnDelete': z.boolean(),
|
||||
'Comfy.Node.Opacity': z.number(),
|
||||
'Comfy.Node.MiddleClickRerouteNode': z.boolean(),
|
||||
'Comfy.Node.ShowDeprecated': z.boolean(),
|
||||
'Comfy.Node.ShowExperimental': z.boolean(),
|
||||
'Comfy.Pointer.ClickBufferTime': z.number(),
|
||||
'Comfy.Pointer.ClickDrift': z.number(),
|
||||
'Comfy.Pointer.DoubleClickTime': z.number(),
|
||||
'Comfy.PreviewFormat': z.string(),
|
||||
'Comfy.PromptFilename': z.boolean(),
|
||||
'Comfy.Sidebar.Location': z.enum(['left', 'right']),
|
||||
'Comfy.Sidebar.Size': z.enum(['small', 'normal']),
|
||||
'Comfy.SwitchUser': z.any(),
|
||||
'Comfy.SnapToGrid.GridSize': z.number(),
|
||||
'Comfy.TextareaWidget.FontSize': z.number(),
|
||||
'Comfy.TextareaWidget.Spellcheck': z.boolean(),
|
||||
'Comfy.UseNewMenu': z.enum(['Disabled', 'Top', 'Bottom']),
|
||||
'Comfy.TreeExplorer.ItemPadding': z.number(),
|
||||
'Comfy.Validation.Workflows': z.boolean(),
|
||||
'Comfy.Validation.NodeDefs': z.boolean(),
|
||||
'Comfy.Workflow.SortNodeIdOnSave': z.boolean(),
|
||||
'Comfy.Queue.ImageFit': z.enum(['contain', 'cover']),
|
||||
'Comfy.Workflow.WorkflowTabsPosition': z.enum([
|
||||
'Sidebar',
|
||||
'Topbar',
|
||||
'Topbar (2nd-row)'
|
||||
]),
|
||||
'Comfy.Node.DoubleClickTitleToEdit': z.boolean(),
|
||||
'Comfy.WidgetControlMode': z.enum(['before', 'after']),
|
||||
'Comfy.Window.UnloadConfirmation': z.boolean(),
|
||||
'Comfy.NodeBadge.NodeSourceBadgeMode': zNodeBadgeMode,
|
||||
'Comfy.NodeBadge.NodeIdBadgeMode': zNodeBadgeMode,
|
||||
'Comfy.NodeBadge.NodeLifeCycleBadgeMode': zNodeBadgeMode,
|
||||
'Comfy.QueueButton.BatchCountLimit': z.number(),
|
||||
'Comfy.Queue.MaxHistoryItems': z.number(),
|
||||
'Comfy.Keybinding.UnsetBindings': z.array(zKeybinding),
|
||||
'Comfy.Keybinding.NewBindings': z.array(zKeybinding),
|
||||
'Comfy.Extension.Disabled': z.array(z.string()),
|
||||
'Comfy.Settings.ExtensionPanel': z.boolean(),
|
||||
'Comfy.LinkRenderMode': z.number(),
|
||||
'Comfy.Node.AutoSnapLinkToSlot': z.boolean(),
|
||||
'Comfy.Node.SnapHighlightsNode': z.boolean(),
|
||||
'Comfy.Server.ServerConfigValues': z.record(z.string(), z.any()),
|
||||
'Comfy.Server.LaunchArgs': z.record(z.string(), z.string()),
|
||||
'LiteGraph.Canvas.MaximumFps': z.number(),
|
||||
'Comfy.Workflow.ConfirmDelete': z.boolean(),
|
||||
'Comfy.Workflow.AutoSaveDelay': z.number(),
|
||||
'Comfy.Workflow.AutoSave': z.enum(['off', 'after delay']),
|
||||
'Comfy.RerouteBeta': z.boolean(),
|
||||
'LiteGraph.Canvas.LowQualityRenderingZoomThreshold': z.number(),
|
||||
'Comfy.Canvas.SelectionToolbox': z.boolean()
|
||||
})
|
||||
.optional()
|
||||
)
|
||||
const zSettings = z.object({
|
||||
'Comfy.ColorPalette': z.string(),
|
||||
'Comfy.CustomColorPalettes': colorPalettesSchema,
|
||||
'Comfy.ConfirmClear': z.boolean(),
|
||||
'Comfy.DevMode': z.boolean(),
|
||||
'Comfy.Workflow.ShowMissingNodesWarning': z.boolean(),
|
||||
'Comfy.Workflow.ShowMissingModelsWarning': z.boolean(),
|
||||
'Comfy.DisableFloatRounding': z.boolean(),
|
||||
'Comfy.DisableSliders': z.boolean(),
|
||||
'Comfy.DOMClippingEnabled': z.boolean(),
|
||||
'Comfy.EditAttention.Delta': z.number(),
|
||||
'Comfy.EnableTooltips': z.boolean(),
|
||||
'Comfy.EnableWorkflowViewRestore': z.boolean(),
|
||||
'Comfy.FloatRoundingPrecision': z.number(),
|
||||
'Comfy.Graph.CanvasInfo': z.boolean(),
|
||||
'Comfy.Graph.CanvasMenu': z.boolean(),
|
||||
'Comfy.Graph.CtrlShiftZoom': z.boolean(),
|
||||
'Comfy.Graph.LinkMarkers': z.nativeEnum(LinkMarkerShape),
|
||||
'Comfy.Graph.ZoomSpeed': z.number(),
|
||||
'Comfy.Group.DoubleClickTitleToEdit': z.boolean(),
|
||||
'Comfy.GroupSelectedNodes.Padding': z.number(),
|
||||
'Comfy.InvertMenuScrolling': z.boolean(),
|
||||
'Comfy.Locale': z.string(),
|
||||
'Comfy.Logging.Enabled': z.boolean(),
|
||||
'Comfy.NodeLibrary.Bookmarks': z.array(z.string()),
|
||||
'Comfy.NodeLibrary.Bookmarks.V2': z.array(z.string()),
|
||||
'Comfy.NodeLibrary.BookmarksCustomization': z.record(
|
||||
z.string(),
|
||||
zBookmarkCustomization
|
||||
),
|
||||
'Comfy.LinkRelease.Action': zLinkReleaseTriggerAction,
|
||||
'Comfy.LinkRelease.ActionShift': zLinkReleaseTriggerAction,
|
||||
'Comfy.ModelLibrary.AutoLoadAll': z.boolean(),
|
||||
'Comfy.ModelLibrary.NameFormat': z.enum(['filename', 'title']),
|
||||
'Comfy.NodeSearchBoxImpl.NodePreview': z.boolean(),
|
||||
'Comfy.NodeSearchBoxImpl': z.enum(['default', 'simple']),
|
||||
'Comfy.NodeSearchBoxImpl.ShowCategory': z.boolean(),
|
||||
'Comfy.NodeSearchBoxImpl.ShowIdName': z.boolean(),
|
||||
'Comfy.NodeSearchBoxImpl.ShowNodeFrequency': z.boolean(),
|
||||
'Comfy.NodeSuggestions.number': z.number(),
|
||||
'Comfy.Node.BypassAllLinksOnDelete': z.boolean(),
|
||||
'Comfy.Node.Opacity': z.number(),
|
||||
'Comfy.Node.MiddleClickRerouteNode': z.boolean(),
|
||||
'Comfy.Node.ShowDeprecated': z.boolean(),
|
||||
'Comfy.Node.ShowExperimental': z.boolean(),
|
||||
'Comfy.Pointer.ClickBufferTime': z.number(),
|
||||
'Comfy.Pointer.ClickDrift': z.number(),
|
||||
'Comfy.Pointer.DoubleClickTime': z.number(),
|
||||
'Comfy.PreviewFormat': z.string(),
|
||||
'Comfy.PromptFilename': z.boolean(),
|
||||
'Comfy.Sidebar.Location': z.enum(['left', 'right']),
|
||||
'Comfy.Sidebar.Size': z.enum(['small', 'normal']),
|
||||
'Comfy.SwitchUser': z.any(),
|
||||
'Comfy.SnapToGrid.GridSize': z.number(),
|
||||
'Comfy.TextareaWidget.FontSize': z.number(),
|
||||
'Comfy.TextareaWidget.Spellcheck': z.boolean(),
|
||||
'Comfy.UseNewMenu': z.enum(['Disabled', 'Top', 'Bottom']),
|
||||
'Comfy.TreeExplorer.ItemPadding': z.number(),
|
||||
'Comfy.Validation.Workflows': z.boolean(),
|
||||
'Comfy.Validation.NodeDefs': z.boolean(),
|
||||
'Comfy.Workflow.SortNodeIdOnSave': z.boolean(),
|
||||
'Comfy.Queue.ImageFit': z.enum(['contain', 'cover']),
|
||||
'Comfy.Workflow.WorkflowTabsPosition': z.enum([
|
||||
'Sidebar',
|
||||
'Topbar',
|
||||
'Topbar (2nd-row)'
|
||||
]),
|
||||
'Comfy.Node.DoubleClickTitleToEdit': z.boolean(),
|
||||
'Comfy.WidgetControlMode': z.enum(['before', 'after']),
|
||||
'Comfy.Window.UnloadConfirmation': z.boolean(),
|
||||
'Comfy.NodeBadge.NodeSourceBadgeMode': zNodeBadgeMode,
|
||||
'Comfy.NodeBadge.NodeIdBadgeMode': zNodeBadgeMode,
|
||||
'Comfy.NodeBadge.NodeLifeCycleBadgeMode': zNodeBadgeMode,
|
||||
'Comfy.QueueButton.BatchCountLimit': z.number(),
|
||||
'Comfy.Queue.MaxHistoryItems': z.number(),
|
||||
'Comfy.Keybinding.UnsetBindings': z.array(zKeybinding),
|
||||
'Comfy.Keybinding.NewBindings': z.array(zKeybinding),
|
||||
'Comfy.Extension.Disabled': z.array(z.string()),
|
||||
'Comfy.Settings.ExtensionPanel': z.boolean(),
|
||||
'Comfy.LinkRenderMode': z.number(),
|
||||
'Comfy.Node.AutoSnapLinkToSlot': z.boolean(),
|
||||
'Comfy.Node.SnapHighlightsNode': z.boolean(),
|
||||
'Comfy.Server.ServerConfigValues': z.record(z.string(), z.any()),
|
||||
'Comfy.Server.LaunchArgs': z.record(z.string(), z.string()),
|
||||
'LiteGraph.Canvas.MaximumFps': z.number(),
|
||||
'Comfy.Workflow.ConfirmDelete': z.boolean(),
|
||||
'Comfy.Workflow.AutoSaveDelay': z.number(),
|
||||
'Comfy.Workflow.AutoSave': z.enum(['off', 'after delay']),
|
||||
'Comfy.RerouteBeta': z.boolean(),
|
||||
'LiteGraph.Canvas.LowQualityRenderingZoomThreshold': z.number(),
|
||||
'Comfy.Canvas.SelectionToolbox': z.boolean(),
|
||||
'LiteGraph.Node.TooltipDelay': z.number(),
|
||||
'Comfy.ComfirmClear': z.boolean(),
|
||||
'LiteGraph.ContextMenu.Scaling': z.boolean(),
|
||||
'LiteGraph.Reroute.SplineOffset': z.number(),
|
||||
'Comfy.Toast.DisableReconnectingToast': z.boolean(),
|
||||
'Comfy.Workflow.Persist': z.boolean(),
|
||||
'Comfy.TutorialCompleted': z.boolean(),
|
||||
'Comfy.Node.AllowImageSizeDraw': z.boolean(),
|
||||
'Comfy-Desktop.AutoUpdate': z.boolean(),
|
||||
'Comfy-Desktop.SendStatistics': z.boolean(),
|
||||
'Comfy-Desktop.WindowStyle': z.string(),
|
||||
'Comfy-Desktop.UV.PythonInstallMirror': z.string(),
|
||||
'Comfy-Desktop.UV.PypiInstallMirror': z.string(),
|
||||
'Comfy-Desktop.UV.TorchInstallMirror': z.string(),
|
||||
'Comfy.MaskEditor.UseNewEditor': z.boolean(),
|
||||
'Comfy.MaskEditor.BrushAdjustmentSpeed': z.number(),
|
||||
'Comfy.MaskEditor.UseDominantAxis': z.boolean(),
|
||||
'pysssss.SnapToGrid': z.boolean(),
|
||||
/** VHS setting is used for queue video preview support. */
|
||||
'VHS.AdvancedPreviews': z.boolean(),
|
||||
/** Settings used for testing */
|
||||
'test.setting': z.any(),
|
||||
'main.sub.setting.name': z.any(),
|
||||
'single.setting': z.any()
|
||||
})
|
||||
|
||||
export type EmbeddingsResponse = z.infer<typeof zEmbeddingsResponse>
|
||||
export type ExtensionsResponse = z.infer<typeof zExtensionsResponse>
|
||||
|
||||
@@ -13,11 +13,10 @@ export interface ComfyCommand {
|
||||
label?: string | (() => string)
|
||||
icon?: string | (() => string)
|
||||
tooltip?: string | (() => string)
|
||||
/** Menubar item label, if different from command label */
|
||||
menubarLabel?: string | (() => string)
|
||||
menubarLabel?: string | (() => string) // Menubar item label, if different from command label
|
||||
versionAdded?: string
|
||||
/** If non-nullish, this command will prompt for confirmation. */
|
||||
confirmation?: string
|
||||
confirmation?: string // If non-nullish, this command will prompt for confirmation
|
||||
source?: string
|
||||
}
|
||||
|
||||
export class ComfyCommandImpl implements ComfyCommand {
|
||||
@@ -29,6 +28,7 @@ export class ComfyCommandImpl implements ComfyCommand {
|
||||
_menubarLabel?: string | (() => string)
|
||||
versionAdded?: string
|
||||
confirmation?: string
|
||||
source?: string
|
||||
|
||||
constructor(command: ComfyCommand) {
|
||||
this.id = command.id
|
||||
@@ -39,6 +39,7 @@ export class ComfyCommandImpl implements ComfyCommand {
|
||||
this._menubarLabel = command.menubarLabel ?? command.label
|
||||
this.versionAdded = command.versionAdded
|
||||
this.confirmation = command.confirmation
|
||||
this.source = command.source
|
||||
}
|
||||
|
||||
get label() {
|
||||
@@ -105,7 +106,10 @@ export const useCommandStore = defineStore('command', () => {
|
||||
const loadExtensionCommands = (extension: ComfyExtension) => {
|
||||
if (extension.commands) {
|
||||
for (const command of extension.commands) {
|
||||
registerCommand(command)
|
||||
registerCommand({
|
||||
...command,
|
||||
source: extension.name
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,6 +283,47 @@ export const useKeybindingStore = defineStore('keybinding', () => {
|
||||
userUnsetKeybindings.value = {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the keybinding for a given command to its default value.
|
||||
*
|
||||
* @param commandId - The commandId of the keybind to be reset
|
||||
* @returns `true` if changes were made, `false` if not
|
||||
*/
|
||||
function resetKeybindingForCommand(commandId: string): boolean {
|
||||
const currentKeybinding = getKeybindingByCommandId(commandId)
|
||||
const defaultKeybinding =
|
||||
defaultKeybindingsByCommandId.value[commandId]?.[0]
|
||||
|
||||
// No default keybinding exists, need to remove any user binding
|
||||
if (!defaultKeybinding) {
|
||||
if (currentKeybinding) {
|
||||
unsetKeybinding(currentKeybinding)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Current binding equals default binding, no changes needed
|
||||
if (currentKeybinding?.equals(defaultKeybinding)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Unset current keybinding if exists
|
||||
if (currentKeybinding) {
|
||||
unsetKeybinding(currentKeybinding)
|
||||
}
|
||||
|
||||
// Remove the unset record if it exists
|
||||
const serializedCombo = defaultKeybinding.combo.serialize()
|
||||
if (
|
||||
userUnsetKeybindings.value[serializedCombo]?.equals(defaultKeybinding)
|
||||
) {
|
||||
delete userUnsetKeybindings.value[serializedCombo]
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function isCommandKeybindingModified(commandId: string): boolean {
|
||||
const currentKeybinding: KeybindingImpl | undefined =
|
||||
getKeybindingByCommandId(commandId)
|
||||
@@ -307,6 +348,7 @@ export const useKeybindingStore = defineStore('keybinding', () => {
|
||||
unsetKeybinding,
|
||||
updateKeybindingOnCommand,
|
||||
resetAllKeybindings,
|
||||
resetKeybindingForCommand,
|
||||
isCommandKeybindingModified
|
||||
}
|
||||
})
|
||||
|
||||
@@ -49,7 +49,8 @@ export const useBottomPanelStore = defineStore('bottomPanel', () => {
|
||||
id: `Workspace.ToggleBottomPanelTab.${tab.id}`,
|
||||
icon: 'pi pi-list',
|
||||
label: `Toggle ${tab.title} Bottom Panel`,
|
||||
function: () => toggleBottomPanelTab(tab.id)
|
||||
function: () => toggleBottomPanelTab(tab.id),
|
||||
source: 'System'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,8 @@ export const useSidebarTabStore = defineStore('sidebarTab', () => {
|
||||
versionAdded: '1.3.9',
|
||||
function: () => {
|
||||
toggleSidebarTab(tab.id)
|
||||
}
|
||||
},
|
||||
source: 'System'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import type { Settings } from '@/schemas/apiSchema'
|
||||
import { useColorPaletteService } from '@/services/colorPaletteService'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import type { SidebarTabExtension, ToastManager } from '@/types/extensionTypes'
|
||||
@@ -31,8 +32,11 @@ export const useWorkspaceStore = defineStore('workspace', () => {
|
||||
const sidebarTab = computed(() => useSidebarTabStore())
|
||||
const setting = computed(() => ({
|
||||
settings: useSettingStore().settingsById,
|
||||
get: useSettingStore().get,
|
||||
set: useSettingStore().set
|
||||
// Allow generic key access to settings as custom nodes may add their
|
||||
// own settings which is not tracked by the `Setting` schema.
|
||||
get: (key: string) => useSettingStore().get(key as keyof Settings),
|
||||
set: (key: string, value: unknown) =>
|
||||
useSettingStore().set(key as keyof Settings, value)
|
||||
}))
|
||||
const workflow = computed(() => useWorkflowStore())
|
||||
const colorPalette = useColorPaletteService()
|
||||
|
||||
@@ -10,6 +10,11 @@ export type PackField = keyof RegistryPack | null
|
||||
export const IsInstallingKey: InjectionKey<Ref<boolean>> =
|
||||
Symbol('isInstalling')
|
||||
|
||||
export enum ManagerWsQueueStatus {
|
||||
DONE = 'done',
|
||||
IN_PROGRESS = 'in_progress'
|
||||
}
|
||||
|
||||
export enum ManagerTab {
|
||||
All = 'all',
|
||||
Installed = 'installed',
|
||||
|
||||
@@ -1,8 +1,57 @@
|
||||
import type {
|
||||
DeviceStats,
|
||||
EmbeddingsResponse,
|
||||
ExtensionsResponse,
|
||||
LogEntry,
|
||||
LogsRawResponse,
|
||||
NodeError,
|
||||
PromptResponse,
|
||||
Settings,
|
||||
SystemStats,
|
||||
TerminalSize,
|
||||
User,
|
||||
UserData,
|
||||
UserDataFullInfo
|
||||
} from '@/schemas/apiSchema'
|
||||
import { ComfyApp } from '@/scripts/app'
|
||||
|
||||
import type {
|
||||
BottomPanelExtension,
|
||||
CommandManager,
|
||||
ExtensionManager,
|
||||
SidebarTabExtension,
|
||||
ToastManager,
|
||||
ToastMessageOptions
|
||||
} from './extensionTypes'
|
||||
|
||||
export type { ComfyExtension } from './comfy'
|
||||
export type { ComfyApi } from '@/scripts/api'
|
||||
export type { ComfyApp } from '@/scripts/app'
|
||||
export type { ComfyNodeDef } from '@/schemas/nodeDefSchema'
|
||||
export type { InputSpec } from '@/schemas/nodeDefSchema'
|
||||
export type {
|
||||
EmbeddingsResponse,
|
||||
ExtensionsResponse,
|
||||
PromptResponse,
|
||||
NodeError,
|
||||
Settings,
|
||||
DeviceStats,
|
||||
SystemStats,
|
||||
User,
|
||||
UserData,
|
||||
UserDataFullInfo,
|
||||
TerminalSize,
|
||||
LogEntry,
|
||||
LogsRawResponse
|
||||
}
|
||||
export type {
|
||||
SidebarTabExtension,
|
||||
BottomPanelExtension,
|
||||
ToastManager,
|
||||
ExtensionManager,
|
||||
CommandManager,
|
||||
ToastMessageOptions
|
||||
}
|
||||
|
||||
declare global {
|
||||
const app: ComfyApp
|
||||
|
||||
@@ -286,4 +286,139 @@ describe('useKeybindingStore', () => {
|
||||
expect(store.getKeybinding(newKeybinding.combo)?.commandId).toBe('command2')
|
||||
expect(store.getKeybindingsByCommandId('command1')).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('should return false when no default or current keybinding exists during reset', () => {
|
||||
const store = useKeybindingStore()
|
||||
const result = store.resetKeybindingForCommand('nonexistent.command')
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
it('should return false when current keybinding equals default keybinding', () => {
|
||||
const store = useKeybindingStore()
|
||||
const defaultKeybinding = new KeybindingImpl({
|
||||
commandId: 'test.command',
|
||||
combo: { key: 'L', ctrl: true }
|
||||
})
|
||||
|
||||
store.addDefaultKeybinding(defaultKeybinding)
|
||||
const result = store.resetKeybindingForCommand('test.command')
|
||||
|
||||
expect(result).toBe(false)
|
||||
expect(store.keybindings).toHaveLength(1)
|
||||
expect(store.getKeybindingByCommandId('test.command')).toEqual(
|
||||
defaultKeybinding
|
||||
)
|
||||
})
|
||||
|
||||
it('should unset user keybinding when no default keybinding exists and return true', () => {
|
||||
const store = useKeybindingStore()
|
||||
const userKeybinding = new KeybindingImpl({
|
||||
commandId: 'test.command',
|
||||
combo: { key: 'M', ctrl: true }
|
||||
})
|
||||
|
||||
store.addUserKeybinding(userKeybinding)
|
||||
expect(store.keybindings).toHaveLength(1)
|
||||
|
||||
const result = store.resetKeybindingForCommand('test.command')
|
||||
|
||||
expect(result).toBe(true)
|
||||
expect(store.keybindings).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('should restore default keybinding when user has overridden it and return true', () => {
|
||||
const store = useKeybindingStore()
|
||||
|
||||
const defaultKeybinding = new KeybindingImpl({
|
||||
commandId: 'test.command',
|
||||
combo: { key: 'N', ctrl: true }
|
||||
})
|
||||
|
||||
const userKeybinding = new KeybindingImpl({
|
||||
commandId: 'test.command',
|
||||
combo: { key: 'O', alt: true }
|
||||
})
|
||||
|
||||
store.addDefaultKeybinding(defaultKeybinding)
|
||||
store.updateKeybindingOnCommand(userKeybinding)
|
||||
|
||||
expect(store.keybindings).toHaveLength(1)
|
||||
expect(store.getKeybindingByCommandId('test.command')).toEqual(
|
||||
userKeybinding
|
||||
)
|
||||
|
||||
const result = store.resetKeybindingForCommand('test.command')
|
||||
|
||||
expect(result).toBe(true)
|
||||
expect(store.keybindings).toHaveLength(1)
|
||||
expect(store.getKeybindingByCommandId('test.command')).toEqual(
|
||||
defaultKeybinding
|
||||
)
|
||||
})
|
||||
|
||||
it('should remove unset record and restore default keybinding when user has unset it', () => {
|
||||
const store = useKeybindingStore()
|
||||
|
||||
const defaultKeybinding = new KeybindingImpl({
|
||||
commandId: 'test.command',
|
||||
combo: { key: 'P', ctrl: true }
|
||||
})
|
||||
|
||||
store.addDefaultKeybinding(defaultKeybinding)
|
||||
|
||||
store.unsetKeybinding(defaultKeybinding)
|
||||
expect(store.keybindings).toHaveLength(0)
|
||||
|
||||
const serializedCombo = defaultKeybinding.combo.serialize()
|
||||
const userUnsetKeybindings = store.getUserUnsetKeybindings()
|
||||
expect(userUnsetKeybindings[serializedCombo]).toBeTruthy()
|
||||
expect(
|
||||
userUnsetKeybindings[serializedCombo].equals(defaultKeybinding)
|
||||
).toBe(true)
|
||||
|
||||
const result = store.resetKeybindingForCommand('test.command')
|
||||
|
||||
expect(result).toBe(true)
|
||||
expect(store.keybindings).toHaveLength(1)
|
||||
expect(store.getKeybindingByCommandId('test.command')).toEqual(
|
||||
defaultKeybinding
|
||||
)
|
||||
|
||||
expect(store.getUserUnsetKeybindings()[serializedCombo]).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should handle complex scenario with both unset and user keybindings', () => {
|
||||
const store = useKeybindingStore()
|
||||
|
||||
// Create default keybinding
|
||||
const defaultKeybinding = new KeybindingImpl({
|
||||
commandId: 'test.command',
|
||||
combo: { key: 'Q', ctrl: true }
|
||||
})
|
||||
store.addDefaultKeybinding(defaultKeybinding)
|
||||
|
||||
// Unset default keybinding
|
||||
store.unsetKeybinding(defaultKeybinding)
|
||||
expect(store.keybindings).toHaveLength(0)
|
||||
|
||||
// Add user keybinding with different combo
|
||||
const userKeybinding = new KeybindingImpl({
|
||||
commandId: 'test.command',
|
||||
combo: { key: 'R', alt: true }
|
||||
})
|
||||
store.addUserKeybinding(userKeybinding)
|
||||
expect(store.keybindings).toHaveLength(1)
|
||||
expect(store.getKeybindingByCommandId('test.command')).toEqual(
|
||||
userKeybinding
|
||||
)
|
||||
|
||||
// Reset keybinding to default
|
||||
const result = store.resetKeybindingForCommand('test.command')
|
||||
|
||||
expect(result).toBe(true)
|
||||
expect(store.keybindings).toHaveLength(1)
|
||||
expect(store.getKeybindingByCommandId('test.command')).toEqual(
|
||||
defaultKeybinding
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -189,11 +189,11 @@ describe('useSettingStore', () => {
|
||||
expect(storedValue).toEqual({ foo: 'bar', nested: { value: 123 } })
|
||||
})
|
||||
|
||||
it('should prevent mutations of retrieved objects', () => {
|
||||
it('should prevent mutations of retrieved objects', async () => {
|
||||
const initialValue = { foo: 'bar', nested: { value: 123 } }
|
||||
|
||||
// Set initial value
|
||||
store.set('test.setting', initialValue)
|
||||
await store.set('test.setting', initialValue)
|
||||
|
||||
// Get the value and try to mutate it
|
||||
const retrievedValue = store.get('test.setting')
|
||||
@@ -230,11 +230,11 @@ describe('useSettingStore', () => {
|
||||
expect(storedValue).toEqual([1, 2, { value: 3 }])
|
||||
})
|
||||
|
||||
it('should prevent mutations of retrieved arrays', () => {
|
||||
it('should prevent mutations of retrieved arrays', async () => {
|
||||
const initialArray = [1, 2, { value: 3 }]
|
||||
|
||||
// Set initial value
|
||||
store.set('test.setting', initialArray)
|
||||
await store.set('test.setting', initialArray)
|
||||
|
||||
// Get the value and try to mutate it
|
||||
const retrievedArray = store.get('test.setting')
|
||||
|
||||
Reference in New Issue
Block a user