mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-31 05:19:53 +00:00
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:
@@ -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>
|
||||
|
||||
@@ -42,6 +42,7 @@ const props = defineProps({
|
||||
border-top: none;
|
||||
border-radius: 0;
|
||||
padding: 0.25rem 1rem;
|
||||
min-height: 2.5rem;
|
||||
}
|
||||
|
||||
.comfy-vue-side-bar-header-span {
|
||||
|
||||
81
src/components/sidebar/tabs/queue/ResultItem.vue
Normal file
81
src/components/sidebar/tabs/queue/ResultItem.vue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div class="result-container" ref="resultContainer">
|
||||
<Image
|
||||
v-if="result.mediaType === 'images'"
|
||||
:src="result.url"
|
||||
alt="Task Output"
|
||||
width="100%"
|
||||
height="100%"
|
||||
preview
|
||||
:pt="{ previewMask: { class: 'image-preview-mask' } }"
|
||||
/>
|
||||
<!-- TODO: handle more media types -->
|
||||
<div v-else class="task-result-preview">
|
||||
<i class="pi pi-file"></i>
|
||||
<span>{{ result.mediaType }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ResultItemImpl } from '@/stores/queueStore'
|
||||
import Image from 'primevue/image'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
result: ResultItemImpl
|
||||
}>()
|
||||
|
||||
const resultContainer = ref<HTMLElement | null>(null)
|
||||
|
||||
onMounted(() => {
|
||||
if (props.result.mediaType === 'images') {
|
||||
resultContainer.value.querySelectorAll('img').forEach((img) => {
|
||||
img.draggable = true
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.result-container {
|
||||
aspect-ratio: 1 / 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:deep(img) {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.p-image-preview {
|
||||
position: static;
|
||||
display: contents;
|
||||
}
|
||||
|
||||
:deep(.image-preview-mask) {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: auto;
|
||||
height: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
color: var(--p-image-preview-mask-color);
|
||||
transition:
|
||||
opacity var(--p-image-transition-duration),
|
||||
background var(--p-image-transition-duration);
|
||||
border-radius: 50%;
|
||||
}
|
||||
</style>
|
||||
144
src/components/sidebar/tabs/queue/TaskItem.vue
Normal file
144
src/components/sidebar/tabs/queue/TaskItem.vue
Normal file
@@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<!-- @contextmenu="handleContextMenu" -->
|
||||
<div class="task-item">
|
||||
<div class="task-result-preview">
|
||||
<div v-if="task.displayStatus === TaskItemDisplayStatus.Completed">
|
||||
<ResultItem v-if="flatOutputs.length" :result="flatOutputs[0]" />
|
||||
</div>
|
||||
<i
|
||||
v-else-if="task.displayStatus === TaskItemDisplayStatus.Running"
|
||||
class="pi pi-spin pi-spinner"
|
||||
></i>
|
||||
<span v-else-if="task.displayStatus === TaskItemDisplayStatus.Pending"
|
||||
>...</span
|
||||
>
|
||||
<i
|
||||
v-else-if="task.displayStatus === TaskItemDisplayStatus.Cancelled"
|
||||
class="pi pi-exclamation-triangle"
|
||||
></i>
|
||||
<i
|
||||
v-else-if="task.displayStatus === TaskItemDisplayStatus.Failed"
|
||||
class="pi pi-exclamation-circle"
|
||||
></i>
|
||||
</div>
|
||||
|
||||
<div class="task-item-details">
|
||||
<div class="tag-wrapper">
|
||||
<Tag :severity="taskTagSeverity(task.displayStatus)">
|
||||
<span v-html="taskStatusText(task.displayStatus)"></span>
|
||||
<span v-if="task.isHistory" class="task-time">
|
||||
{{ formatTime(task.executionTimeInSeconds) }}
|
||||
</span>
|
||||
<span v-if="isFlatTask" class="task-prompt-id">
|
||||
{{ task.promptId.split('-')[0] }}
|
||||
</span>
|
||||
</Tag>
|
||||
</div>
|
||||
<div class="tag-wrapper">
|
||||
<Tag v-if="task.isHistory && flatOutputs.length > 1">
|
||||
<span>{{ flatOutputs.length }}</span>
|
||||
</Tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Tag from 'primevue/tag'
|
||||
import ResultItem from './ResultItem.vue'
|
||||
import { TaskItemDisplayStatus, type TaskItemImpl } from '@/stores/queueStore'
|
||||
|
||||
const props = defineProps<{
|
||||
task: TaskItemImpl
|
||||
isFlatTask: boolean
|
||||
}>()
|
||||
|
||||
const flatOutputs = props.task.flatOutputs
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'contextmenu', { task: TaskItemImpl, event: MouseEvent }): void
|
||||
}>()
|
||||
|
||||
const handleContextMenu = (e: MouseEvent) => {
|
||||
emit('contextmenu', { task: props.task, event: e })
|
||||
}
|
||||
|
||||
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 'warn'
|
||||
}
|
||||
}
|
||||
|
||||
const taskStatusText = (status: TaskItemDisplayStatus) => {
|
||||
switch (status) {
|
||||
case TaskItemDisplayStatus.Pending:
|
||||
return 'Pending'
|
||||
case TaskItemDisplayStatus.Running:
|
||||
return 'Running'
|
||||
case TaskItemDisplayStatus.Completed:
|
||||
return '<i class="pi pi-check" style="font-weight: bold"></i>'
|
||||
case TaskItemDisplayStatus.Failed:
|
||||
return 'Failed'
|
||||
case TaskItemDisplayStatus.Cancelled:
|
||||
return 'Cancelled'
|
||||
}
|
||||
}
|
||||
|
||||
const formatTime = (time?: number) => {
|
||||
if (time === undefined) {
|
||||
return ''
|
||||
}
|
||||
return `${time.toFixed(2)}s`
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.task-result-preview {
|
||||
aspect-ratio: 1 / 1;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.task-result-preview i,
|
||||
.task-result-preview span {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.task-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.task-item-details {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
padding: 0.6rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* In dark mode, transparent background color for tags is not ideal for tags that
|
||||
are floating on top of images. */
|
||||
.tag-wrapper {
|
||||
background-color: var(--p-primary-contrast-color);
|
||||
border-radius: 6px;
|
||||
display: inline-flex;
|
||||
}
|
||||
</style>
|
||||
10
src/i18n.ts
10
src/i18n.ts
@@ -2,6 +2,8 @@ import { createI18n } from 'vue-i18n'
|
||||
|
||||
const messages = {
|
||||
en: {
|
||||
delete: 'Delete',
|
||||
loadWorkflow: 'Load Workflow',
|
||||
settings: 'Settings',
|
||||
searchSettings: 'Search Settings',
|
||||
noResultsFound: 'No Results Found',
|
||||
@@ -15,10 +17,15 @@ const messages = {
|
||||
nodeLibrary: 'Node Library',
|
||||
nodeLibraryTab: {
|
||||
sortOrder: 'Sort Order'
|
||||
},
|
||||
queueTab: {
|
||||
showFlatList: 'Show Flat List'
|
||||
}
|
||||
}
|
||||
},
|
||||
zh: {
|
||||
delete: '删除',
|
||||
loadWorkflow: '加载工作流',
|
||||
settings: '设置',
|
||||
searchSettings: '搜索设置',
|
||||
noResultsFound: '未找到结果',
|
||||
@@ -32,6 +39,9 @@ const messages = {
|
||||
nodeLibrary: '节点库',
|
||||
nodeLibraryTab: {
|
||||
sortOrder: '排序顺序'
|
||||
},
|
||||
queueTab: {
|
||||
showFlatList: '平铺结果'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { api } from '@/scripts/api'
|
||||
import { app } from '@/scripts/app'
|
||||
import {
|
||||
validateTaskItem,
|
||||
import type {
|
||||
TaskItem,
|
||||
TaskType,
|
||||
TaskPrompt,
|
||||
TaskStatus,
|
||||
StatusWsMessageStatus,
|
||||
TaskOutput,
|
||||
StatusWsMessageStatus
|
||||
ResultItem
|
||||
} from '@/types/apiTypes'
|
||||
import { plainToClass } from 'class-transformer'
|
||||
import { validateTaskItem } from '@/types/apiTypes'
|
||||
import type { NodeId } from '@/types/comfyWorkflow'
|
||||
import { instanceToPlain, plainToClass } from 'class-transformer'
|
||||
import _ from 'lodash'
|
||||
import { defineStore } from 'pinia'
|
||||
import { toRaw } from 'vue'
|
||||
@@ -25,11 +27,43 @@ export enum TaskItemDisplayStatus {
|
||||
Cancelled = 'Cancelled'
|
||||
}
|
||||
|
||||
export class ResultItemImpl {
|
||||
filename: string
|
||||
subfolder?: string
|
||||
type: string
|
||||
|
||||
nodeId: NodeId
|
||||
// 'audio' | 'images' | ...
|
||||
mediaType: string
|
||||
|
||||
get url(): string {
|
||||
return api.apiURL(`/view?filename=${encodeURIComponent(this.filename)}&type=${this.type}&
|
||||
subfolder=${encodeURIComponent(this.subfolder || '')}&t=${+new Date()}`)
|
||||
}
|
||||
}
|
||||
|
||||
export class TaskItemImpl {
|
||||
taskType: TaskType
|
||||
prompt: TaskPrompt
|
||||
status?: TaskStatus
|
||||
outputs?: TaskOutput
|
||||
outputs: TaskOutput
|
||||
|
||||
get flatOutputs(): ResultItemImpl[] {
|
||||
if (!this.outputs) {
|
||||
return []
|
||||
}
|
||||
return Object.entries(this.outputs).flatMap(([nodeId, nodeOutputs]) =>
|
||||
Object.entries(nodeOutputs).flatMap(([mediaType, items]) =>
|
||||
(items as ResultItem[]).flatMap((item: ResultItem) =>
|
||||
plainToClass(ResultItemImpl, {
|
||||
...item,
|
||||
nodeId,
|
||||
mediaType
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
get apiTaskType(): APITaskType {
|
||||
switch (this.taskType) {
|
||||
@@ -41,6 +75,10 @@ export class TaskItemImpl {
|
||||
}
|
||||
}
|
||||
|
||||
get key() {
|
||||
return this.promptId + this.displayStatus
|
||||
}
|
||||
|
||||
get queueIndex() {
|
||||
return this.prompt[0]
|
||||
}
|
||||
@@ -174,6 +212,31 @@ export const useQueueStore = defineStore('queue', {
|
||||
...state.historyTasks
|
||||
]
|
||||
},
|
||||
flatTasks(): TaskItemImpl[] {
|
||||
return this.tasks.flatMap((task: TaskItemImpl) => {
|
||||
if (task.displayStatus !== TaskItemDisplayStatus.Completed) {
|
||||
return [task]
|
||||
}
|
||||
|
||||
return task.flatOutputs.map((output: ResultItemImpl, i: number) =>
|
||||
plainToClass(TaskItemImpl, {
|
||||
...instanceToPlain(task),
|
||||
prompt: [
|
||||
task.queueIndex,
|
||||
`${task.promptId}-${i}`,
|
||||
task.promptInputs,
|
||||
task.extraData,
|
||||
task.outputsToExecute
|
||||
],
|
||||
outputs: {
|
||||
[output.nodeId]: {
|
||||
[output.mediaType]: [instanceToPlain(output)]
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
})
|
||||
},
|
||||
lastHistoryQueueIndex(state) {
|
||||
return state.historyTasks.length ? state.historyTasks[0].queueIndex : -1
|
||||
}
|
||||
|
||||
@@ -6,11 +6,18 @@ import { colorPalettesSchema } from './colorPalette'
|
||||
const zNodeType = z.string()
|
||||
const zQueueIndex = z.number()
|
||||
const zPromptId = z.string()
|
||||
const zImageResult = z.object({
|
||||
const zResultItem = z.object({
|
||||
filename: z.string(),
|
||||
subfolder: z.string().optional(),
|
||||
type: z.string()
|
||||
})
|
||||
export type ResultItem = z.infer<typeof zResultItem>
|
||||
const zOutputs = z
|
||||
.object({
|
||||
audio: z.array(zResultItem).optional(),
|
||||
images: z.array(zResultItem).optional()
|
||||
})
|
||||
.passthrough()
|
||||
|
||||
// WS messages
|
||||
const zStatusWsMessageStatus = z.object({
|
||||
@@ -37,11 +44,7 @@ const zExecutingWsMessage = z.object({
|
||||
})
|
||||
|
||||
const zExecutedWsMessage = zExecutingWsMessage.extend({
|
||||
outputs: z
|
||||
.object({
|
||||
images: z.array(zImageResult)
|
||||
})
|
||||
.passthrough()
|
||||
outputs: zOutputs
|
||||
})
|
||||
|
||||
const zExecutionWsMessageBase = z.object({
|
||||
@@ -144,9 +147,6 @@ const zStatus = z.object({
|
||||
messages: z.array(zStatusMessage)
|
||||
})
|
||||
|
||||
// TODO: this is a placeholder
|
||||
const zOutput = z.any()
|
||||
|
||||
const zTaskPrompt = z.tuple([
|
||||
zQueueIndex,
|
||||
zPromptId,
|
||||
@@ -170,7 +170,7 @@ const zPendingTaskItem = z.object({
|
||||
prompt: zTaskPrompt
|
||||
})
|
||||
|
||||
const zTaskOutput = z.record(zNodeId, zOutput)
|
||||
const zTaskOutput = z.record(zNodeId, zOutputs)
|
||||
|
||||
const zHistoryTaskItem = z.object({
|
||||
taskType: z.literal('History'),
|
||||
|
||||
@@ -5,6 +5,7 @@ import { fromZodError } from 'zod-validation-error'
|
||||
// innerNode.id = `${this.node.id}:${i}`
|
||||
// Remove it after GroupNode is redesigned.
|
||||
export const zNodeId = z.union([z.number().int(), z.string()])
|
||||
export type NodeId = z.infer<typeof zNodeId>
|
||||
export const zSlotIndex = z.union([
|
||||
z.number().int(),
|
||||
z
|
||||
|
||||
Reference in New Issue
Block a user