Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b59ad87
migrate manager menu items
christian-byrne Apr 9, 2025
37a5add
Update locales [skip ci]
invalid-email-address Apr 9, 2025
2fc6ae0
switch to v2 manager API endpoints
christian-byrne Apr 9, 2025
aeb948a
re-arrange menu items
christian-byrne Apr 11, 2025
799f13a
await promises. update settings schema
christian-byrne Apr 14, 2025
0f282cc
move legacy option to startup arg
christian-byrne Apr 14, 2025
5733156
Add banner indicating how to use legacy manager UI
christian-byrne Apr 14, 2025
b4e4f45
Update locales [skip ci]
invalid-email-address Apr 14, 2025
3f598e4
add "Check for Updates", "Install Missing" menu items
christian-byrne Apr 14, 2025
f5744d2
Update locales [skip ci]
invalid-email-address Apr 14, 2025
becf55d
use correct response shape
christian-byrne Apr 14, 2025
40bdc9f
improve command names
christian-byrne Apr 14, 2025
450c3b0
dont show missing nodes button in legacy manager mode
christian-byrne Apr 15, 2025
62a33a6
[Update to v2 API] update WS done message
christian-byrne Apr 15, 2025
a29019f
Update locales [skip ci]
invalid-email-address Jul 19, 2025
84d6352
[fix] Fix json syntax error from rebase (#4607)
christian-byrne Jul 30, 2025
2827e78
Fix errors from rebase (removed `Tag` component import and duplicated…
christian-byrne Jul 30, 2025
2a8c474
Update locales [skip ci]
invalid-email-address Jul 30, 2025
2cf4dab
[Manager] "Restarting" state after clicking restart button (#4637)
viva-jinyi Aug 1, 2025
424f6de
[feat] Add reactive feature flags foundation (#4817)
christian-byrne Aug 7, 2025
08f25dd
[feat] Add v2/ prefix to manager service base URL (#4872)
christian-byrne Aug 9, 2025
574a1db
[cleanup] Remove unused manager route enums (#4875)
christian-byrne Aug 9, 2025
f82c4ce
[feat] Add manager capability feature flags
christian-byrne Aug 18, 2025
a257cd5
[feat] Add managerStateStore for three-state manager UI logic
christian-byrne Aug 18, 2025
6bf9fa6
[feat] Address PR review comments for managerStateStore
christian-byrne Aug 18, 2025
5fd1d17
[fix] Remove unnecessary @types/lodash dependency
christian-byrne Aug 21, 2025
08a11d0
[fix] Replace lodash import with es-toolkit/compat in api.ts
christian-byrne Aug 25, 2025
f18c70b
[feat] Apply manager state handling to all entry points
christian-byrne Aug 25, 2025
7e76637
[cleanup] Remove unnecessary comments and files
christian-byrne Aug 25, 2025
ceb54bb
[fix] Fix API URL prefix slash and add error handling
christian-byrne Sep 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
131 changes: 131 additions & 0 deletions src/components/common/DotSpinner.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<template>
<div
class="inline-flex items-center justify-center"
:style="{ width: size + 'px', height: size + 'px' }"
>
<svg
xmlns="http://www.w3.org/2000/svg"
:width="size"
:height="size"
viewBox="0 0 14 14"
fill="none"
class="animate-spin"
:style="{ animationDuration: duration }"
>
<g clip-path="url(#clip0_776_9582)">
<!-- Top dot -->
<path
class="dot-animation"
style="animation-delay: 0s"
fill-rule="evenodd"
clip-rule="evenodd"
d="M7 2.21053C7.61042 2.21053 8.10526 1.71568 8.10526 1.10526C8.10526 0.494843 7.61042 0 7 0C6.38958 0 5.89474 0.494843 5.89474 1.10526C5.89474 1.71568 6.38958 2.21053 7 2.21053Z"
:fill="color"
/>
<!-- Left dot -->
<path
class="dot-animation"
style="animation-delay: 0.25s"
fill-rule="evenodd"
clip-rule="evenodd"
d="M2.21053 7C2.21053 7.61042 1.71568 8.10526 1.10526 8.10526C0.494843 8.10526 0 7.61042 0 7C0 6.38958 0.494843 5.89474 1.10526 5.89474C1.71568 5.89474 2.21053 6.38958 2.21053 7Z"
:fill="color"
/>
<!-- Right dot -->
<path
class="dot-animation"
style="animation-delay: 0.5s"
fill-rule="evenodd"
clip-rule="evenodd"
d="M14 7C14 7.61042 13.5052 8.10526 12.8947 8.10526C12.2843 8.10526 11.7895 7.61042 11.7895 7C11.7895 6.38958 12.2843 5.89474 12.8947 5.89474C13.5052 5.89474 14 6.38958 14 7Z"
:fill="color"
/>
<!-- Bottom dot -->
<path
class="dot-animation"
style="animation-delay: 0.75s"
fill-rule="evenodd"
clip-rule="evenodd"
d="M8.10526 12.8947C8.10526 13.5052 7.61041 14 6.99999 14C6.38957 14 5.89473 13.5052 5.89473 12.8947C5.89473 12.2843 6.38957 11.7895 6.99999 11.7895C7.61041 11.7895 8.10526 12.2843 8.10526 12.8947Z"
:fill="color"
/>
<!-- Top-left dot -->
<path
class="dot-animation"
style="animation-delay: 0.125s"
fill-rule="evenodd"
clip-rule="evenodd"
d="M2.05039 3.61349C2.48203 4.04513 3.18184 4.04513 3.61347 3.61349C4.0451 3.18186 4.0451 2.48205 3.61347 2.05042C3.18184 1.61878 2.48203 1.61878 2.05039 2.05042C1.61876 2.48205 1.61876 3.18186 2.05039 3.61349Z"
:fill="color"
/>
<!-- Bottom-right dot -->
<path
class="dot-animation"
style="animation-delay: 0.625s"
fill-rule="evenodd"
clip-rule="evenodd"
d="M11.9496 11.9496C11.518 12.3812 10.8182 12.3812 10.3865 11.9496C9.9549 11.5179 9.9549 10.8181 10.3865 10.3865C10.8182 9.95485 11.518 9.95485 11.9496 10.3865C12.3812 10.8181 12.3812 11.5179 11.9496 11.9496Z"
:fill="color"
/>
<!-- Bottom-left dot -->
<path
class="dot-animation"
style="animation-delay: 0.875s"
fill-rule="evenodd"
clip-rule="evenodd"
d="M2.05039 11.9496C2.48203 12.3812 3.18184 12.3812 3.61347 11.9496C4.0451 11.5179 4.0451 10.8181 3.61347 10.3865C3.18184 9.95485 2.48203 9.95485 2.05039 10.3865C1.61876 10.8181 1.61876 11.5179 2.05039 11.9496Z"
:fill="color"
/>
<!-- Top-right dot -->
<path
class="dot-animation"
style="animation-delay: 0.375s"
fill-rule="evenodd"
clip-rule="evenodd"
d="M11.9496 3.61349C11.518 4.04513 10.8182 4.04513 10.3865 3.61349C9.9549 3.18186 9.9549 2.48205 10.3865 2.05042C10.8182 1.61878 11.518 1.61878 11.9496 2.05042C12.3812 2.48205 12.3812 3.18186 11.9496 3.61349Z"
:fill="color"
/>
</g>
<defs>
<clipPath id="clip0_776_9582">
<rect width="14" height="14" fill="white" />
</clipPath>
</defs>
</svg>
</div>
</template>

<script setup lang="ts">
import { computed } from 'vue'

import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'

const { size = 24, duration = '2s' } = defineProps<{
size?: number
duration?: string
}>()

const colorPaletteStore = useColorPaletteStore()

const color = computed(() =>
colorPaletteStore.completedActivePalette.light_theme ? '#2C2B30' : '#D4D4D4'
)
</script>

<style scoped>
.dot-animation {
animation: dot-pulse 1s ease-in-out infinite;
}

@keyframes dot-pulse {
0%,
80%,
100% {
opacity: 0.3;
}

40% {
opacity: 1;
}
}
</style>
63 changes: 44 additions & 19 deletions src/components/dialog/content/LoadWorkflowWarning.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,51 +31,61 @@
</div>
</template>
</ListBox>
<div v-if="isManagerInstalled" class="flex justify-end py-3">
<div
v-if="managerState && managerState !== ManagerUIState.DISABLED"
class="flex justify-end py-3"
>
<PackInstallButton
v-if="managerState === ManagerUIState.NEW_UI"
:disabled="isLoading || !!error || missingNodePacks.length === 0"
:node-packs="missingNodePacks"
variant="black"
:label="$t('manager.installAllMissingNodes')"
/>
<Button label="Open Manager" size="small" outlined @click="openManager" />
<Button
label="Open Manager"
size="small"
outlined
@click="handleOpenManager"
/>
</div>
</template>

<script setup lang="ts">
import { useAsyncState } from '@vueuse/core'
import Button from 'primevue/button'
import ListBox from 'primevue/listbox'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'

import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
import MissingCoreNodesMessage from '@/components/dialog/content/MissingCoreNodesMessage.vue'
import PackInstallButton from '@/components/dialog/content/manager/button/PackInstallButton.vue'
import { useMissingNodes } from '@/composables/nodePack/useMissingNodes'
import { useDialogService } from '@/services/dialogService'
import { useAboutPanelStore } from '@/stores/aboutPanelStore'
import { useCommandStore } from '@/stores/commandStore'
import {
ManagerUIState,
useManagerStateStore
} from '@/stores/managerStateStore'
import { useToastStore } from '@/stores/toastStore'
import type { MissingNodeType } from '@/types/comfy'
import { ManagerTab } from '@/types/comfyManagerTypes'

const props = defineProps<{
missingNodeTypes: MissingNodeType[]
}>()

const aboutPanelStore = useAboutPanelStore()

// Get missing node packs from workflow with loading and error states
const { missingNodePacks, isLoading, error, missingCoreNodes } =
useMissingNodes()

// Determines if ComfyUI-Manager is installed by checking for its badge in the about panel
// This allows us to conditionally show the Manager button only when the extension is available
// TODO: Remove this check when Manager functionality is fully migrated into core
const isManagerInstalled = computed(() => {
return aboutPanelStore.badges.some(
(badge) =>
badge.label.includes('ComfyUI-Manager') ||
badge.url.includes('ComfyUI-Manager')
)
})
// Get manager state asynchronously
const managerStateStore = useManagerStateStore()
const { state: managerState } = useAsyncState(
() => managerStateStore.getManagerUIState(),
null
)

const uniqueNodes = computed(() => {
const seenTypes = new Set()
Expand All @@ -98,10 +108,25 @@ const uniqueNodes = computed(() => {
})
})

const openManager = () => {
useDialogService().showManagerDialog({
initialTab: ManagerTab.Missing
})
const handleOpenManager = async () => {
if (managerState.value === ManagerUIState.NEW_UI) {
useDialogService().showManagerDialog({
initialTab: ManagerTab.Missing
})
} else if (managerState.value === ManagerUIState.LEGACY_UI) {
try {
await useCommandStore().execute('Comfy.Manager.Menu.ToggleVisibility')
} catch {
// If legacy command doesn't exist, show toast
const { t } = useI18n()
useToastStore().add({
severity: 'error',
summary: t('g.error'),
detail: t('manager.legacyMenuNotAvailable'),
life: 3000
})
}
}
}
</script>

Expand Down
82 changes: 82 additions & 0 deletions src/components/dialog/content/manager/ManagerHeader.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { mount } from '@vue/test-utils'
import { createPinia } from 'pinia'
import PrimeVue from 'primevue/config'
import Tag from 'primevue/tag'
import Tooltip from 'primevue/tooltip'
import { describe, expect, it } from 'vitest'
import { createI18n } from 'vue-i18n'

import enMessages from '@/locales/en/main.json'

import ManagerHeader from './ManagerHeader.vue'

const i18n = createI18n({
legacy: false,
locale: 'en',
messages: {
en: enMessages
}
})

describe('ManagerHeader', () => {
const createWrapper = () => {
return mount(ManagerHeader, {
global: {
plugins: [createPinia(), PrimeVue, i18n],
directives: {
tooltip: Tooltip
},
components: {
Tag
}
}
})
}

it('renders the component title', () => {
const wrapper = createWrapper()

expect(wrapper.find('h2').text()).toBe(
enMessages.manager.discoverCommunityContent
)
})

it('displays the legacy manager UI tag', () => {
const wrapper = createWrapper()

const tag = wrapper.find('[data-pc-name="tag"]')
expect(tag.exists()).toBe(true)
expect(tag.text()).toContain(enMessages.manager.legacyManagerUI)
})

it('applies info severity to the tag', () => {
const wrapper = createWrapper()

const tag = wrapper.find('[data-pc-name="tag"]')
expect(tag.classes()).toContain('p-tag-info')
})

it('displays info icon in the tag', () => {
const wrapper = createWrapper()

const icon = wrapper.find('.pi-info-circle')
expect(icon.exists()).toBe(true)
})

it('has cursor-help class on the tag', () => {
const wrapper = createWrapper()

const tag = wrapper.find('[data-pc-name="tag"]')
expect(tag.classes()).toContain('cursor-help')
})

it('has proper structure with flex container', () => {
const wrapper = createWrapper()

const flexContainer = wrapper.find('.flex.justify-end.ml-auto.pr-4')
expect(flexContainer.exists()).toBe(true)

const tag = flexContainer.find('[data-pc-name="tag"]')
expect(tag.exists()).toBe(true)
})
})
16 changes: 16 additions & 0 deletions src/components/dialog/content/manager/ManagerHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@
<h2 class="text-lg font-normal text-left">
{{ $t('manager.discoverCommunityContent') }}
</h2>
<div class="flex justify-end ml-auto pr-4">
<Tag
v-tooltip.left="$t('manager.legacyManagerUIDescription')"
severity="info"
icon="pi pi-info-circle"
:value="$t('manager.legacyManagerUI')"
class="cursor-help"
:pt="{
root: { class: 'text-xs' }
}"
/>
</div>
</div>
</div>
</template>

<script setup lang="ts">
import Tag from 'primevue/tag'
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
:loading="isInstalling"
:loading-message="$t('g.installing')"
@action="installAllPacks"
@click="onClick"
/>
</template>

Expand All @@ -37,10 +36,6 @@ const { nodePacks, variant, label } = defineProps<{

const isInstalling = inject(IsInstallingKey, ref(false))

const onClick = (): void => {
isInstalling.value = true
}

const managerStore = useComfyManagerStore()

const createPayload = (installItem: NodePack) => {
Expand All @@ -65,8 +60,6 @@ const installPack = (item: NodePack) =>
const installAllPacks = async () => {
if (!nodePacks?.length) return

isInstalling.value = true

const uninstalledPacks = nodePacks.filter(
(pack) => !managerStore.isPackInstalled(pack.id)
)
Expand Down
Loading