diff --git a/src/components/queue/QueueProgressOverlay.test.ts b/src/components/queue/QueueProgressOverlay.test.ts
new file mode 100644
index 0000000000..801aacfdc4
--- /dev/null
+++ b/src/components/queue/QueueProgressOverlay.test.ts
@@ -0,0 +1,99 @@
+import { createTestingPinia } from '@pinia/testing'
+import { mount } from '@vue/test-utils'
+import { beforeEach, describe, expect, it, vi } from 'vitest'
+import { defineComponent } from 'vue'
+
+import QueueProgressOverlay from '@/components/queue/QueueProgressOverlay.vue'
+import { i18n } from '@/i18n'
+import type { JobStatus } from '@/platform/remote/comfyui/jobs/jobTypes'
+import { TaskItemImpl, useQueueStore } from '@/stores/queueStore'
+
+vi.mock('@/platform/distribution/types', () => ({
+ isCloud: false
+}))
+
+const QueueOverlayExpandedStub = defineComponent({
+ name: 'QueueOverlayExpanded',
+ props: {
+ headerTitle: {
+ type: String,
+ required: true
+ }
+ },
+ template: '
{{ headerTitle }}
'
+})
+
+function createTask(id: string, status: JobStatus): TaskItemImpl {
+ return new TaskItemImpl({
+ id,
+ status,
+ create_time: 0,
+ priority: 0
+ })
+}
+
+const mountComponent = (
+ runningTasks: TaskItemImpl[],
+ pendingTasks: TaskItemImpl[]
+) => {
+ const pinia = createTestingPinia({
+ createSpy: vi.fn,
+ stubActions: false
+ })
+ const queueStore = useQueueStore(pinia)
+ queueStore.runningTasks = runningTasks
+ queueStore.pendingTasks = pendingTasks
+
+ return mount(QueueProgressOverlay, {
+ props: {
+ expanded: true
+ },
+ global: {
+ plugins: [pinia, i18n],
+ stubs: {
+ QueueOverlayExpanded: QueueOverlayExpandedStub,
+ QueueOverlayActive: true,
+ ResultGallery: true
+ },
+ directives: {
+ tooltip: () => {}
+ }
+ }
+ })
+}
+
+describe('QueueProgressOverlay', () => {
+ beforeEach(() => {
+ i18n.global.locale.value = 'en'
+ })
+
+ it('shows expanded header with running and queued labels', () => {
+ const wrapper = mountComponent(
+ [
+ createTask('running-1', 'in_progress'),
+ createTask('running-2', 'in_progress')
+ ],
+ [createTask('pending-1', 'pending')]
+ )
+
+ expect(wrapper.get('[data-testid="expanded-title"]').text()).toBe(
+ '2 running, 1 queued'
+ )
+ })
+
+ it('shows only running label when queued count is zero', () => {
+ const wrapper = mountComponent([createTask('running-1', 'in_progress')], [])
+
+ expect(wrapper.get('[data-testid="expanded-title"]').text()).toBe(
+ '1 running'
+ )
+ })
+
+ it('shows job queue title when there are no active jobs', () => {
+ const wrapper = mountComponent([], [])
+
+ expect(wrapper.get('[data-testid="expanded-title"]').text()).toBe(
+ 'Job Queue'
+ )
+ })
+})
diff --git a/src/components/queue/QueueProgressOverlay.vue b/src/components/queue/QueueProgressOverlay.vue
index 92ff793eb3..8096c5e2bf 100644
--- a/src/components/queue/QueueProgressOverlay.vue
+++ b/src/components/queue/QueueProgressOverlay.vue
@@ -92,7 +92,7 @@ const emit = defineEmits<{
(e: 'update:expanded', value: boolean): void
}>()
-const { t } = useI18n()
+const { t, n } = useI18n()
const queueStore = useQueueStore()
const commandStore = useCommandStore()
const executionStore = useExecutionStore()
@@ -126,7 +126,6 @@ const runningCount = computed(() => queueStore.runningTasks.length)
const queuedCount = computed(() => queueStore.pendingTasks.length)
const isExecuting = computed(() => !executionStore.isIdle)
const hasActiveJob = computed(() => runningCount.value > 0 || isExecuting.value)
-const activeJobsCount = computed(() => runningCount.value + queuedCount.value)
const overlayState = computed(() => {
if (isExpanded.value) return 'expanded'
@@ -156,11 +155,34 @@ const bottomRowClass = computed(
: 'opacity-0 pointer-events-none'
}`
)
-const headerTitle = computed(() =>
- hasActiveJob.value
- ? `${activeJobsCount.value} ${t('sideToolbar.queueProgressOverlay.activeJobsSuffix')}`
- : t('sideToolbar.queueProgressOverlay.jobQueue')
+const runningJobsLabel = computed(() =>
+ t('sideToolbar.queueProgressOverlay.runningJobsLabel', {
+ count: n(runningCount.value)
+ })
)
+const queuedJobsLabel = computed(() =>
+ t('sideToolbar.queueProgressOverlay.queuedJobsLabel', {
+ count: n(queuedCount.value)
+ })
+)
+const headerTitle = computed(() => {
+ if (!hasActiveJob.value) {
+ return t('sideToolbar.queueProgressOverlay.jobQueue')
+ }
+
+ if (queuedCount.value === 0) {
+ return runningJobsLabel.value
+ }
+
+ if (runningCount.value === 0) {
+ return queuedJobsLabel.value
+ }
+
+ return t('sideToolbar.queueProgressOverlay.runningQueuedSummary', {
+ running: runningJobsLabel.value,
+ queued: queuedJobsLabel.value
+ })
+})
const concurrentWorkflowCount = computed(
() => executionStore.runningWorkflowCount
diff --git a/src/locales/en/main.json b/src/locales/en/main.json
index 09dd8961a4..de5ddc3f15 100644
--- a/src/locales/en/main.json
+++ b/src/locales/en/main.json
@@ -814,6 +814,9 @@
"activeJobs": "{count} active job | {count} active jobs",
"activeJobsShort": "{count} active | {count} active",
"activeJobsSuffix": "active jobs",
+ "runningJobsLabel": "{count} running",
+ "queuedJobsLabel": "{count} queued",
+ "runningQueuedSummary": "{running}, {queued}",
"jobQueue": "Job Queue",
"expandCollapsedQueue": "Expand job queue",
"viewJobHistory": "View active jobs (right-click to clear queue)",