diff --git a/src/components/TopMenuSection.vue b/src/components/TopMenuSection.vue index f9cc7054a3..07aa12d143 100644 --- a/src/components/TopMenuSection.vue +++ b/src/components/TopMenuSection.vue @@ -1,10 +1,5 @@ - + @@ -53,10 +48,7 @@ - + @@ -88,7 +80,6 @@ const isDesktop = isElectron() const { t } = useI18n() const isQueueOverlayExpanded = ref(false) const queueStore = useQueueStore() -const isTopMenuHovered = ref(false) const queuedCount = computed(() => queueStore.pendingTasks.length) const queueHistoryTooltipConfig = computed(() => buildTooltipConfig(t('sideToolbar.queueProgressOverlay.viewJobHistory')) diff --git a/src/components/queue/QueueOverlayActive.test.ts b/src/components/queue/QueueOverlayActive.test.ts deleted file mode 100644 index 9b8d131d23..0000000000 --- a/src/components/queue/QueueOverlayActive.test.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { mount } from '@vue/test-utils' -import { describe, expect, it, vi } from 'vitest' -import { createI18n } from 'vue-i18n' - -import QueueOverlayActive from './QueueOverlayActive.vue' -import * as tooltipConfig from '@/composables/useTooltipConfig' - -const i18n = createI18n({ - legacy: false, - locale: 'en', - messages: { - en: { - sideToolbar: { - queueProgressOverlay: { - total: 'Total: {percent}', - currentNode: 'Current node:', - running: 'running', - interruptAll: 'Interrupt all running jobs', - queuedSuffix: 'queued', - clearQueued: 'Clear queued', - viewAllJobs: 'View all jobs', - cancelJobTooltip: 'Cancel job', - clearQueueTooltip: 'Clear queue' - } - } - } - } -}) - -const tooltipDirectiveStub = { - mounted: vi.fn(), - updated: vi.fn() -} - -const SELECTORS = { - interruptAllButton: 'button[aria-label="Interrupt all running jobs"]', - clearQueuedButton: 'button[aria-label="Clear queued"]', - summaryRow: '.flex.items-center.gap-2', - currentNodeRow: '.flex.items-center.gap-1.text-text-secondary' -} - -const COPY = { - viewAllJobs: 'View all jobs' -} - -const mountComponent = (props: Record = {}) => - mount(QueueOverlayActive, { - props: { - totalProgressStyle: { width: '65%' }, - currentNodeProgressStyle: { width: '40%' }, - totalPercentFormatted: '65%', - currentNodePercentFormatted: '40%', - currentNodeName: 'Sampler', - runningCount: 1, - queuedCount: 2, - bottomRowClass: 'flex custom-bottom-row', - ...props - }, - global: { - plugins: [i18n], - directives: { - tooltip: tooltipDirectiveStub - } - } - }) - -describe('QueueOverlayActive', () => { - it('renders progress metrics and emits actions when buttons clicked', async () => { - const wrapper = mountComponent({ runningCount: 2, queuedCount: 3 }) - - const progressBars = wrapper.findAll('.absolute.inset-0') - expect(progressBars[0].attributes('style')).toContain('width: 65%') - expect(progressBars[1].attributes('style')).toContain('width: 40%') - - const content = wrapper.text().replace(/\s+/g, ' ') - expect(content).toContain('Total: 65%') - - const [runningSection, queuedSection] = wrapper.findAll( - SELECTORS.summaryRow - ) - expect(runningSection.text()).toContain('2') - expect(runningSection.text()).toContain('running') - expect(queuedSection.text()).toContain('3') - expect(queuedSection.text()).toContain('queued') - - const currentNodeSection = wrapper.find(SELECTORS.currentNodeRow) - expect(currentNodeSection.text()).toContain('Current node:') - expect(currentNodeSection.text()).toContain('Sampler') - expect(currentNodeSection.text()).toContain('40%') - - const interruptButton = wrapper.get(SELECTORS.interruptAllButton) - await interruptButton.trigger('click') - expect(wrapper.emitted('interruptAll')).toHaveLength(1) - - const clearQueuedButton = wrapper.get(SELECTORS.clearQueuedButton) - await clearQueuedButton.trigger('click') - expect(wrapper.emitted('clearQueued')).toHaveLength(1) - - const buttons = wrapper.findAll('button') - const viewAllButton = buttons.find((btn) => - btn.text().includes(COPY.viewAllJobs) - ) - expect(viewAllButton).toBeDefined() - await viewAllButton!.trigger('click') - expect(wrapper.emitted('viewAllJobs')).toHaveLength(1) - - expect(wrapper.find('.custom-bottom-row').exists()).toBe(true) - }) - - it('hides action buttons when counts are zero', () => { - const wrapper = mountComponent({ runningCount: 0, queuedCount: 0 }) - - expect(wrapper.find(SELECTORS.interruptAllButton).exists()).toBe(false) - expect(wrapper.find(SELECTORS.clearQueuedButton).exists()).toBe(false) - }) - - it('builds tooltip configs with translated strings', () => { - const spy = vi.spyOn(tooltipConfig, 'buildTooltipConfig') - - mountComponent() - - expect(spy).toHaveBeenCalledWith('Cancel job') - expect(spy).toHaveBeenCalledWith('Clear queue') - }) -}) diff --git a/src/components/queue/QueueOverlayActive.vue b/src/components/queue/QueueOverlayActive.vue deleted file mode 100644 index abb3ae982f..0000000000 --- a/src/components/queue/QueueOverlayActive.vue +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - - - - - - - {{ totalPercentFormatted }} - - - - - {{ t('sideToolbar.queueProgressOverlay.currentNode') }} - {{ - currentNodeName - }} - - {{ currentNodePercentFormatted }} - - - - - - - - - - {{ runningCount }} - {{ - t('sideToolbar.queueProgressOverlay.running') - }} - - - - - - - - - {{ queuedCount }} - {{ - t('sideToolbar.queueProgressOverlay.queuedSuffix') - }} - - - - - - - - - - - - - diff --git a/src/components/queue/QueueOverlayExpanded.vue b/src/components/queue/QueueOverlayExpanded.vue index 64e8458e0b..8129ab2655 100644 --- a/src/components/queue/QueueOverlayExpanded.vue +++ b/src/components/queue/QueueOverlayExpanded.vue @@ -46,18 +46,6 @@ - - () const emit = defineEmits<{ (e: 'showAssets'): void (e: 'clearHistory'): void (e: 'clearQueued'): void - (e: 'update:selectedJobTab', value: JobTab): void - (e: 'update:selectedWorkflowFilter', value: 'all' | 'current'): void - (e: 'update:selectedSortMode', value: JobSortMode): void (e: 'cancelItem', item: JobListItem): void (e: 'deleteItem', item: JobListItem): void (e: 'viewItem', item: JobListItem): void diff --git a/src/components/queue/QueueProgressOverlay.vue b/src/components/queue/QueueProgressOverlay.vue index a941af55aa..cf97dd52bd 100644 --- a/src/components/queue/QueueProgressOverlay.vue +++ b/src/components/queue/QueueProgressOverlay.vue @@ -6,22 +6,16 @@ - - (), - { - menuHovered: false - } -) +const props = defineProps<{ + expanded?: boolean +}>() const emit = defineEmits<{ (e: 'update:expanded', value: boolean): void @@ -110,14 +80,6 @@ const assetsStore = useAssetsStore() const assetSelectionStore = useAssetSelectionStore() const { wrapWithErrorHandlingAsync } = useErrorHandling() -const { - totalPercentFormatted, - currentNodePercentFormatted, - totalProgressStyle, - currentNodeProgressStyle -} = useQueueProgress() -const isHovered = ref(false) -const isOverlayHovered = computed(() => isHovered.value || props.menuHovered) const internalExpanded = ref(false) const isExpanded = computed({ get: () => @@ -141,16 +103,12 @@ const activeJobsCount = computed(() => runningCount.value + queuedCount.value) const overlayState = computed(() => { if (isExpanded.value) return 'expanded' - if (hasActiveJob.value) return 'active' if (hasCompletionSummary.value) return 'empty' return 'hidden' }) const showBackground = computed( - () => - overlayState.value === 'expanded' || - overlayState.value === 'empty' || - (overlayState.value === 'active' && isOverlayHovered.value) + () => overlayState.value === 'expanded' || overlayState.value === 'empty' ) const isVisible = computed(() => overlayState.value !== 'hidden') @@ -161,14 +119,6 @@ const containerClass = computed(() => : 'border-transparent bg-transparent shadow-none' ) -const bottomRowClass = computed( - () => - `flex items-center justify-end gap-4 transition-opacity duration-200 ease-in-out ${ - overlayState.value === 'active' && isOverlayHovered.value - ? 'opacity-100 pointer-events-auto' - : 'opacity-0 pointer-events-none' - }` -) const headerTitle = computed(() => hasActiveJob.value ? `${activeJobsCount.value} ${t('sideToolbar.queueProgressOverlay.activeJobsSuffix')}` @@ -182,15 +132,7 @@ const showConcurrentIndicator = computed( () => concurrentWorkflowCount.value > 1 ) -const { - selectedJobTab, - selectedWorkflowFilter, - selectedSortMode, - hasFailedJobs, - filteredTasks, - groupedJobItems, - currentNodeName -} = useJobList() +const { filteredTasks, groupedJobItems } = useJobList() const displayedJobGroups = computed(() => groupedJobItems.value) @@ -219,10 +161,6 @@ const openExpandedFromEmpty = () => { setExpanded(true) } -const viewAllJobs = () => { - setExpanded(true) -} - const onSummaryClick = () => { openExpandedFromEmpty() clearSummary() @@ -262,25 +200,6 @@ const cancelQueuedWorkflows = wrapWithErrorHandlingAsync(async () => { await commandStore.execute('Comfy.ClearPendingTasks') }) -const interruptAll = wrapWithErrorHandlingAsync(async () => { - const tasks = queueStore.runningTasks - const promptIds = tasks - .map((task) => task.promptId) - .filter((id): id is string => typeof id === 'string' && id.length > 0) - - if (!promptIds.length) return - - // Cloud backend supports cancelling specific jobs via /queue delete, - // while /interrupt always targets the "first" job. Use the targeted API - // on cloud to ensure we cancel the workflow the user clicked. - if (isCloud) { - await Promise.all(promptIds.map((id) => api.deleteItem('queue', id))) - return - } - - await Promise.all(promptIds.map((id) => api.interrupt(id))) -}) - const showClearHistoryDialog = () => { dialogStore.showDialog({ key: 'queue-clear-history', diff --git a/src/components/queue/job/JobFiltersBar.vue b/src/components/queue/job/JobFiltersBar.vue deleted file mode 100644 index c6796428a2..0000000000 --- a/src/components/queue/job/JobFiltersBar.vue +++ /dev/null @@ -1,231 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/composables/queue/useJobList.ts b/src/composables/queue/useJobList.ts index 47996636be..a2cf6871c5 100644 --- a/src/composables/queue/useJobList.ts +++ b/src/composables/queue/useJobList.ts @@ -4,7 +4,6 @@ import { useI18n } from 'vue-i18n' import { useQueueProgress } from '@/composables/queue/useQueueProgress' import { st } from '@/i18n' import { isCloud } from '@/platform/distribution/types' -import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore' import { useExecutionStore } from '@/stores/executionStore' import { useQueueStore } from '@/stores/queueStore' import type { TaskItemImpl } from '@/stores/queueStore' @@ -20,13 +19,6 @@ import { normalizeI18nKey } from '@/utils/formatUtil' import { buildJobDisplay } from '@/utils/queueDisplay' import { jobStateFromTask } from '@/utils/queueUtil' -/** Tabs for job list filtering */ -export const jobTabs = ['All', 'Completed', 'Failed'] as const -export type JobTab = (typeof jobTabs)[number] - -export const jobSortModes = ['mostRecent', 'totalGenerationTime'] as const -export type JobSortMode = (typeof jobSortModes)[number] - /** * UI item in the job list. Mirrors data previously prepared inline. */ @@ -88,14 +80,11 @@ type TaskWithState = { state: JobState } -/** - * Builds the reactive job list, filters, and grouped view for the queue overlay. - */ +/** Builds the reactive job list and grouped view for the queue overlay. */ export function useJobList() { const { t, locale } = useI18n() const queueStore = useQueueStore() const executionStore = useExecutionStore() - const workflowStore = useWorkflowStore() const seenPendingIds = ref>(new Set()) const recentlyAddedPendingIds = ref>(new Set()) @@ -193,10 +182,6 @@ export function useJobList() { return st(key, nodeType) }) - const selectedJobTab = ref('All') - const selectedWorkflowFilter = ref<'all' | 'current'>('all') - const selectedSortMode = ref('mostRecent') - const allTasksSorted = computed(() => { const all = [ ...queueStore.pendingTasks, @@ -213,38 +198,10 @@ export function useJobList() { })) ) - const hasFailedJobs = computed(() => - tasksWithJobState.value.some(({ state }) => state === 'failed') - ) - - watch( - () => hasFailedJobs.value, - (hasFailed) => { - if (!hasFailed && selectedJobTab.value === 'Failed') { - selectedJobTab.value = 'All' - } - } + const filteredTaskEntries = computed( + () => tasksWithJobState.value ) - const filteredTaskEntries = computed(() => { - let entries = tasksWithJobState.value - if (selectedJobTab.value === 'Completed') { - entries = entries.filter(({ state }) => state === 'completed') - } else if (selectedJobTab.value === 'Failed') { - entries = entries.filter(({ state }) => state === 'failed') - } - - if (selectedWorkflowFilter.value === 'current') { - const activeId = workflowStore.activeWorkflow?.activeState?.id - if (!activeId) return [] - entries = entries.filter(({ task }) => { - const wid = task.workflow?.id - return !!wid && wid === activeId - }) - } - return entries - }) - const filteredTasks = computed(() => filteredTaskEntries.value.map(({ task }) => task) ) @@ -330,27 +287,10 @@ export function useJobList() { if (ji) groups[groupIdx].items.push(ji) } - if (selectedSortMode.value === 'totalGenerationTime') { - const valueOrDefault = (value: JobListItem['executionTimeMs']) => - typeof value === 'number' && !Number.isNaN(value) ? value : -1 - const sortByExecutionTimeDesc = (a: JobListItem, b: JobListItem) => - valueOrDefault(b.executionTimeMs) - valueOrDefault(a.executionTimeMs) - - groups.forEach((group) => { - group.items.sort(sortByExecutionTimeDesc) - }) - } - return groups }) return { - // filters/state - selectedJobTab, - selectedWorkflowFilter, - selectedSortMode, - hasFailedJobs, - // data sources allTasksSorted, filteredTasks, jobItems, diff --git a/tests-ui/tests/composables/useJobList.test.ts b/tests-ui/tests/composables/useJobList.test.ts index 36a14162b5..c65f79afe8 100644 --- a/tests-ui/tests/composables/useJobList.test.ts +++ b/tests-ui/tests/composables/useJobList.test.ts @@ -158,23 +158,6 @@ vi.mock('@/stores/executionStore', () => ({ } })) -let workflowStoreMock: { - activeWorkflow: null | { activeState?: { id?: string } } -} -const ensureWorkflowStore = () => { - if (!workflowStoreMock) { - workflowStoreMock = reactive({ - activeWorkflow: null as null | { activeState?: { id?: string } } - }) - } - return workflowStoreMock -} -vi.mock('@/platform/workflow/management/stores/workflowStore', () => ({ - useWorkflowStore: () => { - return ensureWorkflowStore() - } -})) - const createTask = ( overrides: Partial & { mockState?: JobState } = {} ): TestTask => ({ @@ -210,9 +193,6 @@ const resetStores = () => { executionStore.activePromptId = null executionStore.executingNode = null - const workflowStore = ensureWorkflowStore() - workflowStore.activeWorkflow = null - ensureProgressRefs() totalPercent.value = 0 currentNodePercent.value = 0 @@ -353,65 +333,6 @@ describe('useJobList', () => { ]) }) - it('filters by job tab and resets failed tab when failures disappear', async () => { - queueStoreMock.historyTasks = [ - createTask({ promptId: 'c', queueIndex: 3, mockState: 'completed' }), - createTask({ promptId: 'f', queueIndex: 2, mockState: 'failed' }), - createTask({ promptId: 'p', queueIndex: 1, mockState: 'pending' }) - ] - - const instance = initComposable() - await flush() - - instance.selectedJobTab.value = 'Completed' - await flush() - expect(instance.filteredTasks.value.map((t) => t.promptId)).toEqual(['c']) - - instance.selectedJobTab.value = 'Failed' - await flush() - expect(instance.filteredTasks.value.map((t) => t.promptId)).toEqual(['f']) - expect(instance.hasFailedJobs.value).toBe(true) - - queueStoreMock.historyTasks = [ - createTask({ promptId: 'c', queueIndex: 3, mockState: 'completed' }) - ] - await flush() - - expect(instance.hasFailedJobs.value).toBe(false) - expect(instance.selectedJobTab.value).toBe('All') - }) - - it('filters by active workflow when requested', async () => { - queueStoreMock.pendingTasks = [ - createTask({ - promptId: 'wf-1', - queueIndex: 2, - mockState: 'pending', - workflow: { id: 'workflow-1' } - }), - createTask({ - promptId: 'wf-2', - queueIndex: 1, - mockState: 'pending', - workflow: { id: 'workflow-2' } - }) - ] - - const instance = initComposable() - await flush() - - instance.selectedWorkflowFilter.value = 'current' - await flush() - expect(instance.filteredTasks.value).toEqual([]) - - workflowStoreMock.activeWorkflow = { activeState: { id: 'workflow-1' } } - await flush() - - expect(instance.filteredTasks.value.map((t) => t.promptId)).toEqual([ - 'wf-1' - ]) - }) - it('hydrates job items with active progress and compute hours', async () => { queueStoreMock.runningTasks = [ createTask({ @@ -501,7 +422,6 @@ describe('useJobList', () => { ] const instance = initComposable() - instance.selectedSortMode.value = 'totalGenerationTime' await flush() const groups = instance.groupedJobItems.value @@ -513,8 +433,8 @@ describe('useJobList', () => { const todayGroup = groups[0] expect(todayGroup.items.map((item) => item.id)).toEqual([ - 'today-large', - 'today-small' + 'today-small', + 'today-large' ]) }) })