feat: show legacy manager search tip in no-results empty state (#8537)

## Summary

Show a non-intrusive tip in the no-results empty state when users search
legacy manager-related terms in the new manager UI.

## Changes

- **What**: Add `useLegacySearchTip` composable that detects when search
query matches legacy manager keywords ("manager", "comfyui-manager",
etc.) and appends a tip to the empty state message suggesting the
`--enable-manager-legacy-ui` flag
- **Integration**: Tip appears as secondary muted text below "Try a
different search" message when no results found
- **Tests**: 9 test cases covering detection logic and edge cases

## Review Focus

- Keyword list covers common legacy manager search terms
- Non-intrusive approach: tip only shows in no-results state, no dismiss
button needed

Fixes COM-12509
This commit is contained in:
Christian Byrne
2026-02-01 23:43:42 -08:00
committed by GitHub
parent 2a167a675d
commit d34d6a8a92
4 changed files with 167 additions and 2 deletions

View File

@@ -290,6 +290,7 @@
"legacyMenuNotAvailable": "Legacy manager menu is not available, defaulting to the new manager menu.",
"legacyManagerUI": "Use Legacy UI",
"legacyManagerUIDescription": "To use the legacy Manager UI, start ComfyUI with --enable-manager-legacy-ui",
"legacyManagerSearchTip": "Looking for ComfyUI-Manager? You can enable the legacy manager UI by starting ComfyUI with the --enable-manager-legacy-ui flag.",
"failed": "Failed",
"failedToInstall": "Failed to Install",
"installError": "Install Error",

View File

@@ -206,6 +206,8 @@ import { useManagerDisplayPacks } from '@/workbench/extensions/manager/composabl
import { useManagerStatePersistence } from '@/workbench/extensions/manager/composables/useManagerStatePersistence'
import { useRegistrySearch } from '@/workbench/extensions/manager/composables/useRegistrySearch'
import { useConflictDetectionStore } from '@/workbench/extensions/manager/stores/conflictDetectionStore'
import { useLegacySearchTip } from '@/workbench/extensions/manager/composables/useLegacySearchTip'
import { useManagerState } from '@/workbench/extensions/manager/composables/useManagerState'
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
import { ManagerTab } from '@/workbench/extensions/manager/types/comfyManagerTypes'
@@ -222,6 +224,7 @@ const comfyManagerStore = useComfyManagerStore()
const { getPackById } = useComfyRegistryStore()
const conflictAcknowledgment = useConflictAcknowledgment()
const conflictDetectionStore = useConflictDetectionStore()
const { isNewManagerUI } = useManagerState()
const workflowStore = useWorkflowStore()
const persistedState = useManagerStatePersistence()
const initialState = persistedState.loadStoredState()
@@ -349,7 +352,11 @@ const {
})
pageNumber.value = 0
// Filter and sort options for SingleSelect
const { isLegacyManagerSearch } = useLegacySearchTip(
searchQuery,
isNewManagerUI
)
const filterOptions = computed(() => [
{ name: t('manager.filter.nodePack'), value: 'packs' },
{ name: t('g.nodes'), value: 'nodes' }
@@ -414,7 +421,13 @@ const emptyStateTitle = computed(() => {
const emptyStateMessage = computed(() => {
if (comfyManagerStore.error) return t('manager.tryAgainLater')
if (searchQuery.value) return t('manager.tryDifferentSearch')
if (searchQuery.value) {
const baseMessage = t('manager.tryDifferentSearch')
if (isLegacyManagerSearch.value) {
return `${baseMessage}\n\n${t('manager.legacyManagerSearchTip')}`
}
return baseMessage
}
const tabId = selectedTab.value?.id as ManagerTab | undefined
const emptyStateKey = tabId ? tabEmptyStateKeys[tabId] : undefined

View File

@@ -0,0 +1,126 @@
import type { Ref } from 'vue'
import { beforeEach, describe, expect, it } from 'vitest'
import { nextTick, ref } from 'vue'
import { useLegacySearchTip } from './useLegacySearchTip'
describe('useLegacySearchTip', () => {
let searchQuery: Ref<string>
let isNewManagerUI: Ref<boolean>
beforeEach(() => {
searchQuery = ref('')
isNewManagerUI = ref(true)
})
describe('isLegacyManagerSearch', () => {
it('returns true when searching "manager" in new manager UI', async () => {
const { isLegacyManagerSearch } = useLegacySearchTip(
searchQuery,
isNewManagerUI
)
searchQuery.value = 'manager'
await nextTick()
expect(isLegacyManagerSearch.value).toBe(true)
})
it('returns true when searching "comfyui-manager"', async () => {
const { isLegacyManagerSearch } = useLegacySearchTip(
searchQuery,
isNewManagerUI
)
searchQuery.value = 'comfyui-manager'
await nextTick()
expect(isLegacyManagerSearch.value).toBe(true)
})
it('returns true when searching "comfyui manager" (space variant)', async () => {
const { isLegacyManagerSearch } = useLegacySearchTip(
searchQuery,
isNewManagerUI
)
searchQuery.value = 'comfyui manager'
await nextTick()
expect(isLegacyManagerSearch.value).toBe(true)
})
it('returns true when keyword is part of larger query', async () => {
const { isLegacyManagerSearch } = useLegacySearchTip(
searchQuery,
isNewManagerUI
)
searchQuery.value = 'where is manager'
await nextTick()
expect(isLegacyManagerSearch.value).toBe(true)
})
it('matches case-insensitively', async () => {
const { isLegacyManagerSearch } = useLegacySearchTip(
searchQuery,
isNewManagerUI
)
searchQuery.value = 'MANAGER'
await nextTick()
expect(isLegacyManagerSearch.value).toBe(true)
})
it('returns false when query is empty', async () => {
const { isLegacyManagerSearch } = useLegacySearchTip(
searchQuery,
isNewManagerUI
)
searchQuery.value = ''
await nextTick()
expect(isLegacyManagerSearch.value).toBe(false)
})
it('returns false when query is whitespace only', async () => {
const { isLegacyManagerSearch } = useLegacySearchTip(
searchQuery,
isNewManagerUI
)
searchQuery.value = ' '
await nextTick()
expect(isLegacyManagerSearch.value).toBe(false)
})
it('returns false when searching unrelated terms', async () => {
const { isLegacyManagerSearch } = useLegacySearchTip(
searchQuery,
isNewManagerUI
)
searchQuery.value = 'controlnet'
await nextTick()
expect(isLegacyManagerSearch.value).toBe(false)
})
it('returns false when isNewManagerUI is false', async () => {
isNewManagerUI.value = false
const { isLegacyManagerSearch } = useLegacySearchTip(
searchQuery,
isNewManagerUI
)
searchQuery.value = 'manager'
await nextTick()
expect(isLegacyManagerSearch.value).toBe(false)
})
})
})

View File

@@ -0,0 +1,25 @@
import type { Ref } from 'vue'
import { computed } from 'vue'
const LEGACY_MANAGER_KEYWORDS = [
'manager',
'comfyui-manager',
'manager comfyui',
'comfyui manager'
] as const
export function useLegacySearchTip(
searchQuery: Ref<string>,
isNewManagerUI: Ref<boolean>
) {
const isLegacyManagerSearch = computed(() => {
if (!isNewManagerUI.value) return false
const query = searchQuery.value.toLowerCase().trim()
if (!query) return false
return LEGACY_MANAGER_KEYWORDS.some((keyword) => query.includes(keyword))
})
return {
isLegacyManagerSearch
}
}