Queue media preview (#449)

* output url

* Basic image previews

* Split out task item component

* Move task actions to context menu

* simplify

* Move spinner

* Lift context menu to tab scope

* Better tag

* Fix placeholder style

* nit

* Correctly handle cancelled

* nit

* Split out result item as separate component

* nit

* Fix center crop

* nit

* Simplify task item

* Flat list

* Show prompt id

* Make image draggable

* Disable preview for dragging

* Fix key

* Correctly handle task in expanded view

* Add preview
This commit is contained in:
Chenlei Hu
2024-08-15 23:26:38 -04:00
committed by GitHub
parent 9e3dffd7fd
commit e6d29656fa
8 changed files with 397 additions and 125 deletions

View File

@@ -1,125 +1,82 @@
<template>
<DataTable
v-if="tasks.length > 0"
:value="tasks"
dataKey="promptId"
class="queue-table"
>
<Column header="STATUS">
<template #body="{ data }">
<Tag :severity="taskTagSeverity(data.displayStatus)">
{{ data.displayStatus.toUpperCase() }}
</Tag>
</template>
</Column>
<Column header="TIME" :pt="{ root: { class: 'queue-time-cell' } }">
<template #body="{ data }">
<div v-if="data.isHistory" class="queue-time-cell-content">
{{ formatTime(data.executionTimeInSeconds) }}
</div>
<div v-else-if="data.isRunning" class="queue-time-cell-content">
<i class="pi pi-spin pi-spinner"></i>
</div>
<div v-else class="queue-time-cell-content">...</div>
</template>
</Column>
<Column
:pt="{
headerCell: {
class: 'queue-tool-header-cell'
},
bodyCell: {
class: 'queue-tool-body-cell'
}
}"
>
<template #header>
<Toast />
<ConfirmPopup />
<Button
icon="pi pi-trash"
text
severity="primary"
@click="confirmRemoveAll($event)"
<SideBarTabTemplate :title="$t('sideToolbar.queue')">
<template #tool-buttons>
<Button
:icon="isExpanded ? 'pi pi-chevron-up' : 'pi pi-chevron-down'"
text
severity="secondary"
@click="isExpanded = !isExpanded"
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 #body>
<div v-if="tasks.length > 0" class="queue-grid">
<TaskItem
v-for="task in tasks"
:key="task.key"
:task="task"
:isFlatTask="isExpanded"
@contextmenu="handleContextMenu"
/>
</template>
<template #body="{ data }">
<Button
icon="pi pi-file-export"
text
severity="primary"
@click="data.loadWorkflow()"
</div>
<div v-else>
<NoResultsPlaceholder
icon="pi pi-info-circle"
:title="$t('noTasksFound')"
:message="$t('noTasksFoundMessage')"
/>
<Button
icon="pi pi-times"
text
severity="secondary"
@click="removeTask(data)"
/>
</template>
</Column>
</DataTable>
<div v-else>
<NoResultsPlaceholder
icon="pi pi-info-circle"
:title="$t('noTasksFound')"
:message="$t('noTasksFoundMessage')"
/>
</div>
</div>
</template>
</SideBarTabTemplate>
<Toast />
<ConfirmPopup />
<ContextMenu ref="menu" :model="menuItems" />
</template>
<script setup lang="ts">
import DataTable from 'primevue/datatable'
import Column from 'primevue/column'
import Tag from 'primevue/tag'
import Button from 'primevue/button'
import ConfirmPopup from 'primevue/confirmpopup'
import Toast from 'primevue/toast'
import ContextMenu from 'primevue/contextmenu'
import TaskItem from './queue/TaskItem.vue'
import SideBarTabTemplate from './SidebarTabTemplate.vue'
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
import { useConfirm } from 'primevue/useconfirm'
import { useToast } from 'primevue/usetoast'
import {
TaskItemDisplayStatus,
TaskItemImpl,
useQueueStore
} from '@/stores/queueStore'
import { computed, onMounted, onUnmounted } from 'vue'
import { TaskItemImpl, useQueueStore } from '@/stores/queueStore'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { type MenuItem } from 'primevue/menuitem'
import { api } from '@/scripts/api'
const confirm = useConfirm()
const toast = useToast()
const queueStore = useQueueStore()
const { t } = useI18n()
const tasks = computed(() =>
isExpanded.value ? queueStore.flatTasks : queueStore.tasks
)
const tasks = computed(() => queueStore.tasks)
const taskTagSeverity = (status: TaskItemDisplayStatus) => {
switch (status) {
case TaskItemDisplayStatus.Pending:
return 'secondary'
case TaskItemDisplayStatus.Running:
return 'info'
case TaskItemDisplayStatus.Completed:
return 'success'
case TaskItemDisplayStatus.Failed:
return 'danger'
case TaskItemDisplayStatus.Cancelled:
return 'warning'
}
}
const formatTime = (time?: number) => {
if (time === undefined) {
return ''
}
return `${time.toFixed(2)}s`
}
const removeTask = (task: TaskItemImpl) => {
if (task.isRunning) {
api.interrupt()
}
queueStore.delete(task)
}
const removeAllTasks = async () => {
await queueStore.clear()
}
const confirmRemoveAll = (event) => {
confirm.require({
target: event.currentTarget,
@@ -147,30 +104,45 @@ const confirmRemoveAll = (event) => {
}
const onStatus = () => queueStore.update()
const menu = ref(null)
const menuTargetTask = ref<TaskItemImpl | null>(null)
const menuItems = computed<MenuItem[]>(() => {
return [
{
label: t('delete'),
icon: 'pi pi-trash',
command: () => removeTask(menuTargetTask.value!)
},
{
label: t('loadWorkflow'),
icon: 'pi pi-file-export',
command: () => menuTargetTask.value?.loadWorkflow()
}
]
})
const handleContextMenu = ({ task, event }) => {
menuTargetTask.value = task
menu.value.show(event)
}
onMounted(() => {
api.addEventListener('status', onStatus)
queueStore.update()
})
onUnmounted(() => {
api.removeEventListener('status', onStatus)
})
const isExpanded = ref(false)
</script>
<style>
.queue-tool-header-cell {
display: flex;
justify-content: flex-end;
}
.queue-tool-body-cell {
display: table-cell;
text-align: right !important;
}
</style>
<style scoped>
.queue-time-cell-content {
width: fit-content;
.queue-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
padding: 0.5rem;
gap: 0.5rem;
}
</style>