mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-25 14:57:37 +00:00
Compare commits
4 Commits
feat-deskt
...
implement-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
892a9b6068 | ||
|
|
dfc38083fe | ||
|
|
1f055686c3 | ||
|
|
f145a89c66 |
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"version": "1.22.2",
|
||||
"version": "1.22.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"version": "1.22.2",
|
||||
"version": "1.22.1",
|
||||
"license": "GPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@alloc/quick-lru": "^5.2.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"private": true,
|
||||
"version": "1.22.2",
|
||||
"version": "1.22.1",
|
||||
"type": "module",
|
||||
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
|
||||
"homepage": "https://comfy.org",
|
||||
|
||||
@@ -93,14 +93,7 @@
|
||||
import { whenever } from '@vueuse/core'
|
||||
import { merge } from 'lodash'
|
||||
import Button from 'primevue/button'
|
||||
import {
|
||||
computed,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
ref,
|
||||
watch
|
||||
} from 'vue'
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import ContentDivider from '@/components/common/ContentDivider.vue'
|
||||
@@ -113,7 +106,6 @@ import PackCard from '@/components/dialog/content/manager/packCard/PackCard.vue'
|
||||
import RegistrySearchBar from '@/components/dialog/content/manager/registrySearchBar/RegistrySearchBar.vue'
|
||||
import GridSkeleton from '@/components/dialog/content/manager/skeleton/GridSkeleton.vue'
|
||||
import { useResponsiveCollapse } from '@/composables/element/useResponsiveCollapse'
|
||||
import { useManagerStatePersistence } from '@/composables/manager/useManagerStatePersistence'
|
||||
import { useInstalledPacks } from '@/composables/nodePack/useInstalledPacks'
|
||||
import { usePackUpdateStatus } from '@/composables/nodePack/usePackUpdateStatus'
|
||||
import { useWorkflowPacks } from '@/composables/nodePack/useWorkflowPacks'
|
||||
@@ -124,15 +116,13 @@ import type { TabItem } from '@/types/comfyManagerTypes'
|
||||
import { ManagerTab } from '@/types/comfyManagerTypes'
|
||||
import { components } from '@/types/comfyRegistryTypes'
|
||||
|
||||
const { initialTab } = defineProps<{
|
||||
initialTab?: ManagerTab
|
||||
const { initialTab = ManagerTab.All } = defineProps<{
|
||||
initialTab: ManagerTab
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const comfyManagerStore = useComfyManagerStore()
|
||||
const { getPackById } = useComfyRegistryStore()
|
||||
const persistedState = useManagerStatePersistence()
|
||||
const initialState = persistedState.loadStoredState()
|
||||
|
||||
const GRID_STYLE = {
|
||||
display: 'grid',
|
||||
@@ -166,10 +156,8 @@ const tabs = ref<TabItem[]>([
|
||||
icon: 'pi-sync'
|
||||
}
|
||||
])
|
||||
|
||||
const initialTabId = initialTab ?? initialState.selectedTabId
|
||||
const selectedTab = ref<TabItem>(
|
||||
tabs.value.find((tab) => tab.id === initialTabId) || tabs.value[0]
|
||||
tabs.value.find((tab) => tab.id === initialTab) || tabs.value[0]
|
||||
)
|
||||
|
||||
const {
|
||||
@@ -180,11 +168,7 @@ const {
|
||||
searchMode,
|
||||
sortField,
|
||||
suggestions
|
||||
} = useRegistrySearch({
|
||||
initialSortField: initialState.sortField,
|
||||
initialSearchMode: initialState.searchMode,
|
||||
initialSearchQuery: initialState.searchQuery
|
||||
})
|
||||
} = useRegistrySearch()
|
||||
pageNumber.value = 0
|
||||
const onApproachEnd = () => {
|
||||
pageNumber.value++
|
||||
@@ -248,11 +232,7 @@ watch(
|
||||
|
||||
if (!isEmptySearch.value) {
|
||||
displayPacks.value = filterOutdatedPacks(installedPacks.value)
|
||||
} else if (
|
||||
!installedPacks.value.length &&
|
||||
!installedPacksReady.value &&
|
||||
!isLoadingInstalled.value
|
||||
) {
|
||||
} else if (!installedPacks.value.length) {
|
||||
await startFetchInstalled()
|
||||
} else {
|
||||
displayPacks.value = filterOutdatedPacks(installedPacks.value)
|
||||
@@ -446,13 +426,7 @@ whenever(selectedNodePack, async () => {
|
||||
if (data?.id === pack.id) {
|
||||
lastFetchedPackId.value = pack.id
|
||||
const mergedPack = merge({}, pack, data)
|
||||
// Update the pack in current selection without changing selection state
|
||||
const packIndex = selectedNodePacks.value.findIndex(
|
||||
(p) => p.id === mergedPack.id
|
||||
)
|
||||
if (packIndex !== -1) {
|
||||
selectedNodePacks.value.splice(packIndex, 1, mergedPack)
|
||||
}
|
||||
selectedNodePacks.value = [mergedPack]
|
||||
// Replace pack in displayPacks so that children receive a fresh prop reference
|
||||
const idx = displayPacks.value.findIndex((p) => p.id === mergedPack.id)
|
||||
if (idx !== -1) {
|
||||
@@ -472,15 +446,6 @@ watch(searchQuery, () => {
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
persistedState.persistState({
|
||||
selectedTabId: selectedTab.value?.id,
|
||||
searchQuery: searchQuery.value,
|
||||
searchMode: searchMode.value,
|
||||
sortField: sortField.value
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
getPackById.cancel()
|
||||
})
|
||||
|
||||
@@ -62,7 +62,6 @@ describe('PackVersionBadge', () => {
|
||||
return mount(PackVersionBadge, {
|
||||
props: {
|
||||
nodePack: mockNodePack,
|
||||
isSelected: false,
|
||||
...props
|
||||
},
|
||||
global: {
|
||||
@@ -163,58 +162,4 @@ describe('PackVersionBadge', () => {
|
||||
// Verify that the hide method was called
|
||||
expect(mockHide).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
describe('selection state changes', () => {
|
||||
it('closes the popover when card is deselected', async () => {
|
||||
const wrapper = mountComponent({
|
||||
props: { isSelected: true }
|
||||
})
|
||||
|
||||
// Change isSelected from true to false
|
||||
await wrapper.setProps({ isSelected: false })
|
||||
await nextTick()
|
||||
|
||||
// Verify that the hide method was called
|
||||
expect(mockHide).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not close the popover when card is selected', async () => {
|
||||
const wrapper = mountComponent({
|
||||
props: { isSelected: false }
|
||||
})
|
||||
|
||||
// Change isSelected from false to true
|
||||
await wrapper.setProps({ isSelected: true })
|
||||
await nextTick()
|
||||
|
||||
// Verify that the hide method was NOT called
|
||||
expect(mockHide).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not close the popover when isSelected remains false', async () => {
|
||||
const wrapper = mountComponent({
|
||||
props: { isSelected: false }
|
||||
})
|
||||
|
||||
// Change isSelected from false to false (no change)
|
||||
await wrapper.setProps({ isSelected: false })
|
||||
await nextTick()
|
||||
|
||||
// Verify that the hide method was NOT called
|
||||
expect(mockHide).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not close the popover when isSelected remains true', async () => {
|
||||
const wrapper = mountComponent({
|
||||
props: { isSelected: true }
|
||||
})
|
||||
|
||||
// Change isSelected from true to true (no change)
|
||||
await wrapper.setProps({ isSelected: true })
|
||||
await nextTick()
|
||||
|
||||
// Verify that the hide method was NOT called
|
||||
expect(mockHide).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<script setup lang="ts">
|
||||
import Button from 'primevue/button'
|
||||
import Popover from 'primevue/popover'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import PackVersionSelectorPopover from '@/components/dialog/content/manager/PackVersionSelectorPopover.vue'
|
||||
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
|
||||
@@ -43,9 +43,8 @@ import { isSemVer } from '@/utils/formatUtil'
|
||||
|
||||
const TRUNCATED_HASH_LENGTH = 7
|
||||
|
||||
const { nodePack, isSelected } = defineProps<{
|
||||
const { nodePack } = defineProps<{
|
||||
nodePack: components['schemas']['Node']
|
||||
isSelected: boolean
|
||||
}>()
|
||||
|
||||
const popoverRef = ref()
|
||||
@@ -70,14 +69,4 @@ const toggleVersionSelector = (event: Event) => {
|
||||
const closeVersionSelector = () => {
|
||||
popoverRef.value.hide()
|
||||
}
|
||||
|
||||
// If the card is unselected, automatically close the version selector popover
|
||||
watch(
|
||||
() => isSelected,
|
||||
(isSelected, wasSelected) => {
|
||||
if (wasSelected && !isSelected) {
|
||||
closeVersionSelector()
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -191,100 +191,6 @@ describe('PackVersionSelectorPopover', () => {
|
||||
expect(mockGetPackVersions).toHaveBeenCalledWith(newNodePack.id)
|
||||
})
|
||||
|
||||
describe('nodePack.id changes', () => {
|
||||
it('re-fetches versions when nodePack.id changes', async () => {
|
||||
// Set up the mock for the initial fetch
|
||||
mockGetPackVersions.mockResolvedValueOnce(defaultMockVersions)
|
||||
|
||||
const wrapper = mountComponent()
|
||||
await waitForPromises()
|
||||
|
||||
// Verify initial fetch
|
||||
expect(mockGetPackVersions).toHaveBeenCalledTimes(1)
|
||||
expect(mockGetPackVersions).toHaveBeenCalledWith(mockNodePack.id)
|
||||
|
||||
// Set up the mock for the second fetch
|
||||
const newVersions = [
|
||||
{ version: '2.0.0', createdAt: '2023-06-01' },
|
||||
{ version: '1.9.0', createdAt: '2023-05-01' }
|
||||
]
|
||||
mockGetPackVersions.mockResolvedValueOnce(newVersions)
|
||||
|
||||
// Update the nodePack with a new ID
|
||||
const newNodePack = {
|
||||
...mockNodePack,
|
||||
id: 'different-pack',
|
||||
name: 'Different Pack'
|
||||
}
|
||||
await wrapper.setProps({ nodePack: newNodePack })
|
||||
await waitForPromises()
|
||||
|
||||
// Should fetch versions for the new nodePack
|
||||
expect(mockGetPackVersions).toHaveBeenCalledTimes(2)
|
||||
expect(mockGetPackVersions).toHaveBeenLastCalledWith(newNodePack.id)
|
||||
|
||||
// Check that new versions are displayed
|
||||
const listbox = wrapper.findComponent(Listbox)
|
||||
const options = listbox.props('options')!
|
||||
expect(options.some((o) => o.value === '2.0.0')).toBe(true)
|
||||
expect(options.some((o) => o.value === '1.9.0')).toBe(true)
|
||||
})
|
||||
|
||||
it('does not re-fetch when nodePack changes but id remains the same', async () => {
|
||||
// Set up the mock for the initial fetch
|
||||
mockGetPackVersions.mockResolvedValueOnce(defaultMockVersions)
|
||||
|
||||
const wrapper = mountComponent()
|
||||
await waitForPromises()
|
||||
|
||||
// Verify initial fetch
|
||||
expect(mockGetPackVersions).toHaveBeenCalledTimes(1)
|
||||
|
||||
// Update the nodePack with same ID but different properties
|
||||
const updatedNodePack = {
|
||||
...mockNodePack,
|
||||
name: 'Updated Test Pack',
|
||||
description: 'New description'
|
||||
}
|
||||
await wrapper.setProps({ nodePack: updatedNodePack })
|
||||
await waitForPromises()
|
||||
|
||||
// Should NOT fetch versions again
|
||||
expect(mockGetPackVersions).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('maintains selected version when switching to a new pack', async () => {
|
||||
// Set up the mock for the initial fetch
|
||||
mockGetPackVersions.mockResolvedValueOnce(defaultMockVersions)
|
||||
|
||||
const wrapper = mountComponent()
|
||||
await waitForPromises()
|
||||
|
||||
// Select a specific version
|
||||
const listbox = wrapper.findComponent(Listbox)
|
||||
await listbox.setValue('0.9.0')
|
||||
expect(listbox.props('modelValue')).toBe('0.9.0')
|
||||
|
||||
// Set up the mock for the second fetch
|
||||
mockGetPackVersions.mockResolvedValueOnce([
|
||||
{ version: '3.0.0', createdAt: '2023-07-01' },
|
||||
{ version: '0.9.0', createdAt: '2023-04-01' }
|
||||
])
|
||||
|
||||
// Update to a new pack that also has version 0.9.0
|
||||
const newNodePack = {
|
||||
id: 'another-pack',
|
||||
name: 'Another Pack',
|
||||
latest_version: { version: '3.0.0' }
|
||||
}
|
||||
await wrapper.setProps({ nodePack: newNodePack })
|
||||
await waitForPromises()
|
||||
|
||||
// Selected version should remain the same if available
|
||||
expect(listbox.props('modelValue')).toBe('0.9.0')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Unclaimed GitHub packs handling', () => {
|
||||
it('falls back to nightly when no versions exist', async () => {
|
||||
// Set up the mock to return versions
|
||||
|
||||
@@ -62,7 +62,7 @@ import { whenever } from '@vueuse/core'
|
||||
import Button from 'primevue/button'
|
||||
import Listbox from 'primevue/listbox'
|
||||
import ProgressSpinner from 'primevue/progressspinner'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import ContentDivider from '@/components/common/ContentDivider.vue'
|
||||
@@ -161,11 +161,9 @@ const onNodePackChange = async () => {
|
||||
}
|
||||
|
||||
whenever(
|
||||
() => nodePack.id,
|
||||
(nodePackId, oldNodePackId) => {
|
||||
if (nodePackId !== oldNodePackId) {
|
||||
void onNodePackChange()
|
||||
}
|
||||
() => nodePack,
|
||||
() => {
|
||||
void onNodePackChange()
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
)
|
||||
@@ -184,4 +182,8 @@ const handleSubmit = async () => {
|
||||
isQueueing.value = false
|
||||
emit('submit')
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
managerStore.installPack.clear()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
/>
|
||||
</MetadataRow>
|
||||
<MetadataRow :label="t('manager.version')">
|
||||
<PackVersionBadge :node-pack="nodePack" :is-selected="true" />
|
||||
<PackVersionBadge :node-pack="nodePack" />
|
||||
</MetadataRow>
|
||||
</div>
|
||||
<div class="mb-6 overflow-hidden">
|
||||
@@ -118,15 +118,7 @@ const onNodePackChange = () => {
|
||||
y.value = 0
|
||||
}
|
||||
|
||||
whenever(
|
||||
() => nodePack.id,
|
||||
(nodePackId, oldNodePackId) => {
|
||||
if (nodePackId !== oldNodePackId) {
|
||||
onNodePackChange()
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
whenever(() => nodePack, onNodePackChange, { immediate: true, deep: true })
|
||||
</script>
|
||||
<style scoped>
|
||||
.hidden-scrollbar {
|
||||
|
||||
@@ -51,7 +51,6 @@ const isLoading = ref(false)
|
||||
const registryNodeDefs = shallowRef<ListComfyNodesResponse | null>(null)
|
||||
|
||||
const fetchNodeDefs = async () => {
|
||||
getNodeDefs.cancel()
|
||||
isLoading.value = true
|
||||
|
||||
const { id: packId } = nodePack
|
||||
|
||||
@@ -1,37 +1,11 @@
|
||||
<template>
|
||||
<div :style="{ width: cssWidth, height: cssHeight }" class="overflow-hidden">
|
||||
<!-- default banner show -->
|
||||
<div v-if="showDefaultBanner" class="w-full h-full">
|
||||
<img
|
||||
:src="DEFAULT_BANNER"
|
||||
alt="default banner"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<!-- banner_url or icon show -->
|
||||
<div v-else class="relative w-full h-full">
|
||||
<!-- blur background -->
|
||||
<div
|
||||
v-if="imgSrc"
|
||||
class="absolute inset-0 bg-cover bg-center bg-no-repeat opacity-30"
|
||||
:style="{
|
||||
backgroundImage: `url(${imgSrc})`,
|
||||
filter: 'blur(10px)'
|
||||
}"
|
||||
></div>
|
||||
<!-- image -->
|
||||
<img
|
||||
:src="isImageError ? DEFAULT_BANNER : imgSrc"
|
||||
:alt="nodePack.name + ' banner'"
|
||||
:class="
|
||||
isImageError
|
||||
? 'relative w-full h-full object-cover z-10'
|
||||
: 'relative w-full h-full object-contain z-10'
|
||||
"
|
||||
@error="isImageError = true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<img
|
||||
:src="isImageError ? DEFAULT_BANNER : imgSrc"
|
||||
:alt="nodePack.name + ' banner'"
|
||||
class="object-cover"
|
||||
:style="{ width: cssWidth, height: cssHeight }"
|
||||
@error="isImageError = true"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -46,15 +20,18 @@ const {
|
||||
width = '100%',
|
||||
height = '12rem'
|
||||
} = defineProps<{
|
||||
nodePack: components['schemas']['Node']
|
||||
nodePack: components['schemas']['Node'] & { banner?: string } // Temporary measure until banner is in backend
|
||||
width?: string
|
||||
height?: string
|
||||
}>()
|
||||
|
||||
const isImageError = ref(false)
|
||||
|
||||
const showDefaultBanner = computed(() => !nodePack.banner_url && !nodePack.icon)
|
||||
const imgSrc = computed(() => nodePack.banner_url || nodePack.icon)
|
||||
const shouldShowFallback = computed(
|
||||
() => !nodePack.banner || nodePack.banner.trim() === '' || isImageError.value
|
||||
)
|
||||
const imgSrc = computed(() =>
|
||||
shouldShowFallback.value ? DEFAULT_BANNER : nodePack.banner
|
||||
)
|
||||
|
||||
const convertToCssValue = (value: string | number) =>
|
||||
typeof value === 'number' ? `${value}rem` : value
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<Card
|
||||
class="w-full h-full inline-flex flex-col justify-between items-start overflow-hidden rounded-lg shadow-elevation-3 dark-theme:bg-dark-elevation-2 transition-all duration-200"
|
||||
class="w-full h-full inline-flex flex-col justify-between items-start overflow-hidden rounded-2xl shadow-elevation-3 dark-theme:bg-dark-elevation-2 transition-all duration-200"
|
||||
:class="{
|
||||
'selected-card': isSelected,
|
||||
'outline outline-[6px] outline-[var(--p-primary-color)]': isSelected,
|
||||
'opacity-60': isDisabled
|
||||
}"
|
||||
:pt="{
|
||||
body: { class: 'p-0 flex flex-col w-full h-full rounded-lg gap-0' },
|
||||
content: { class: 'flex-1 flex flex-col rounded-lg min-h-0' },
|
||||
body: { class: 'p-0 flex flex-col w-full h-full rounded-2xl gap-0' },
|
||||
content: { class: 'flex-1 flex flex-col rounded-2xl min-h-0' },
|
||||
title: { class: 'w-full h-full rounded-t-lg cursor-pointer' },
|
||||
footer: { class: 'p-0 m-0' }
|
||||
}"
|
||||
@@ -70,10 +70,7 @@
|
||||
>
|
||||
<i class="pi pi-arrow-circle-up text-blue-600" />
|
||||
</div>
|
||||
<PackVersionBadge
|
||||
:node-pack="nodePack"
|
||||
:is-selected="isSelected"
|
||||
/>
|
||||
<PackVersionBadge :node-pack="nodePack" />
|
||||
</div>
|
||||
<div
|
||||
v-if="formattedLatestVersionDate"
|
||||
@@ -116,15 +113,11 @@ import PackBanner from '@/components/dialog/content/manager/packBanner/PackBanne
|
||||
import PackCardFooter from '@/components/dialog/content/manager/packCard/PackCardFooter.vue'
|
||||
import { usePackUpdateStatus } from '@/composables/nodePack/usePackUpdateStatus'
|
||||
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
|
||||
import {
|
||||
IsInstallingKey,
|
||||
type MergedNodePack,
|
||||
type RegistryPack,
|
||||
isMergedNodePack
|
||||
} from '@/types/comfyManagerTypes'
|
||||
import { IsInstallingKey } from '@/types/comfyManagerTypes'
|
||||
import type { components } from '@/types/comfyRegistryTypes'
|
||||
|
||||
const { nodePack, isSelected = false } = defineProps<{
|
||||
nodePack: MergedNodePack | RegistryPack
|
||||
nodePack: components['schemas']['Node']
|
||||
isSelected?: boolean
|
||||
}>()
|
||||
|
||||
@@ -143,9 +136,9 @@ const isDisabled = computed(
|
||||
|
||||
whenever(isInstalled, () => (isInstalling.value = false))
|
||||
|
||||
const nodesCount = computed(() =>
|
||||
isMergedNodePack(nodePack) ? nodePack.comfy_nodes?.length : undefined
|
||||
)
|
||||
// TODO: remove type assertion once comfy_nodes is added to node (pack) info type in backend
|
||||
const nodesCount = computed(() => (nodePack as any).comfy_nodes?.length)
|
||||
|
||||
const publisherName = computed(() => {
|
||||
if (!nodePack) return null
|
||||
|
||||
@@ -161,22 +154,3 @@ const formattedLatestVersionDate = computed(() => {
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.selected-card {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.selected-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border: 3px solid var(--p-primary-color);
|
||||
border-radius: 0.5rem;
|
||||
pointer-events: none;
|
||||
z-index: 100;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
<i class="pi pi-download text-muted"></i>
|
||||
<span>{{ formattedDownloads }}</span>
|
||||
</div>
|
||||
<PackInstallButton v-if="!isInstalled" :node-packs="[nodePack]" />
|
||||
<PackEnableToggle v-else :node-pack="nodePack" />
|
||||
<PackInstallButton :node-packs="[nodePack]" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -15,18 +14,13 @@
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import PackEnableToggle from '@/components/dialog/content/manager/button/PackEnableToggle.vue'
|
||||
import PackInstallButton from '@/components/dialog/content/manager/button/PackInstallButton.vue'
|
||||
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
|
||||
import type { components } from '@/types/comfyRegistryTypes'
|
||||
|
||||
const { nodePack } = defineProps<{
|
||||
nodePack: components['schemas']['Node']
|
||||
}>()
|
||||
|
||||
const { isPackInstalled } = useComfyManagerStore()
|
||||
const isInstalled = computed(() => isPackInstalled(nodePack?.id))
|
||||
|
||||
const { n } = useI18n()
|
||||
|
||||
const formattedDownloads = computed(() =>
|
||||
|
||||
@@ -56,7 +56,7 @@ import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import SearchFilterDropdown from '@/components/dialog/content/manager/registrySearchBar/SearchFilterDropdown.vue'
|
||||
import type { NodesIndexSuggestion } from '@/types/algoliaTypes'
|
||||
import type { NodesIndexSuggestion } from '@/services/algoliaSearchService'
|
||||
import {
|
||||
type SearchOption,
|
||||
SortableAlgoliaField
|
||||
|
||||
@@ -86,6 +86,7 @@ import { useSettingStore } from '@/stores/settingStore'
|
||||
import { useToastStore } from '@/stores/toastStore'
|
||||
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
import { getCurrentVersion } from '@/utils/versioning'
|
||||
|
||||
const emit = defineEmits<{
|
||||
ready: []
|
||||
@@ -300,6 +301,13 @@ onMounted(async () => {
|
||||
CORE_SETTINGS.forEach((setting) => {
|
||||
settingStore.addSetting(setting)
|
||||
})
|
||||
if (!settingStore.get('Comfy.InstalledVersion')) {
|
||||
const currentVersion =
|
||||
Object.keys(settingStore.settingValues).length > 0
|
||||
? '0.0.1'
|
||||
: getCurrentVersion()
|
||||
await settingStore.set('Comfy.InstalledVersion', currentVersion)
|
||||
}
|
||||
// @ts-expect-error fixme ts strict error
|
||||
await comfyApp.setup(canvasRef.value)
|
||||
canvasStore.canvas = comfyApp.canvas
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
import {
|
||||
ManagerState,
|
||||
ManagerTab,
|
||||
SortableAlgoliaField
|
||||
} from '@/types/comfyManagerTypes'
|
||||
|
||||
const STORAGE_KEY = 'Comfy.Manager.UI.State'
|
||||
|
||||
export const useManagerStatePersistence = () => {
|
||||
/**
|
||||
* Load the UI state from localStorage.
|
||||
*/
|
||||
const loadStoredState = (): ManagerState => {
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEY)
|
||||
if (stored) {
|
||||
return JSON.parse(stored)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to load manager UI state:', e)
|
||||
}
|
||||
return {
|
||||
selectedTabId: ManagerTab.All,
|
||||
searchQuery: '',
|
||||
searchMode: 'packs',
|
||||
sortField: SortableAlgoliaField.Downloads
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist the UI state to localStorage.
|
||||
*/
|
||||
const persistState = (state: ManagerState) => {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(state))
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the UI state to the default values.
|
||||
*/
|
||||
const reset = () => {
|
||||
persistState({
|
||||
selectedTabId: ManagerTab.All,
|
||||
searchQuery: '',
|
||||
searchMode: 'packs',
|
||||
sortField: SortableAlgoliaField.Downloads
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
loadStoredState,
|
||||
persistState,
|
||||
reset
|
||||
}
|
||||
}
|
||||
@@ -658,19 +658,19 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
{
|
||||
id: 'Comfy.Manager.CustomNodesManager',
|
||||
icon: 'pi pi-puzzle',
|
||||
label: 'Toggle the Custom Nodes Manager',
|
||||
label: 'Custom Nodes Manager',
|
||||
versionAdded: '1.12.10',
|
||||
function: () => {
|
||||
dialogService.toggleManagerDialog()
|
||||
dialogService.showManagerDialog()
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Manager.ToggleManagerProgressDialog',
|
||||
icon: 'pi pi-spinner',
|
||||
label: 'Toggle the Custom Nodes Manager Progress Bar',
|
||||
label: 'Toggle Progress Dialog',
|
||||
versionAdded: '1.13.9',
|
||||
function: () => {
|
||||
dialogService.toggleManagerProgressDialog()
|
||||
dialogService.showManagerProgressDialog()
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -15,10 +15,7 @@ export const useProgressFavicon = () => {
|
||||
if (isIdle) {
|
||||
favicon.value = defaultFavicon
|
||||
} else {
|
||||
const frame = Math.min(
|
||||
Math.max(0, Math.floor(progress * totalFrames)),
|
||||
totalFrames - 1
|
||||
)
|
||||
const frame = Math.floor(progress * totalFrames)
|
||||
favicon.value = `/assets/images/favicon_progress_16x16/frame_${frame}.png`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import { watchDebounced } from '@vueuse/core'
|
||||
import type { Hit } from 'algoliasearch/dist/lite/browser'
|
||||
import { memoize, orderBy } from 'lodash'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { computed, onUnmounted, ref, watch } from 'vue'
|
||||
|
||||
import { useAlgoliaSearchService } from '@/services/algoliaSearchService'
|
||||
import type {
|
||||
import {
|
||||
AlgoliaNodePack,
|
||||
NodesIndexSuggestion,
|
||||
SearchAttribute
|
||||
} from '@/types/algoliaTypes'
|
||||
SearchAttribute,
|
||||
useAlgoliaSearchService
|
||||
} from '@/services/algoliaSearchService'
|
||||
import type { NodesIndexSuggestion } from '@/services/algoliaSearchService'
|
||||
import { SortableAlgoliaField } from '@/types/comfyManagerTypes'
|
||||
|
||||
const SEARCH_DEBOUNCE_TIME = 320
|
||||
const DEFAULT_PAGE_SIZE = 64
|
||||
const DEFAULT_SORT_FIELD = SortableAlgoliaField.Downloads // Set in the index configuration
|
||||
const DEFAULT_MAX_CACHE_SIZE = 64
|
||||
const SORT_DIRECTIONS: Record<SortableAlgoliaField, 'asc' | 'desc'> = {
|
||||
[SortableAlgoliaField.Downloads]: 'desc',
|
||||
[SortableAlgoliaField.Created]: 'desc',
|
||||
@@ -29,25 +30,18 @@ const isDateField = (field: SortableAlgoliaField): boolean =>
|
||||
/**
|
||||
* Composable for managing UI state of Comfy Node Registry search.
|
||||
*/
|
||||
export function useRegistrySearch(options: {
|
||||
initialSortField?: SortableAlgoliaField
|
||||
initialSearchMode?: 'nodes' | 'packs'
|
||||
initialSearchQuery?: string
|
||||
initialPageNumber?: number
|
||||
}) {
|
||||
const {
|
||||
initialSortField = SortableAlgoliaField.Downloads,
|
||||
initialSearchMode = 'packs',
|
||||
initialSearchQuery = '',
|
||||
initialPageNumber = 0
|
||||
} = options
|
||||
|
||||
export function useRegistrySearch(
|
||||
options: {
|
||||
maxCacheSize?: number
|
||||
} = {}
|
||||
) {
|
||||
const { maxCacheSize = DEFAULT_MAX_CACHE_SIZE } = options
|
||||
const isLoading = ref(false)
|
||||
const sortField = ref<SortableAlgoliaField>(initialSortField)
|
||||
const searchMode = ref<'nodes' | 'packs'>(initialSearchMode)
|
||||
const sortField = ref<SortableAlgoliaField>(SortableAlgoliaField.Downloads)
|
||||
const searchMode = ref<'nodes' | 'packs'>('packs')
|
||||
const pageSize = ref(DEFAULT_PAGE_SIZE)
|
||||
const pageNumber = ref(initialPageNumber)
|
||||
const searchQuery = ref(initialSearchQuery)
|
||||
const pageNumber = ref(0)
|
||||
const searchQuery = ref('')
|
||||
const results = ref<AlgoliaNodePack[]>([])
|
||||
const suggestions = ref<NodesIndexSuggestion[]>([])
|
||||
|
||||
@@ -68,7 +62,9 @@ export function useRegistrySearch(options: {
|
||||
)
|
||||
|
||||
const { searchPacksCached, toRegistryPack, clearSearchPacksCache } =
|
||||
useAlgoliaSearchService()
|
||||
useAlgoliaSearchService({
|
||||
maxCacheSize
|
||||
})
|
||||
|
||||
const algoliaToRegistry = memoize(
|
||||
toRegistryPack,
|
||||
@@ -128,6 +124,8 @@ export function useRegistrySearch(options: {
|
||||
immediate: true
|
||||
})
|
||||
|
||||
onUnmounted(clearSearchPacksCache)
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
pageNumber,
|
||||
@@ -137,7 +135,6 @@ export function useRegistrySearch(options: {
|
||||
searchQuery,
|
||||
suggestions,
|
||||
searchResults: resultsAsRegistryPacks,
|
||||
nodeSearchResults: resultsAsNodes,
|
||||
clearCache: clearSearchPacksCache
|
||||
nodeSearchResults: resultsAsNodes
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,10 @@ export const CORE_SETTINGS: SettingParams[] = [
|
||||
name: 'Action on link release (No modifier)',
|
||||
type: 'combo',
|
||||
options: Object.values(LinkReleaseTriggerAction),
|
||||
defaultValue: LinkReleaseTriggerAction.CONTEXT_MENU
|
||||
defaultValue: LinkReleaseTriggerAction.CONTEXT_MENU,
|
||||
defaultsByInstallVersion: {
|
||||
'1.21.3': LinkReleaseTriggerAction.SEARCH_BOX
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'Comfy.LinkRelease.ActionShift',
|
||||
@@ -747,6 +750,13 @@ export const CORE_SETTINGS: SettingParams[] = [
|
||||
defaultValue: false,
|
||||
versionAdded: '1.8.7'
|
||||
},
|
||||
{
|
||||
id: 'Comfy.InstalledVersion',
|
||||
name: 'Installed version',
|
||||
type: 'hidden',
|
||||
defaultValue: null,
|
||||
versionAdded: '1.22.1'
|
||||
},
|
||||
{
|
||||
id: 'LiteGraph.ContextMenu.Scaling',
|
||||
name: 'Scale node combo widget menus (lists) when zoomed in',
|
||||
|
||||
@@ -43,15 +43,6 @@ import { checkMirrorReachable } from '@/utils/networkUtil'
|
||||
defaultValue: true,
|
||||
onChange: onChangeRestartApp
|
||||
},
|
||||
{
|
||||
id: 'Comfy-Desktop.LaunchOptions',
|
||||
category: ['Comfy-Desktop', 'General', 'Launch Options'],
|
||||
name: 'Custom Launch Arguments',
|
||||
tooltip:
|
||||
'Custom command line arguments for launching the backend (e.g., --cpu). Requires restart.',
|
||||
type: 'text',
|
||||
defaultValue: ''
|
||||
},
|
||||
{
|
||||
id: 'Comfy-Desktop.WindowStyle',
|
||||
category: ['Comfy-Desktop', 'General', 'Window Style'],
|
||||
|
||||
@@ -5,7 +5,7 @@ import { EventManagerInterface, PreviewManagerInterface } from './interfaces'
|
||||
|
||||
export class PreviewManager implements PreviewManagerInterface {
|
||||
previewCamera: THREE.Camera
|
||||
previewContainer: HTMLDivElement = null!
|
||||
previewContainer: HTMLDivElement = {} as HTMLDivElement
|
||||
showPreview: boolean = true
|
||||
previewWidth: number = 120
|
||||
|
||||
|
||||
@@ -147,10 +147,10 @@
|
||||
"label": "Load Default Workflow"
|
||||
},
|
||||
"Comfy_Manager_CustomNodesManager": {
|
||||
"label": "Toggle the Custom Nodes Manager"
|
||||
"label": "Custom Nodes Manager"
|
||||
},
|
||||
"Comfy_Manager_ToggleManagerProgressDialog": {
|
||||
"label": "Toggle the Custom Nodes Manager Progress Bar"
|
||||
"label": "Toggle Progress Dialog"
|
||||
},
|
||||
"Comfy_MaskEditor_OpenMaskEditor": {
|
||||
"label": "Open Mask Editor for Selected Node"
|
||||
|
||||
@@ -828,8 +828,8 @@
|
||||
"ComfyUI Issues": "ComfyUI Issues",
|
||||
"Interrupt": "Interrupt",
|
||||
"Load Default Workflow": "Load Default Workflow",
|
||||
"Toggle the Custom Nodes Manager": "Toggle the Custom Nodes Manager",
|
||||
"Toggle the Custom Nodes Manager Progress Bar": "Toggle the Custom Nodes Manager Progress Bar",
|
||||
"Custom Nodes Manager": "Custom Nodes Manager",
|
||||
"Toggle Progress Dialog": "Toggle Progress Dialog",
|
||||
"Open Mask Editor for Selected Node": "Open Mask Editor for Selected Node",
|
||||
"New": "New",
|
||||
"Clipspace": "Clipspace",
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
"Comfy-Desktop_AutoUpdate": {
|
||||
"name": "Automatically check for updates"
|
||||
},
|
||||
"Comfy-Desktop_LaunchOptions": {
|
||||
"name": "Custom Launch Arguments",
|
||||
"tooltip": "Custom command line arguments for launching the backend (e.g., --cpu). Requires restart."
|
||||
},
|
||||
"Comfy-Desktop_SendStatistics": {
|
||||
"name": "Send anonymous usage metrics"
|
||||
},
|
||||
|
||||
@@ -694,6 +694,7 @@
|
||||
"ComfyUI Issues": "Problemas de ComfyUI",
|
||||
"Contact Support": "Contactar soporte",
|
||||
"Convert selected nodes to group node": "Convertir nodos seleccionados en nodo de grupo",
|
||||
"Custom Nodes Manager": "Gestor de nodos personalizados",
|
||||
"Delete Selected Items": "Eliminar elementos seleccionados",
|
||||
"Desktop User Guide": "Guía de usuario de escritorio",
|
||||
"Duplicate Current Workflow": "Duplicar flujo de trabajo actual",
|
||||
@@ -747,13 +748,12 @@
|
||||
"Toggle Logs Bottom Panel": "Alternar panel inferior de registros",
|
||||
"Toggle Model Library Sidebar": "Alternar barra lateral de biblioteca de modelos",
|
||||
"Toggle Node Library Sidebar": "Alternar barra lateral de biblioteca de nodos",
|
||||
"Toggle Progress Dialog": "Alternar diálogo de progreso",
|
||||
"Toggle Queue Sidebar": "Alternar barra lateral de cola",
|
||||
"Toggle Search Box": "Alternar caja de búsqueda",
|
||||
"Toggle Terminal Bottom Panel": "Alternar panel inferior de terminal",
|
||||
"Toggle Theme (Dark/Light)": "Alternar tema (Oscuro/Claro)",
|
||||
"Toggle Workflows Sidebar": "Alternar barra lateral de flujos de trabajo",
|
||||
"Toggle the Custom Nodes Manager": "Alternar el Administrador de Nodos Personalizados",
|
||||
"Toggle the Custom Nodes Manager Progress Bar": "Alternar la Barra de Progreso del Administrador de Nodos Personalizados",
|
||||
"Undo": "Deshacer",
|
||||
"Ungroup selected group nodes": "Desagrupar nodos de grupo seleccionados",
|
||||
"Workflow": "Flujo de trabajo",
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
"Comfy-Desktop_AutoUpdate": {
|
||||
"name": "Verificar actualizaciones automáticamente"
|
||||
},
|
||||
"Comfy-Desktop_LaunchOptions": {
|
||||
"name": "Argumentos de lanzamiento personalizados",
|
||||
"tooltip": "Argumentos personalizados de línea de comandos para iniciar el backend (por ejemplo, --cpu). Requiere reinicio."
|
||||
},
|
||||
"Comfy-Desktop_SendStatistics": {
|
||||
"name": "Enviar métricas de uso anónimas"
|
||||
},
|
||||
|
||||
@@ -694,6 +694,7 @@
|
||||
"ComfyUI Issues": "Problèmes de ComfyUI",
|
||||
"Contact Support": "Contacter le support",
|
||||
"Convert selected nodes to group node": "Convertir les nœuds sélectionnés en nœud de groupe",
|
||||
"Custom Nodes Manager": "Gestionnaire de Nœuds Personnalisés",
|
||||
"Delete Selected Items": "Supprimer les éléments sélectionnés",
|
||||
"Desktop User Guide": "Guide de l'utilisateur de bureau",
|
||||
"Duplicate Current Workflow": "Dupliquer le flux de travail actuel",
|
||||
@@ -747,13 +748,12 @@
|
||||
"Toggle Logs Bottom Panel": "Basculer le panneau inférieur des journaux",
|
||||
"Toggle Model Library Sidebar": "Basculer la barre latérale de la bibliothèque de modèles",
|
||||
"Toggle Node Library Sidebar": "Basculer la barre latérale de la bibliothèque de nœuds",
|
||||
"Toggle Progress Dialog": "Basculer la boîte de dialogue de progression",
|
||||
"Toggle Queue Sidebar": "Basculer la barre latérale de la file d'attente",
|
||||
"Toggle Search Box": "Basculer la boîte de recherche",
|
||||
"Toggle Terminal Bottom Panel": "Basculer le panneau inférieur du terminal",
|
||||
"Toggle Theme (Dark/Light)": "Basculer le thème (Sombre/Clair)",
|
||||
"Toggle Workflows Sidebar": "Basculer la barre latérale des flux de travail",
|
||||
"Toggle the Custom Nodes Manager": "Basculer le gestionnaire de nœuds personnalisés",
|
||||
"Toggle the Custom Nodes Manager Progress Bar": "Basculer la barre de progression du gestionnaire de nœuds personnalisés",
|
||||
"Undo": "Annuler",
|
||||
"Ungroup selected group nodes": "Dégrouper les nœuds de groupe sélectionnés",
|
||||
"Workflow": "Flux de travail",
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
"Comfy-Desktop_AutoUpdate": {
|
||||
"name": "Vérifier automatiquement les mises à jour"
|
||||
},
|
||||
"Comfy-Desktop_LaunchOptions": {
|
||||
"name": "Arguments de lancement personnalisés",
|
||||
"tooltip": "Arguments de ligne de commande personnalisés pour lancer le backend (par exemple, --cpu). Nécessite un redémarrage."
|
||||
},
|
||||
"Comfy-Desktop_SendStatistics": {
|
||||
"name": "Envoyer des métriques d'utilisation anonymes"
|
||||
},
|
||||
|
||||
@@ -694,6 +694,7 @@
|
||||
"ComfyUI Issues": "ComfyUIの問題",
|
||||
"Contact Support": "サポートに連絡",
|
||||
"Convert selected nodes to group node": "選択したノードをグループノードに変換",
|
||||
"Custom Nodes Manager": "カスタムノードマネージャ",
|
||||
"Delete Selected Items": "選択したアイテムを削除",
|
||||
"Desktop User Guide": "デスクトップユーザーガイド",
|
||||
"Duplicate Current Workflow": "現在のワークフローを複製",
|
||||
@@ -708,10 +709,10 @@
|
||||
"Interrupt": "中断",
|
||||
"Load Default Workflow": "デフォルトワークフローを読み込む",
|
||||
"Manage group nodes": "グループノードを管理",
|
||||
"Move Selected Nodes Down": "選択したノードを下へ移動",
|
||||
"Move Selected Nodes Left": "選択したノードを左へ移動",
|
||||
"Move Selected Nodes Right": "選択したノードを右へ移動",
|
||||
"Move Selected Nodes Up": "選択したノードを上へ移動",
|
||||
"Move Selected Nodes Down": "選択したノードを下に移動",
|
||||
"Move Selected Nodes Left": "選択したノードを左に移動",
|
||||
"Move Selected Nodes Right": "選択したノードを右に移動",
|
||||
"Move Selected Nodes Up": "選択したノードを上に移動",
|
||||
"Mute/Unmute Selected Nodes": "選択したノードのミュート/ミュート解除",
|
||||
"New": "新規",
|
||||
"Next Opened Workflow": "次に開いたワークフロー",
|
||||
@@ -747,13 +748,12 @@
|
||||
"Toggle Logs Bottom Panel": "ログパネル下部を切り替え",
|
||||
"Toggle Model Library Sidebar": "モデルライブラリサイドバーを切り替え",
|
||||
"Toggle Node Library Sidebar": "ノードライブラリサイドバーを切り替え",
|
||||
"Toggle Progress Dialog": "進行状況ダイアログの切り替え",
|
||||
"Toggle Queue Sidebar": "キューサイドバーを切り替え",
|
||||
"Toggle Search Box": "検索ボックスの切り替え",
|
||||
"Toggle Terminal Bottom Panel": "ターミナルパネル下部を切り替え",
|
||||
"Toggle Theme (Dark/Light)": "テーマを切り替え(ダーク/ライト)",
|
||||
"Toggle Workflows Sidebar": "ワークフローサイドバーを切り替え",
|
||||
"Toggle the Custom Nodes Manager": "カスタムノードマネージャーを切り替え",
|
||||
"Toggle the Custom Nodes Manager Progress Bar": "カスタムノードマネージャーの進行状況バーを切り替え",
|
||||
"Undo": "元に戻す",
|
||||
"Ungroup selected group nodes": "選択したグループノードのグループ解除",
|
||||
"Workflow": "ワークフロー",
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
"Comfy-Desktop_AutoUpdate": {
|
||||
"name": "自動的に更新を確認する"
|
||||
},
|
||||
"Comfy-Desktop_LaunchOptions": {
|
||||
"name": "カスタム起動引数",
|
||||
"tooltip": "バックエンドを起動するためのカスタムコマンドライン引数(例: --cpu)。再起動が必要です。"
|
||||
},
|
||||
"Comfy-Desktop_SendStatistics": {
|
||||
"name": "匿名の使用統計を送信する"
|
||||
},
|
||||
|
||||
@@ -694,6 +694,7 @@
|
||||
"ComfyUI Issues": "ComfyUI 이슈 페이지",
|
||||
"Contact Support": "고객 지원 문의",
|
||||
"Convert selected nodes to group node": "선택한 노드를 그룹 노드로 변환",
|
||||
"Custom Nodes Manager": "사용자 정의 노드 관리자",
|
||||
"Delete Selected Items": "선택한 항목 삭제",
|
||||
"Desktop User Guide": "데스크톱 사용자 가이드",
|
||||
"Duplicate Current Workflow": "현재 워크플로 복제",
|
||||
@@ -747,13 +748,12 @@
|
||||
"Toggle Logs Bottom Panel": "로그 하단 패널 전환",
|
||||
"Toggle Model Library Sidebar": "모델 라이브러리 사이드바 전환",
|
||||
"Toggle Node Library Sidebar": "노드 라이브러리 사이드바 전환",
|
||||
"Toggle Progress Dialog": "진행 상황 대화 상자 전환",
|
||||
"Toggle Queue Sidebar": "실행 대기열 사이드바 전환",
|
||||
"Toggle Search Box": "검색 상자 전환",
|
||||
"Toggle Terminal Bottom Panel": "터미널 하단 패널 전환",
|
||||
"Toggle Theme (Dark/Light)": "테마 전환 (어두운/밝은)",
|
||||
"Toggle Workflows Sidebar": "워크플로 사이드바 전환",
|
||||
"Toggle the Custom Nodes Manager": "커스텀 노드 매니저 전환",
|
||||
"Toggle the Custom Nodes Manager Progress Bar": "커스텀 노드 매니저 진행률 표시줄 전환",
|
||||
"Undo": "실행 취소",
|
||||
"Ungroup selected group nodes": "선택한 그룹 노드 그룹 해제",
|
||||
"Workflow": "워크플로",
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
"Comfy-Desktop_AutoUpdate": {
|
||||
"name": "자동 업데이트 확인"
|
||||
},
|
||||
"Comfy-Desktop_LaunchOptions": {
|
||||
"name": "사용자 지정 실행 인수",
|
||||
"tooltip": "백엔드 실행을 위한 사용자 지정 명령줄 인수입니다(예: --cpu). 재시작이 필요합니다."
|
||||
},
|
||||
"Comfy-Desktop_SendStatistics": {
|
||||
"name": "익명 사용 통계 보내기"
|
||||
},
|
||||
|
||||
@@ -694,6 +694,7 @@
|
||||
"ComfyUI Issues": "Проблемы ComfyUI",
|
||||
"Contact Support": "Связаться с поддержкой",
|
||||
"Convert selected nodes to group node": "Преобразовать выбранные ноды в групповую ноду",
|
||||
"Custom Nodes Manager": "Менеджер Пользовательских Узлов",
|
||||
"Delete Selected Items": "Удалить выбранные элементы",
|
||||
"Desktop User Guide": "Руководство пользователя для настольных ПК",
|
||||
"Duplicate Current Workflow": "Дублировать текущий рабочий процесс",
|
||||
@@ -747,13 +748,12 @@
|
||||
"Toggle Logs Bottom Panel": "Переключение нижней панели журналов",
|
||||
"Toggle Model Library Sidebar": "Переключение боковой панели библиотеки моделей",
|
||||
"Toggle Node Library Sidebar": "Переключение боковой панели библиотеки нод",
|
||||
"Toggle Progress Dialog": "Переключить диалоговое окно прогресса",
|
||||
"Toggle Queue Sidebar": "Переключение боковой панели очереди",
|
||||
"Toggle Search Box": "Переключить поисковую панель",
|
||||
"Toggle Terminal Bottom Panel": "Переключение нижней панели терминала",
|
||||
"Toggle Theme (Dark/Light)": "Переключение темы (Тёмная/Светлая)",
|
||||
"Toggle Workflows Sidebar": "Переключение боковой панели рабочих процессов",
|
||||
"Toggle the Custom Nodes Manager": "Переключить менеджер пользовательских узлов",
|
||||
"Toggle the Custom Nodes Manager Progress Bar": "Переключить индикатор выполнения менеджера пользовательских узлов",
|
||||
"Undo": "Отменить",
|
||||
"Ungroup selected group nodes": "Разгруппировать выбранные групповые ноды",
|
||||
"Workflow": "Рабочий процесс",
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
"Comfy-Desktop_AutoUpdate": {
|
||||
"name": "Автоматически проверять обновления"
|
||||
},
|
||||
"Comfy-Desktop_LaunchOptions": {
|
||||
"name": "Пользовательские параметры запуска",
|
||||
"tooltip": "Пользовательские аргументы командной строки для запуска backend (например, --cpu). Требуется перезапуск."
|
||||
},
|
||||
"Comfy-Desktop_SendStatistics": {
|
||||
"name": "Отправлять анонимную статистику использования"
|
||||
},
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
"label": "适应视图到选中节点"
|
||||
},
|
||||
"Comfy_Canvas_MoveSelectedNodes_Down": {
|
||||
"label": "下移选中的节点"
|
||||
"label": "下移所选节点"
|
||||
},
|
||||
"Comfy_Canvas_MoveSelectedNodes_Left": {
|
||||
"label": "向左移动选中节点"
|
||||
@@ -54,7 +54,7 @@
|
||||
"label": "向右移动选中节点"
|
||||
},
|
||||
"Comfy_Canvas_MoveSelectedNodes_Up": {
|
||||
"label": "上移选中的节点"
|
||||
"label": "上移所选节点"
|
||||
},
|
||||
"Comfy_Canvas_ResetView": {
|
||||
"label": "重置视图"
|
||||
|
||||
@@ -694,6 +694,7 @@
|
||||
"ComfyUI Issues": "ComfyUI 问题",
|
||||
"Contact Support": "联系支持",
|
||||
"Convert selected nodes to group node": "将选中节点转换为组节点",
|
||||
"Custom Nodes Manager": "自定义节点管理器",
|
||||
"Delete Selected Items": "删除选定的项目",
|
||||
"Desktop User Guide": "桌面端用户指南",
|
||||
"Duplicate Current Workflow": "复制当前工作流",
|
||||
@@ -747,13 +748,12 @@
|
||||
"Toggle Logs Bottom Panel": "切换日志底部面板",
|
||||
"Toggle Model Library Sidebar": "切换模型库侧边栏",
|
||||
"Toggle Node Library Sidebar": "切换节点库侧边栏",
|
||||
"Toggle Progress Dialog": "切换进度对话框",
|
||||
"Toggle Queue Sidebar": "切换队列侧边栏",
|
||||
"Toggle Search Box": "切换搜索框",
|
||||
"Toggle Terminal Bottom Panel": "切换终端底部面板",
|
||||
"Toggle Theme (Dark/Light)": "切换主题(暗/亮)",
|
||||
"Toggle Workflows Sidebar": "切换工作流侧边栏",
|
||||
"Toggle the Custom Nodes Manager": "切换自定义节点管理器",
|
||||
"Toggle the Custom Nodes Manager Progress Bar": "切换自定义节点管理器进度条",
|
||||
"Undo": "撤销",
|
||||
"Ungroup selected group nodes": "解散选中组节点",
|
||||
"Workflow": "工作流",
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
"Comfy-Desktop_AutoUpdate": {
|
||||
"name": "自动检查更新"
|
||||
},
|
||||
"Comfy-Desktop_LaunchOptions": {
|
||||
"name": "自定义启动参数",
|
||||
"tooltip": "用于启动后端的自定义命令行参数(例如:--cpu)。需要重启。"
|
||||
},
|
||||
"Comfy-Desktop_SendStatistics": {
|
||||
"name": "发送匿名使用情况统计"
|
||||
},
|
||||
|
||||
@@ -448,9 +448,9 @@ const zSettings = z.object({
|
||||
'Comfy.Toast.DisableReconnectingToast': z.boolean(),
|
||||
'Comfy.Workflow.Persist': z.boolean(),
|
||||
'Comfy.TutorialCompleted': z.boolean(),
|
||||
'Comfy.InstalledVersion': z.string().nullable(),
|
||||
'Comfy.Node.AllowImageSizeDraw': z.boolean(),
|
||||
'Comfy-Desktop.AutoUpdate': z.boolean(),
|
||||
'Comfy-Desktop.LaunchOptions': z.string(),
|
||||
'Comfy-Desktop.SendStatistics': z.boolean(),
|
||||
'Comfy-Desktop.WindowStyle': z.string(),
|
||||
'Comfy-Desktop.UV.PythonInstallMirror': z.string(),
|
||||
@@ -472,6 +472,7 @@ const zSettings = z.object({
|
||||
'VHS.AdvancedPreviews': z.string(),
|
||||
/** Settings used for testing */
|
||||
'test.setting': z.any(),
|
||||
'test.versionedSetting': z.any(),
|
||||
'main.sub.setting.name': z.any(),
|
||||
'single.setting': z.any(),
|
||||
'LiteGraph.Node.DefaultPadding': z.boolean(),
|
||||
|
||||
@@ -1,26 +1,68 @@
|
||||
import QuickLRU from '@alloc/quick-lru'
|
||||
import type {
|
||||
BaseSearchParamsWithoutQuery,
|
||||
Hit,
|
||||
SearchQuery,
|
||||
SearchResponse
|
||||
} from 'algoliasearch/dist/lite/browser'
|
||||
import { liteClient as algoliasearch } from 'algoliasearch/dist/lite/builds/browser'
|
||||
import { omit } from 'lodash'
|
||||
|
||||
import type {
|
||||
AlgoliaNodePack,
|
||||
NodesIndexSuggestion,
|
||||
SearchAttribute,
|
||||
SearchNodePacksParams,
|
||||
SearchPacksResult
|
||||
} from '@/types/algoliaTypes'
|
||||
import type { components } from '@/types/comfyRegistryTypes'
|
||||
import { components } from '@/types/comfyRegistryTypes'
|
||||
import { paramsToCacheKey } from '@/utils/formatUtil'
|
||||
|
||||
type RegistryNodePack = components['schemas']['Node']
|
||||
|
||||
const DEFAULT_MAX_CACHE_SIZE = 64
|
||||
const DEFAULT_MIN_CHARS_FOR_SUGGESTIONS = 2
|
||||
|
||||
type SafeNestedProperty<
|
||||
T,
|
||||
K1 extends keyof T,
|
||||
K2 extends keyof NonNullable<T[K1]>
|
||||
> = T[K1] extends undefined | null ? undefined : NonNullable<T[K1]>[K2]
|
||||
|
||||
type RegistryNodePack = components['schemas']['Node']
|
||||
type SearchPacksResult = {
|
||||
nodePacks: Hit<AlgoliaNodePack>[]
|
||||
querySuggestions: Hit<NodesIndexSuggestion>[]
|
||||
}
|
||||
|
||||
export interface AlgoliaNodePack {
|
||||
objectID: RegistryNodePack['id']
|
||||
name: RegistryNodePack['name']
|
||||
publisher_id: SafeNestedProperty<RegistryNodePack, 'publisher', 'id'>
|
||||
description: RegistryNodePack['description']
|
||||
comfy_nodes: string[]
|
||||
total_install: RegistryNodePack['downloads']
|
||||
id: RegistryNodePack['id']
|
||||
create_time: string
|
||||
update_time: SafeNestedProperty<
|
||||
RegistryNodePack,
|
||||
'latest_version',
|
||||
'createdAt'
|
||||
>
|
||||
license: RegistryNodePack['license']
|
||||
repository_url: RegistryNodePack['repository']
|
||||
status: RegistryNodePack['status']
|
||||
latest_version: SafeNestedProperty<
|
||||
RegistryNodePack,
|
||||
'latest_version',
|
||||
'version'
|
||||
>
|
||||
latest_version_status: SafeNestedProperty<
|
||||
RegistryNodePack,
|
||||
'latest_version',
|
||||
'status'
|
||||
>
|
||||
comfy_node_extract_status: SafeNestedProperty<
|
||||
RegistryNodePack,
|
||||
'latest_version',
|
||||
'comfy_node_extract_status'
|
||||
>
|
||||
icon_url: RegistryNodePack['icon']
|
||||
}
|
||||
|
||||
export type SearchAttribute = keyof AlgoliaNodePack
|
||||
|
||||
const RETRIEVE_ATTRIBUTES: SearchAttribute[] = [
|
||||
'comfy_nodes',
|
||||
'name',
|
||||
@@ -39,7 +81,33 @@ const RETRIEVE_ATTRIBUTES: SearchAttribute[] = [
|
||||
'icon_url'
|
||||
]
|
||||
|
||||
export interface NodesIndexSuggestion {
|
||||
nb_words: number
|
||||
nodes_index: {
|
||||
exact_nb_hits: number
|
||||
facets: {
|
||||
exact_matches: Record<string, number>
|
||||
analytics: Record<string, any>
|
||||
}
|
||||
}
|
||||
objectID: RegistryNodePack['id']
|
||||
popularity: number
|
||||
query: string
|
||||
}
|
||||
|
||||
type SearchNodePacksParams = BaseSearchParamsWithoutQuery & {
|
||||
pageSize: number
|
||||
pageNumber: number
|
||||
restrictSearchableAttributes: SearchAttribute[]
|
||||
}
|
||||
|
||||
interface AlgoliaSearchServiceOptions {
|
||||
/**
|
||||
* Maximum number of search results to store in the cache.
|
||||
* The cache is automatically cleared when the component is unmounted.
|
||||
* @default 64
|
||||
*/
|
||||
maxCacheSize?: number
|
||||
/**
|
||||
* Minimum number of characters for suggestions. An additional query
|
||||
* will be made to the suggestions/completions index for queries that
|
||||
@@ -49,15 +117,17 @@ interface AlgoliaSearchServiceOptions {
|
||||
minCharsForSuggestions?: number
|
||||
}
|
||||
|
||||
const searchPacksCache = new QuickLRU<string, SearchPacksResult>({
|
||||
maxSize: DEFAULT_MAX_CACHE_SIZE
|
||||
})
|
||||
|
||||
export const useAlgoliaSearchService = (
|
||||
options: AlgoliaSearchServiceOptions = {}
|
||||
) => {
|
||||
const { minCharsForSuggestions = DEFAULT_MIN_CHARS_FOR_SUGGESTIONS } = options
|
||||
const {
|
||||
maxCacheSize = DEFAULT_MAX_CACHE_SIZE,
|
||||
minCharsForSuggestions = DEFAULT_MIN_CHARS_FOR_SUGGESTIONS
|
||||
} = options
|
||||
const searchClient = algoliasearch(__ALGOLIA_APP_ID__, __ALGOLIA_API_KEY__)
|
||||
const searchPacksCache = new QuickLRU<string, SearchPacksResult>({
|
||||
maxSize: maxCacheSize
|
||||
})
|
||||
|
||||
const toRegistryLatestVersion = (
|
||||
algoliaNode: AlgoliaNodePack
|
||||
|
||||
@@ -21,6 +21,7 @@ import TemplateWorkflowsDialogHeader from '@/components/templates/TemplateWorkfl
|
||||
import { t } from '@/i18n'
|
||||
import type { ExecutionErrorWsMessage } from '@/schemas/apiSchema'
|
||||
import { type ShowDialogOptions, useDialogStore } from '@/stores/dialogStore'
|
||||
import { ManagerTab } from '@/types/comfyManagerTypes'
|
||||
|
||||
export type ConfirmationDialogType =
|
||||
| 'default'
|
||||
@@ -128,14 +129,16 @@ export const useDialogService = () => {
|
||||
}
|
||||
|
||||
function showManagerDialog(
|
||||
props: InstanceType<typeof ManagerDialogContent>['$props'] = {}
|
||||
props: InstanceType<typeof ManagerDialogContent>['$props'] = {
|
||||
initialTab: ManagerTab.All
|
||||
}
|
||||
) {
|
||||
dialogStore.showDialog({
|
||||
key: 'global-manager',
|
||||
component: ManagerDialogContent,
|
||||
headerComponent: ManagerHeader,
|
||||
dialogComponentProps: {
|
||||
closable: true,
|
||||
closable: false,
|
||||
pt: {
|
||||
header: { class: '!p-0 !m-0' },
|
||||
content: { class: '!px-0 h-[83vh] w-[90vw] overflow-y-hidden' }
|
||||
@@ -394,26 +397,6 @@ export const useDialogService = () => {
|
||||
}
|
||||
}
|
||||
|
||||
function toggleManagerDialog(
|
||||
props?: InstanceType<typeof ManagerDialogContent>['$props']
|
||||
) {
|
||||
if (dialogStore.isDialogOpen('global-manager')) {
|
||||
dialogStore.closeDialog({ key: 'global-manager' })
|
||||
} else {
|
||||
showManagerDialog(props)
|
||||
}
|
||||
}
|
||||
|
||||
function toggleManagerProgressDialog(
|
||||
props?: InstanceType<typeof ManagerProgressDialogContent>['$props']
|
||||
) {
|
||||
if (dialogStore.isDialogOpen('global-manager-progress-dialog')) {
|
||||
dialogStore.closeDialog({ key: 'global-manager-progress-dialog' })
|
||||
} else {
|
||||
showManagerProgressDialog({ props })
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
showLoadWorkflowWarning,
|
||||
showMissingModelsWarning,
|
||||
@@ -431,8 +414,6 @@ export const useDialogService = () => {
|
||||
showUpdatePasswordDialog,
|
||||
showExtensionDialog,
|
||||
prompt,
|
||||
confirm,
|
||||
toggleManagerDialog,
|
||||
toggleManagerProgressDialog
|
||||
confirm
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,16 +169,11 @@ export const useDialogStore = defineStore('dialog', () => {
|
||||
return dialog
|
||||
}
|
||||
|
||||
function isDialogOpen(key: string) {
|
||||
return dialogStack.value.some((d) => d.key === key)
|
||||
}
|
||||
|
||||
return {
|
||||
dialogStack,
|
||||
riseDialog,
|
||||
showDialog,
|
||||
closeDialog,
|
||||
showExtensionDialog,
|
||||
isDialogOpen
|
||||
showExtensionDialog
|
||||
}
|
||||
})
|
||||
|
||||
@@ -88,30 +88,54 @@ export const useExecutionStore = defineStore('execution', () => {
|
||||
if (!activePrompt.value) return 0
|
||||
const total = totalNodesToExecute.value
|
||||
const done = nodesExecuted.value
|
||||
return total > 0 ? done / total : 0
|
||||
return done / total
|
||||
})
|
||||
|
||||
function bindExecutionEvents() {
|
||||
api.addEventListener('execution_start', handleExecutionStart)
|
||||
api.addEventListener('execution_cached', handleExecutionCached)
|
||||
api.addEventListener('executed', handleExecuted)
|
||||
api.addEventListener('executing', handleExecuting)
|
||||
api.addEventListener('progress', handleProgress)
|
||||
api.addEventListener('status', handleStatus)
|
||||
api.addEventListener('execution_error', handleExecutionError)
|
||||
api.addEventListener(
|
||||
'execution_start',
|
||||
handleExecutionStart as EventListener
|
||||
)
|
||||
api.addEventListener(
|
||||
'execution_cached',
|
||||
handleExecutionCached as EventListener
|
||||
)
|
||||
api.addEventListener('executed', handleExecuted as EventListener)
|
||||
api.addEventListener('executing', handleExecuting as EventListener)
|
||||
api.addEventListener('progress', handleProgress as EventListener)
|
||||
api.addEventListener('status', handleStatus as EventListener)
|
||||
api.addEventListener(
|
||||
'execution_error',
|
||||
handleExecutionError as EventListener
|
||||
)
|
||||
}
|
||||
api.addEventListener('progress_text', handleProgressText)
|
||||
api.addEventListener('display_component', handleDisplayComponent)
|
||||
api.addEventListener('progress_text', handleProgressText as EventListener)
|
||||
api.addEventListener(
|
||||
'display_component',
|
||||
handleDisplayComponent as EventListener
|
||||
)
|
||||
|
||||
function unbindExecutionEvents() {
|
||||
api.removeEventListener('execution_start', handleExecutionStart)
|
||||
api.removeEventListener('execution_cached', handleExecutionCached)
|
||||
api.removeEventListener('executed', handleExecuted)
|
||||
api.removeEventListener('executing', handleExecuting)
|
||||
api.removeEventListener('progress', handleProgress)
|
||||
api.removeEventListener('status', handleStatus)
|
||||
api.removeEventListener('execution_error', handleExecutionError)
|
||||
api.removeEventListener('progress_text', handleProgressText)
|
||||
api.removeEventListener(
|
||||
'execution_start',
|
||||
handleExecutionStart as EventListener
|
||||
)
|
||||
api.removeEventListener(
|
||||
'execution_cached',
|
||||
handleExecutionCached as EventListener
|
||||
)
|
||||
api.removeEventListener('executed', handleExecuted as EventListener)
|
||||
api.removeEventListener('executing', handleExecuting as EventListener)
|
||||
api.removeEventListener('progress', handleProgress as EventListener)
|
||||
api.removeEventListener('status', handleStatus as EventListener)
|
||||
api.removeEventListener(
|
||||
'execution_error',
|
||||
handleExecutionError as EventListener
|
||||
)
|
||||
api.removeEventListener(
|
||||
'progress_text',
|
||||
handleProgressText as EventListener
|
||||
)
|
||||
}
|
||||
|
||||
function handleExecutionStart(e: CustomEvent<ExecutionStartWsMessage>) {
|
||||
@@ -160,7 +184,7 @@ export const useExecutionStore = defineStore('execution', () => {
|
||||
clientId.value = api.clientId
|
||||
|
||||
// Once we've received the clientId we no longer need to listen
|
||||
api.removeEventListener('status', handleStatus)
|
||||
api.removeEventListener('status', handleStatus as EventListener)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { api } from '@/scripts/api'
|
||||
import { app } from '@/scripts/app'
|
||||
import type { SettingParams } from '@/types/settingTypes'
|
||||
import type { TreeNode } from '@/types/treeExplorerTypes'
|
||||
import { compareVersions } from '@/utils/versioning'
|
||||
|
||||
export const getSettingInfo = (setting: SettingParams) => {
|
||||
const parts = setting.category || setting.id.split('.')
|
||||
@@ -83,6 +84,29 @@ export const useSettingStore = defineStore('setting', () => {
|
||||
*/
|
||||
function getDefaultValue<K extends keyof Settings>(key: K): Settings[K] {
|
||||
const param = settingsById.value[key]
|
||||
|
||||
// Check for versioned defaults based on installation version
|
||||
if (param?.defaultsByInstallVersion) {
|
||||
const installedVersion = get('Comfy.InstalledVersion')
|
||||
|
||||
if (installedVersion) {
|
||||
// Find the highest version that is <= installedVersion
|
||||
const sortedVersions = Object.keys(param.defaultsByInstallVersion).sort(
|
||||
(a, b) => compareVersions(a, b)
|
||||
)
|
||||
|
||||
for (const version of sortedVersions.reverse()) {
|
||||
if (compareVersions(installedVersion, version) >= 0) {
|
||||
const versionedDefault = param.defaultsByInstallVersion[version]
|
||||
return typeof versionedDefault === 'function'
|
||||
? versionedDefault()
|
||||
: versionedDefault
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to original defaultValue
|
||||
return typeof param?.defaultValue === 'function'
|
||||
? param.defaultValue()
|
||||
: param?.defaultValue
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
import type {
|
||||
BaseSearchParamsWithoutQuery,
|
||||
Hit
|
||||
} from 'algoliasearch/dist/lite/browser'
|
||||
|
||||
import type { components } from '@/types/comfyRegistryTypes'
|
||||
|
||||
type SafeNestedProperty<
|
||||
T,
|
||||
K1 extends keyof T,
|
||||
K2 extends keyof NonNullable<T[K1]>
|
||||
> = T[K1] extends undefined | null ? undefined : NonNullable<T[K1]>[K2]
|
||||
|
||||
type RegistryNodePack = components['schemas']['Node']
|
||||
export type SearchPacksResult = {
|
||||
nodePacks: Hit<AlgoliaNodePack>[]
|
||||
querySuggestions: Hit<NodesIndexSuggestion>[]
|
||||
}
|
||||
|
||||
export interface AlgoliaNodePack {
|
||||
objectID: RegistryNodePack['id']
|
||||
name: RegistryNodePack['name']
|
||||
publisher_id: SafeNestedProperty<RegistryNodePack, 'publisher', 'id'>
|
||||
description: RegistryNodePack['description']
|
||||
comfy_nodes: string[]
|
||||
total_install: RegistryNodePack['downloads']
|
||||
id: RegistryNodePack['id']
|
||||
create_time: string
|
||||
update_time: SafeNestedProperty<
|
||||
RegistryNodePack,
|
||||
'latest_version',
|
||||
'createdAt'
|
||||
>
|
||||
license: RegistryNodePack['license']
|
||||
repository_url: RegistryNodePack['repository']
|
||||
status: RegistryNodePack['status']
|
||||
latest_version: SafeNestedProperty<
|
||||
RegistryNodePack,
|
||||
'latest_version',
|
||||
'version'
|
||||
>
|
||||
latest_version_status: SafeNestedProperty<
|
||||
RegistryNodePack,
|
||||
'latest_version',
|
||||
'status'
|
||||
>
|
||||
comfy_node_extract_status: SafeNestedProperty<
|
||||
RegistryNodePack,
|
||||
'latest_version',
|
||||
'comfy_node_extract_status'
|
||||
>
|
||||
icon_url: RegistryNodePack['icon']
|
||||
}
|
||||
|
||||
export type SearchAttribute = keyof AlgoliaNodePack
|
||||
export interface NodesIndexSuggestion {
|
||||
nb_words: number
|
||||
nodes_index: {
|
||||
exact_nb_hits: number
|
||||
facets: {
|
||||
exact_matches: Record<string, number>
|
||||
analytics: Record<string, any>
|
||||
}
|
||||
}
|
||||
objectID: RegistryNodePack['id']
|
||||
popularity: number
|
||||
query: string
|
||||
}
|
||||
|
||||
export type SearchNodePacksParams = BaseSearchParamsWithoutQuery & {
|
||||
pageSize: number
|
||||
pageNumber: number
|
||||
restrictSearchableAttributes: SearchAttribute[]
|
||||
}
|
||||
@@ -1,17 +1,10 @@
|
||||
import type { InjectionKey, Ref } from 'vue'
|
||||
|
||||
import type { ComfyWorkflowJSON } from '@/schemas/comfyWorkflowSchema'
|
||||
import type { AlgoliaNodePack } from '@/types/algoliaTypes'
|
||||
import type { components } from '@/types/comfyRegistryTypes'
|
||||
|
||||
type RegistryPack = components['schemas']['Node']
|
||||
type WorkflowNodeProperties = ComfyWorkflowJSON['nodes'][0]['properties']
|
||||
|
||||
export type RegistryPack = components['schemas']['Node']
|
||||
export type MergedNodePack = RegistryPack & AlgoliaNodePack
|
||||
export const isMergedNodePack = (
|
||||
nodePack: RegistryPack | AlgoliaNodePack
|
||||
): nodePack is MergedNodePack => 'comfy_nodes' in nodePack
|
||||
|
||||
export type PackField = keyof RegistryPack | null
|
||||
|
||||
export const IsInstallingKey: InjectionKey<Ref<boolean>> =
|
||||
@@ -39,7 +32,7 @@ export enum SortableAlgoliaField {
|
||||
}
|
||||
|
||||
export interface TabItem {
|
||||
id: ManagerTab
|
||||
id: string
|
||||
label: string
|
||||
icon: string
|
||||
}
|
||||
@@ -234,10 +227,3 @@ export interface InstallPackParams extends ManagerPackInfo {
|
||||
export interface UpdateAllPacksParams {
|
||||
mode?: ManagerDatabaseSource
|
||||
}
|
||||
|
||||
export interface ManagerState {
|
||||
selectedTabId: ManagerTab
|
||||
searchQuery: string
|
||||
searchMode: 'nodes' | 'packs'
|
||||
sortField: SortableAlgoliaField
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ export interface Setting {
|
||||
export interface SettingParams extends FormItem {
|
||||
id: keyof Settings
|
||||
defaultValue: any | (() => any)
|
||||
// Optional versioned defaults based on installation version
|
||||
defaultsByInstallVersion?: Record<string, any | (() => any)>
|
||||
onChange?: (newValue: any, oldValue?: any) => void
|
||||
// By default category is id.split('.'). However, changing id to assign
|
||||
// new category has poor backward compatibility. Use this field to overwrite
|
||||
|
||||
@@ -4,12 +4,12 @@ import type { InjectionKey, ModelRef } from 'vue'
|
||||
|
||||
export interface TreeNode extends PrimeVueTreeNode {
|
||||
label: string
|
||||
children?: this[]
|
||||
children?: TreeNode[]
|
||||
}
|
||||
|
||||
export interface TreeExplorerNode<T = any> extends TreeNode {
|
||||
data?: T
|
||||
children?: this[]
|
||||
children?: TreeExplorerNode<T>[]
|
||||
icon?: string
|
||||
/**
|
||||
* Function to override what icon to use for the node.
|
||||
@@ -62,7 +62,7 @@ export interface TreeExplorerNode<T = any> extends TreeNode {
|
||||
}
|
||||
|
||||
export interface RenderedTreeExplorerNode<T = any> extends TreeExplorerNode<T> {
|
||||
children?: this[]
|
||||
children?: RenderedTreeExplorerNode<T>[]
|
||||
icon: string
|
||||
type: 'folder' | 'node'
|
||||
/** Total number of leaves in the subtree */
|
||||
|
||||
@@ -61,8 +61,8 @@ const mergeNumericInputSpec = <T extends IntInputSpec | FloatInputSpec>(
|
||||
}
|
||||
|
||||
return mergeCommonInputSpec(
|
||||
[type, { ...options1, ...mergedOptions }] as T,
|
||||
[type, { ...options2, ...mergedOptions }] as T
|
||||
[type, { ...options1, ...mergedOptions }] as unknown as T,
|
||||
[type, { ...options2, ...mergedOptions }] as unknown as T
|
||||
)
|
||||
}
|
||||
|
||||
@@ -84,8 +84,8 @@ const mergeComboInputSpec = <T extends ComboInputSpec | ComboInputSpecV2>(
|
||||
}
|
||||
|
||||
return mergeCommonInputSpec(
|
||||
['COMBO', { ...options1, options: intersection }] as T,
|
||||
['COMBO', { ...options2, options: intersection }] as T
|
||||
['COMBO', { ...options1, options: intersection }] as unknown as T,
|
||||
['COMBO', { ...options2, options: intersection }] as unknown as T
|
||||
)
|
||||
}
|
||||
|
||||
@@ -107,7 +107,9 @@ const mergeCommonInputSpec = <T extends InputSpec>(
|
||||
return value1 === value2 || (_.isNil(value1) && _.isNil(value2))
|
||||
})
|
||||
|
||||
return mergeIsValid ? ([type, { ...options1, ...options2 }] as T) : null
|
||||
return mergeIsValid
|
||||
? ([type, { ...options1, ...options2 }] as unknown as T)
|
||||
: null
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -116,7 +116,7 @@ export const findNodeByKey = <T extends TreeNode>(
|
||||
return null
|
||||
}
|
||||
for (const child of root.children) {
|
||||
const result = findNodeByKey(child, key)
|
||||
const result = findNodeByKey(child as T, key)
|
||||
if (result) {
|
||||
return result
|
||||
}
|
||||
@@ -130,11 +130,11 @@ export const findNodeByKey = <T extends TreeNode>(
|
||||
* @returns A deep clone of the node.
|
||||
*/
|
||||
export function cloneTree<T extends TreeNode>(node: T): T {
|
||||
const clone = { ...node }
|
||||
const clone: T = { ...node } as T
|
||||
|
||||
// Clone children recursively
|
||||
if (node.children && node.children.length > 0) {
|
||||
clone.children = node.children.map((child) => cloneTree(child))
|
||||
clone.children = node.children.map((child) => cloneTree(child as T))
|
||||
}
|
||||
|
||||
return clone
|
||||
|
||||
37
src/utils/versioning.ts
Normal file
37
src/utils/versioning.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { electronAPI, isElectron } from '@/utils/envUtil'
|
||||
|
||||
/**
|
||||
* Compare two semantic version strings.
|
||||
* @param a - First version string
|
||||
* @param b - Second version string
|
||||
* @returns 0 if equal, 1 if a > b, -1 if a < b
|
||||
*/
|
||||
export function compareVersions(a: string, b: string): number {
|
||||
const parseVersion = (version: string) => {
|
||||
return version.split('.').map((v) => parseInt(v, 10) || 0)
|
||||
}
|
||||
|
||||
const versionA = parseVersion(a)
|
||||
const versionB = parseVersion(b)
|
||||
|
||||
for (let i = 0; i < Math.max(versionA.length, versionB.length); i++) {
|
||||
const numA = versionA[i] || 0
|
||||
const numB = versionB[i] || 0
|
||||
|
||||
if (numA > numB) return 1
|
||||
if (numA < numB) return -1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current ComfyUI version for version tracking
|
||||
*/
|
||||
export function getCurrentVersion(): string {
|
||||
if (isElectron()) {
|
||||
return electronAPI().getComfyUIVersion()
|
||||
}
|
||||
// For web version, fallback to frontend version
|
||||
return __COMFYUI_FRONTEND_VERSION__
|
||||
}
|
||||
@@ -122,6 +122,26 @@ describe('useSettingStore', () => {
|
||||
expect(store.get('test.setting')).toBe('default')
|
||||
})
|
||||
|
||||
it('should use versioned default based on installation version', () => {
|
||||
// Set up an installed version
|
||||
store.settingValues['Comfy.InstalledVersion'] = '1.25.0'
|
||||
|
||||
const setting: SettingParams = {
|
||||
id: 'test.versionedSetting',
|
||||
name: 'test.versionedSetting',
|
||||
type: 'text',
|
||||
defaultValue: 'original',
|
||||
defaultsByInstallVersion: {
|
||||
'1.20.0': 'version_1_20',
|
||||
'1.24.0': 'version_1_24'
|
||||
}
|
||||
}
|
||||
store.addSetting(setting)
|
||||
|
||||
// Should use the highest version <= installed version
|
||||
expect(store.get('test.versionedSetting')).toBe('version_1_24')
|
||||
})
|
||||
|
||||
it('should set value and trigger onChange', async () => {
|
||||
const onChangeMock = vi.fn()
|
||||
const dispatchChangeMock = vi.mocked(app.ui.settings.dispatchChange)
|
||||
|
||||
@@ -3,9 +3,10 @@ import dotenv from 'dotenv'
|
||||
import IconsResolver from 'unplugin-icons/resolver'
|
||||
import Icons from 'unplugin-icons/vite'
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
import { type UserConfig, defineConfig } from 'vite'
|
||||
import { defineConfig } from 'vite'
|
||||
import { createHtmlPlugin } from 'vite-plugin-html'
|
||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
import type { UserConfigExport } from 'vitest/config'
|
||||
|
||||
import {
|
||||
addElementVnodeExportPlugin,
|
||||
@@ -153,4 +154,4 @@ export default defineConfig({
|
||||
optimizeDeps: {
|
||||
exclude: ['@comfyorg/litegraph', '@comfyorg/comfyui-electron-types']
|
||||
}
|
||||
}) satisfies UserConfig as UserConfig
|
||||
}) as UserConfigExport
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Plugin, defineConfig } from 'vite'
|
||||
import { mergeConfig } from 'vite'
|
||||
import type { UserConfig } from 'vitest/config'
|
||||
|
||||
import baseConfig from './vite.config.mts'
|
||||
|
||||
@@ -82,7 +83,7 @@ const mockElectronAPI: Plugin = {
|
||||
}
|
||||
|
||||
export default mergeConfig(
|
||||
baseConfig,
|
||||
baseConfig as unknown as UserConfig,
|
||||
defineConfig({
|
||||
plugins: [mockElectronAPI]
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user