mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 19:21:54 +00:00
@@ -1,21 +1,32 @@
|
|||||||
<template>
|
<template>
|
||||||
<SideBarTabTemplate :title="$t('sideToolbar.queue')">
|
<SidebarTabTemplate :title="$t('sideToolbar.queue')">
|
||||||
<template #tool-buttons>
|
<template #tool-buttons>
|
||||||
<Button
|
<Button
|
||||||
:icon="isExpanded ? 'pi pi-chevron-up' : 'pi pi-chevron-down'"
|
v-if="isInFolderView"
|
||||||
|
icon="pi pi-arrow-left"
|
||||||
text
|
text
|
||||||
severity="secondary"
|
severity="secondary"
|
||||||
@click="toggleExpanded"
|
@click="exitFolderView"
|
||||||
class="toggle-expanded-button"
|
class="back-button"
|
||||||
v-tooltip="$t('sideToolbar.queueTab.showFlatList')"
|
v-tooltip="$t('sideToolbar.queueTab.backToAllTasks')"
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
icon="pi pi-trash"
|
|
||||||
text
|
|
||||||
severity="primary"
|
|
||||||
@click="confirmRemoveAll($event)"
|
|
||||||
class="clear-all-button"
|
|
||||||
/>
|
/>
|
||||||
|
<template v-else>
|
||||||
|
<Button
|
||||||
|
:icon="isExpanded ? 'pi pi-chevron-up' : 'pi pi-chevron-down'"
|
||||||
|
text
|
||||||
|
severity="secondary"
|
||||||
|
@click="toggleExpanded"
|
||||||
|
class="toggle-expanded-button"
|
||||||
|
v-tooltip="$t('sideToolbar.queueTab.showFlatList')"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon="pi pi-trash"
|
||||||
|
text
|
||||||
|
severity="primary"
|
||||||
|
@click="confirmRemoveAll($event)"
|
||||||
|
class="clear-all-button"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template #body>
|
<template #body>
|
||||||
<div
|
<div
|
||||||
@@ -28,9 +39,10 @@
|
|||||||
v-for="task in visibleTasks"
|
v-for="task in visibleTasks"
|
||||||
:key="task.key"
|
:key="task.key"
|
||||||
:task="task"
|
:task="task"
|
||||||
:isFlatTask="isExpanded"
|
:isFlatTask="isExpanded || isInFolderView"
|
||||||
@contextmenu="handleContextMenu"
|
@contextmenu="handleContextMenu"
|
||||||
@preview="handlePreview"
|
@preview="handlePreview"
|
||||||
|
@taskOutputLengthClicked="enterFolderView($event)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div ref="loadMoreTrigger" style="height: 1px" />
|
<div ref="loadMoreTrigger" style="height: 1px" />
|
||||||
@@ -43,7 +55,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</SideBarTabTemplate>
|
</SidebarTabTemplate>
|
||||||
<ConfirmPopup />
|
<ConfirmPopup />
|
||||||
<ContextMenu ref="menu" :model="menuItems" />
|
<ContextMenu ref="menu" :model="menuItems" />
|
||||||
<ResultGallery
|
<ResultGallery
|
||||||
@@ -64,7 +76,7 @@ import ContextMenu from 'primevue/contextmenu'
|
|||||||
import type { MenuItem } from 'primevue/menuitem'
|
import type { MenuItem } from 'primevue/menuitem'
|
||||||
import TaskItem from './queue/TaskItem.vue'
|
import TaskItem from './queue/TaskItem.vue'
|
||||||
import ResultGallery from './queue/ResultGallery.vue'
|
import ResultGallery from './queue/ResultGallery.vue'
|
||||||
import SideBarTabTemplate from './SidebarTabTemplate.vue'
|
import SidebarTabTemplate from './SidebarTabTemplate.vue'
|
||||||
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
|
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
|
||||||
import { TaskItemImpl, useQueueStore } from '@/stores/queueStore'
|
import { TaskItemImpl, useQueueStore } from '@/stores/queueStore'
|
||||||
import { api } from '@/scripts/api'
|
import { api } from '@/scripts/api'
|
||||||
@@ -74,17 +86,27 @@ const toast = useToast()
|
|||||||
const queueStore = useQueueStore()
|
const queueStore = useQueueStore()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
// Expanded view: show all outputs in a flat list.
|
||||||
const isExpanded = ref(false)
|
const isExpanded = ref(false)
|
||||||
const visibleTasks = ref<TaskItemImpl[]>([])
|
const visibleTasks = ref<TaskItemImpl[]>([])
|
||||||
const scrollContainer = ref<HTMLElement | null>(null)
|
const scrollContainer = ref<HTMLElement | null>(null)
|
||||||
const loadMoreTrigger = ref<HTMLElement | null>(null)
|
const loadMoreTrigger = ref<HTMLElement | null>(null)
|
||||||
const galleryActiveIndex = ref(-1)
|
const galleryActiveIndex = ref(-1)
|
||||||
|
// Folder view: only show outputs from a single selected task.
|
||||||
|
const folderTask = ref<TaskItemImpl | null>(null)
|
||||||
|
const isInFolderView = computed(() => folderTask.value !== null)
|
||||||
|
|
||||||
const ITEMS_PER_PAGE = 8
|
const ITEMS_PER_PAGE = 8
|
||||||
const SCROLL_THRESHOLD = 100 // pixels from bottom to trigger load
|
const SCROLL_THRESHOLD = 100 // pixels from bottom to trigger load
|
||||||
|
|
||||||
const allTasks = computed(() =>
|
const allTasks = computed(() =>
|
||||||
isExpanded.value ? queueStore.flatTasks : queueStore.tasks
|
isInFolderView.value
|
||||||
|
? folderTask.value
|
||||||
|
? folderTask.value.flatten()
|
||||||
|
: []
|
||||||
|
: isExpanded.value
|
||||||
|
? queueStore.flatTasks
|
||||||
|
: queueStore.tasks
|
||||||
)
|
)
|
||||||
const allGalleryItems = computed(() =>
|
const allGalleryItems = computed(() =>
|
||||||
allTasks.value.flatMap((task: TaskItemImpl) => {
|
allTasks.value.flatMap((task: TaskItemImpl) => {
|
||||||
@@ -129,9 +151,13 @@ useResizeObserver(scrollContainer, () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const updateVisibleTasks = () => {
|
||||||
|
visibleTasks.value = allTasks.value.slice(0, ITEMS_PER_PAGE)
|
||||||
|
}
|
||||||
|
|
||||||
const toggleExpanded = () => {
|
const toggleExpanded = () => {
|
||||||
isExpanded.value = !isExpanded.value
|
isExpanded.value = !isExpanded.value
|
||||||
visibleTasks.value = allTasks.value.slice(0, ITEMS_PER_PAGE)
|
updateVisibleTasks()
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeTask = (task: TaskItemImpl) => {
|
const removeTask = (task: TaskItemImpl) => {
|
||||||
@@ -173,7 +199,7 @@ const confirmRemoveAll = (event: Event) => {
|
|||||||
|
|
||||||
const onStatus = async () => {
|
const onStatus = async () => {
|
||||||
await queueStore.update()
|
await queueStore.update()
|
||||||
visibleTasks.value = allTasks.value.slice(0, ITEMS_PER_PAGE)
|
updateVisibleTasks()
|
||||||
}
|
}
|
||||||
|
|
||||||
const menu = ref(null)
|
const menu = ref(null)
|
||||||
@@ -182,7 +208,8 @@ const menuItems = computed<MenuItem[]>(() => [
|
|||||||
{
|
{
|
||||||
label: t('delete'),
|
label: t('delete'),
|
||||||
icon: 'pi pi-trash',
|
icon: 'pi pi-trash',
|
||||||
command: () => menuTargetTask.value && removeTask(menuTargetTask.value)
|
command: () => menuTargetTask.value && removeTask(menuTargetTask.value),
|
||||||
|
disabled: isExpanded.value || isInFolderView.value
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('loadWorkflow'),
|
label: t('loadWorkflow'),
|
||||||
@@ -208,6 +235,16 @@ const handlePreview = (task: TaskItemImpl) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const enterFolderView = (task: TaskItemImpl) => {
|
||||||
|
folderTask.value = task
|
||||||
|
updateVisibleTasks()
|
||||||
|
}
|
||||||
|
|
||||||
|
const exitFolderView = () => {
|
||||||
|
folderTask.value = null
|
||||||
|
updateVisibleTasks()
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
api.addEventListener('status', onStatus)
|
api.addEventListener('status', onStatus)
|
||||||
queueStore.update()
|
queueStore.update()
|
||||||
@@ -225,7 +262,7 @@ watch(
|
|||||||
visibleTasks.value.length === 0 ||
|
visibleTasks.value.length === 0 ||
|
||||||
visibleTasks.value.length > newTasks.length
|
visibleTasks.value.length > newTasks.length
|
||||||
) {
|
) {
|
||||||
visibleTasks.value = newTasks.slice(0, ITEMS_PER_PAGE)
|
updateVisibleTasks()
|
||||||
}
|
}
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
|
|||||||
@@ -38,15 +38,20 @@
|
|||||||
</Tag>
|
</Tag>
|
||||||
</div>
|
</div>
|
||||||
<div class="tag-wrapper">
|
<div class="tag-wrapper">
|
||||||
<Tag v-if="task.isHistory && flatOutputs.length > 1">
|
<Button
|
||||||
<span>{{ flatOutputs.length }}</span>
|
v-if="task.isHistory && flatOutputs.length > 1"
|
||||||
</Tag>
|
outlined
|
||||||
|
@click="handleOutputLengthClick"
|
||||||
|
>
|
||||||
|
<span style="font-weight: bold">{{ flatOutputs.length }}</span>
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import Button from 'primevue/button'
|
||||||
import Tag from 'primevue/tag'
|
import Tag from 'primevue/tag'
|
||||||
import ResultItem from './ResultItem.vue'
|
import ResultItem from './ResultItem.vue'
|
||||||
import { TaskItemDisplayStatus, type TaskItemImpl } from '@/stores/queueStore'
|
import { TaskItemDisplayStatus, type TaskItemImpl } from '@/stores/queueStore'
|
||||||
@@ -61,6 +66,7 @@ const flatOutputs = props.task.flatOutputs
|
|||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'contextmenu', value: { task: TaskItemImpl; event: MouseEvent }): void
|
(e: 'contextmenu', value: { task: TaskItemImpl; event: MouseEvent }): void
|
||||||
(e: 'preview', value: TaskItemImpl): void
|
(e: 'preview', value: TaskItemImpl): void
|
||||||
|
(e: 'task-output-length-clicked', value: TaskItemImpl): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const handleContextMenu = (e: MouseEvent) => {
|
const handleContextMenu = (e: MouseEvent) => {
|
||||||
@@ -71,6 +77,10 @@ const handlePreview = () => {
|
|||||||
emit('preview', props.task)
|
emit('preview', props.task)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleOutputLengthClick = () => {
|
||||||
|
emit('task-output-length-clicked', props.task)
|
||||||
|
}
|
||||||
|
|
||||||
const taskTagSeverity = (status: TaskItemDisplayStatus) => {
|
const taskTagSeverity = (status: TaskItemDisplayStatus) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case TaskItemDisplayStatus.Pending:
|
case TaskItemDisplayStatus.Pending:
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ const messages = {
|
|||||||
sortOrder: 'Sort Order'
|
sortOrder: 'Sort Order'
|
||||||
},
|
},
|
||||||
queueTab: {
|
queueTab: {
|
||||||
showFlatList: 'Show Flat List'
|
showFlatList: 'Show Flat List',
|
||||||
|
backToAllTasks: 'Back to All Tasks'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -51,7 +52,8 @@ const messages = {
|
|||||||
sortOrder: '排序顺序'
|
sortOrder: '排序顺序'
|
||||||
},
|
},
|
||||||
queueTab: {
|
queueTab: {
|
||||||
showFlatList: '平铺结果'
|
showFlatList: '平铺结果',
|
||||||
|
backToAllTasks: '返回'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -219,6 +219,33 @@ export class TaskItemImpl {
|
|||||||
app.nodeOutputs = toRaw(this.outputs)
|
app.nodeOutputs = toRaw(this.outputs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public flatten(): TaskItemImpl[] {
|
||||||
|
if (this.displayStatus !== TaskItemDisplayStatus.Completed) {
|
||||||
|
return [this]
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.flatOutputs.map(
|
||||||
|
(output: ResultItemImpl, i: number) =>
|
||||||
|
new TaskItemImpl(
|
||||||
|
this.taskType,
|
||||||
|
[
|
||||||
|
this.queueIndex,
|
||||||
|
`${this.promptId}-${i}`,
|
||||||
|
this.promptInputs,
|
||||||
|
this.extraData,
|
||||||
|
this.outputsToExecute
|
||||||
|
],
|
||||||
|
this.status,
|
||||||
|
{
|
||||||
|
[output.nodeId]: {
|
||||||
|
[output.mediaType]: [output]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[output]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
@@ -244,32 +271,7 @@ export const useQueueStore = defineStore('queue', {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
flatTasks(): TaskItemImpl[] {
|
flatTasks(): TaskItemImpl[] {
|
||||||
return this.tasks.flatMap((task: TaskItemImpl) => {
|
return this.tasks.flatMap((task: TaskItemImpl) => task.flatten())
|
||||||
if (task.displayStatus !== TaskItemDisplayStatus.Completed) {
|
|
||||||
return [task]
|
|
||||||
}
|
|
||||||
|
|
||||||
return task.flatOutputs.map(
|
|
||||||
(output: ResultItemImpl, i: number) =>
|
|
||||||
new TaskItemImpl(
|
|
||||||
task.taskType,
|
|
||||||
[
|
|
||||||
task.queueIndex,
|
|
||||||
`${task.promptId}-${i}`,
|
|
||||||
task.promptInputs,
|
|
||||||
task.extraData,
|
|
||||||
task.outputsToExecute
|
|
||||||
],
|
|
||||||
task.status,
|
|
||||||
{
|
|
||||||
[output.nodeId]: {
|
|
||||||
[output.mediaType]: [output]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[output]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
lastHistoryQueueIndex(state) {
|
lastHistoryQueueIndex(state) {
|
||||||
return state.historyTasks.length ? state.historyTasks[0].queueIndex : -1
|
return state.historyTasks.length ? state.historyTasks[0].queueIndex : -1
|
||||||
|
|||||||
Reference in New Issue
Block a user