Use responsive grid for templates dialog (#2791)

This commit is contained in:
bymyself
2025-03-01 15:08:41 -07:00
committed by GitHub
parent 09ab14ac81
commit e58fab92d1
12 changed files with 280 additions and 126 deletions

View File

@@ -2,13 +2,14 @@
<Card
ref="cardRef"
:data-testid="`template-workflow-${template.name}`"
class="w-64 group"
class="w-64 template-card rounded-2xl overflow-hidden cursor-pointer shadow-[0_10px_15px_-3px_rgba(0,0,0,0.08),0_4px_6px_-4px_rgba(0,0,0,0.05)]"
:pt="{
body: { class: 'p-0' }
}"
@click="$emit('loadWorkflow', template.name)"
>
<template #header>
<div
class="flex items-center justify-center cursor-pointer"
@click="$emit('loadWorkflow', template.name)"
>
<div class="flex items-center justify-center">
<div class="relative overflow-hidden rounded-t-lg">
<template v-if="template.mediaType === 'audio'">
<AudioThumbnail :src="baseThumbnailSrc" />
@@ -33,6 +34,7 @@
<DefaultThumbnail
:src="baseThumbnailSrc"
:alt="title"
:is-hovered="isHovered"
:hover-zoom="
template.thumbnailVariant === 'zoomHover'
? UPSCALE_ZOOM_SCALE
@@ -47,11 +49,23 @@
</div>
</div>
</template>
<template #subtitle>
<div
class="text-center py-2 opacity-85 group-hover:opacity-100 transition-opacity"
>
{{ title }}
<template #content>
<div class="flex items-center px-4 py-3">
<div class="flex-1">
<h3
class="line-clamp-1 text-lg font-normal text-surface-900 dark:text-surface-100"
>
{{ title }}
</h3>
<p class="line-clamp-2 text-sm text-surface-600 dark:text text-muted">
{{ template.description.replace(/[-_]/g, ' ') }}
</p>
</div>
<div
class="flex md:hidden xl:flex items-center justify-center ml-4 w-10 h-10 rounded-full bg-surface-100"
>
<i class="pi pi-angle-right text-2xl"></i>
</div>
</div>
</template>
</Card>
@@ -71,8 +85,8 @@ import HoverDissolveThumbnail from '@/components/templates/thumbnails/HoverDisso
import { TemplateInfo } from '@/types/workflowTemplateTypes'
import { normalizeI18nKey } from '@/utils/formatUtil'
const UPSCALE_ZOOM_SCALE = 38 // for upscale templates, exaggerate the hover zoom
const DEFAULT_ZOOM_SCALE = 6
const UPSCALE_ZOOM_SCALE = 16 // for upscale templates, exaggerate the hover zoom
const DEFAULT_ZOOM_SCALE = 5
const { sourceModule, categoryTitle, loading, template } = defineProps<{
sourceModule: string
@@ -86,18 +100,23 @@ const { t } = useI18n()
const cardRef = ref<HTMLElement | null>(null)
const isHovered = useElementHover(cardRef)
const thumbnailSrc = computed(() =>
sourceModule === 'default'
? `/templates/${template.name}`
: `/api/workflow_templates/${sourceModule}/${template.name}`
)
const getThumbnailUrl = (index = '') => {
const basePath =
sourceModule === 'default'
? `/templates/${template.name}`
: `/api/workflow_templates/${sourceModule}/${template.name}`
const baseThumbnailSrc = computed(
() => `${thumbnailSrc.value}-1.${template.mediaSubtype}`
)
// For templates from custom nodes, multiple images is not yet supported
const indexSuffix = sourceModule === 'default' && index ? `-${index}` : ''
const overlayThumbnailSrc = computed(
() => `${thumbnailSrc.value}-2.${template.mediaSubtype}`
return `${basePath}${indexSuffix}.${template.mediaSubtype}`
}
const baseThumbnailSrc = computed(() =>
getThumbnailUrl(sourceModule === 'default' ? '1' : '')
)
const overlayThumbnailSrc = computed(() =>
getThumbnailUrl(sourceModule === 'default' ? '2' : '')
)
const title = computed(() => {
@@ -112,10 +131,3 @@ defineEmits<{
loadWorkflow: [name: string]
}>()
</script>
<style lang="css" scoped>
.p-card {
--p-card-body-padding: 10px 0 0 0;
overflow: hidden;
}
</style>

View File

@@ -1,56 +1,75 @@
<template>
<div
class="flex h-96 overflow-y-hidden"
class="flex flex-col h-[83vh] w-[90vw] relative"
data-testid="template-workflows-content"
>
<div class="relative">
<ProgressSpinner
v-if="!workflowTemplatesStore.isLoaded"
class="absolute w-8 h-full inset-0"
/>
<Listbox
:model-value="selectedTab"
@update:model-value="handleTabSelection"
:options="tabs"
option-group-label="label"
option-label="title"
option-group-children="modules"
scroll-height="auto"
class="overflow-y-auto w-64 h-full"
listStyle="max-height:unset"
/>
</div>
<Carousel
class="carousel justify-center"
:value="selectedTab.templates"
:responsive-options="responsiveOptions"
:num-visible="4"
:num-scroll="3"
:key="`${selectedTab.moduleName}${selectedTab.title}`"
>
<template #item="slotProps">
<div class="p-2 justify-items-center">
<TemplateWorkflowCard
:sourceModule="selectedTab.moduleName"
:template="slotProps.data"
:loading="slotProps.data.name === workflowLoading"
:categoryTitle="selectedTab.title"
@loadWorkflow="loadWorkflow"
/>
<Button
v-if="isSmallScreen"
:icon="isSideNavOpen ? 'pi pi-chevron-left' : 'pi pi-chevron-right'"
text
class="absolute top-1/2 -translate-y-1/2 z-10"
:class="isSideNavOpen ? 'left-[19rem]' : 'left-2'"
@click="toggleSideNav"
/>
<Divider
class="m-0 [&::before]:border-surface-border/70 [&::before]:border-t-2"
/>
<div class="flex flex-1 relative overflow-hidden">
<aside
v-if="isSideNavOpen"
class="absolute translate-x-0 top-0 left-0 h-full w-80 shadow-md z-5 transition-transform duration-300 ease-in-out"
>
<ProgressSpinner
v-if="!workflowTemplatesStore.isLoaded"
class="absolute w-8 h-full inset-0"
/>
<TemplateWorkflowsSideNav
:tabs="tabs"
:selected-tab="selectedTab"
@update:selected-tab="handleTabSelection"
/>
</aside>
<div
class="flex-1 overflow-auto transition-all duration-300"
:class="{
'pl-80': isSideNavOpen || !isSmallScreen,
'pl-8': !isSideNavOpen && isSmallScreen
}"
>
<div v-if="selectedTab" class="flex flex-col px-12 pb-4">
<div class="py-3 text-left">
<h2 class="text-lg">{{ selectedTab.title }}</h2>
</div>
<div
class="grid grid-cols-[repeat(auto-fill,minmax(16rem,1fr))] gap-8 justify-items-center"
>
<div v-for="template in selectedTab.templates" :key="template.name">
<TemplateWorkflowCard
:sourceModule="selectedTab.moduleName"
:template="template"
:loading="template.name === workflowLoading"
:categoryTitle="selectedTab.title"
@loadWorkflow="loadWorkflow"
/>
</div>
</div>
</div>
</template>
</Carousel>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import Carousel from 'primevue/carousel'
import Listbox from 'primevue/listbox'
import { useBreakpoints } from '@vueuse/core'
import { useAsyncState } from '@vueuse/core'
import Button from 'primevue/button'
import Divider from 'primevue/divider'
import ProgressSpinner from 'primevue/progressspinner'
import { computed, onMounted, ref } from 'vue'
import { computed, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import TemplateWorkflowCard from '@/components/templates/TemplateWorkflowCard.vue'
import TemplateWorkflowsSideNav from '@/components/templates/TemplateWorkflowsSideNav.vue'
import { api } from '@/scripts/api'
import { app } from '@/scripts/app'
import { useDialogStore } from '@/stores/dialogStore'
@@ -59,43 +78,46 @@ import type { WorkflowTemplates } from '@/types/workflowTemplateTypes'
const { t } = useI18n()
const responsiveOptions = ref([
{
breakpoint: '1660px',
numVisible: 3,
numScroll: 2
},
{
breakpoint: '1360px',
numVisible: 2,
numScroll: 1
},
{
breakpoint: '960px',
numVisible: 1,
numScroll: 1
}
])
const breakpoints = useBreakpoints({
mobile: 0,
tablet: 768,
desktop: 1024
})
const isSmallScreen = breakpoints.between('mobile', 'desktop')
const isSideNavOpen = ref(!isSmallScreen.value)
const toggleSideNav = () => {
isSideNavOpen.value = !isSideNavOpen.value
}
watch(isSmallScreen, toggleSideNav)
const workflowTemplatesStore = useWorkflowTemplatesStore()
const { isReady } = useAsyncState(
workflowTemplatesStore.loadWorkflowTemplates,
null
)
const selectedTab = ref<WorkflowTemplates | null>(
workflowTemplatesStore?.defaultTemplate
workflowTemplatesStore.defaultTemplate
)
const workflowLoading = ref<string | null>(null)
const tabs = computed(() => workflowTemplatesStore.groupedTemplates)
onMounted(async () => {
await workflowTemplatesStore.loadWorkflowTemplates()
})
const handleTabSelection = (selection: WorkflowTemplates | null) => {
//Listbox allows deselecting so this special case is ignored here
if (selection !== selectedTab.value && selection !== null)
if (selection !== selectedTab.value && selection !== null) {
selectedTab.value = selection
// On small screens, close the sidebar when a category is selected
if (isSmallScreen.value) {
isSideNavOpen.value = false
}
}
}
const loadWorkflow = async (id: string) => {
if (!isReady.value) return
workflowLoading.value = id
let json
if (selectedTab.value.moduleName === 'default') {
@@ -120,9 +142,3 @@ const loadWorkflow = async (id: string) => {
return false
}
</script>
<style lang="css" scoped>
.carousel {
width: 66vw;
}
</style>

View File

@@ -0,0 +1,7 @@
<template>
<div>
<h3 class="px-4">
<span>{{ $t('templateWorkflows.title') }}</span>
</h3>
</div>
</template>

View File

@@ -0,0 +1,48 @@
<template>
<ScrollPanel class="w-80" style="height: calc(85vh - 48px)">
<Listbox
:model-value="selectedTab"
@update:model-value="handleTabSelection"
:options="tabs"
option-group-label="label"
option-label="title"
option-group-children="modules"
:pt="{
root: { class: 'w-full border-0 bg-transparent' },
list: { class: 'p-0' },
option: { class: 'px-12 py-3 text-lg' },
optionGroup: { class: 'p-0 text-left text-inherit' }
}"
listStyle="max-height:unset"
>
<template #optiongroup="slotProps">
<div class="text-left py-3 px-12">
<h2 class="text-lg">{{ slotProps.option.label }}</h2>
</div>
</template>
</Listbox>
</ScrollPanel>
</template>
<script setup lang="ts">
import Listbox from 'primevue/listbox'
import ScrollPanel from 'primevue/scrollpanel'
import type {
TemplateGroup,
WorkflowTemplates
} from '@/types/workflowTemplateTypes'
defineProps<{
tabs: TemplateGroup[]
selectedTab: WorkflowTemplates | null
}>()
const emit = defineEmits<{
(e: 'update:selectedTab', tab: WorkflowTemplates): void
}>()
const handleTabSelection = (tab: WorkflowTemplates) => {
emit('update:selectedTab', tab)
}
</script>

View File

@@ -1,6 +1,13 @@
<template>
<div class="relative w-64 h-64 rounded-t-lg overflow-hidden select-none">
<div v-if="!error" ref="contentRef">
<div
v-if="!error"
ref="contentRef"
class="w-64 h-64 object-cover transform-gpu transition-transform duration-1000 ease-out"
:style="
isHovered ? { transform: `scale(${1 + hoverZoom / 100})` } : undefined
"
>
<slot />
</div>
<div
@@ -19,6 +26,11 @@ import { onMounted, ref } from 'vue'
const error = ref(false)
const contentRef = ref<HTMLElement | null>(null)
const { hoverZoom = 4 } = defineProps<{
hoverZoom?: number
isHovered?: boolean
}>()
onMounted(() => {
const images = Array.from(contentRef.value?.getElementsByTagName('img') ?? [])
images.forEach((img) => {
@@ -28,3 +40,8 @@ onMounted(() => {
})
})
</script>
<style scoped>
img {
transition: transform 1s cubic-bezier(0.2, 0, 0.4, 1);
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<BaseThumbnail>
<BaseThumbnail :is-hovered="isHovered">
<img :src="baseImageSrc" :alt="alt" class="w-full h-full object-cover" />
<div ref="containerRef" class="absolute inset-0">
<img
@@ -26,6 +26,8 @@ import { ref, watch } from 'vue'
import BaseThumbnail from '@/components/templates/thumbnails/BaseThumbnail.vue'
const SLIDER_START_POSITION = 21
const { isHovered } = defineProps<{
baseImageSrc: string
overlayImageSrc: string
@@ -33,7 +35,7 @@ const { isHovered } = defineProps<{
isHovered?: boolean
}>()
const sliderPosition = ref(21)
const sliderPosition = ref(SLIDER_START_POSITION)
const containerRef = ref<HTMLElement | null>(null)
const { elementX, elementWidth, isOutside } = useMouseInElement(containerRef)

View File

@@ -1,6 +1,6 @@
<template>
<BaseThumbnail>
<div ref="containerRef" class="overflow-hidden">
<BaseThumbnail :hover-zoom="hoverZoom" :is-hovered="isHovered">
<div class="overflow-hidden">
<img
:src="src"
:alt="alt"
@@ -15,23 +15,12 @@
</template>
<script setup lang="ts">
import { useElementHover } from '@vueuse/core'
import { ref } from 'vue'
import BaseThumbnail from '@/components/templates/thumbnails/BaseThumbnail.vue'
const { hoverZoom = 8 } = defineProps<{
defineProps<{
src: string
alt: string
hoverZoom?: number
isHovered?: boolean
}>()
const containerRef = ref<HTMLElement | null>(null)
const isHovered = useElementHover(containerRef)
</script>
<style scoped>
img {
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<BaseThumbnail>
<BaseThumbnail :is-hovered="isHovered">
<div class="relative w-full h-full">
<img
:src="baseImageSrc"

View File

@@ -7,12 +7,14 @@ export const CORE_TEMPLATES = [
{
name: 'default',
mediaType: 'image',
mediaSubtype: 'webp'
mediaSubtype: 'webp',
description: 'Generate images from text descriptions.'
},
{
name: 'image2image',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Transform existing images using text prompts.',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/img2img/'
},
@@ -20,12 +22,14 @@ export const CORE_TEMPLATES = [
name: 'lora',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Apply LoRA models for specialized styles or subjects.',
tutorialUrl: 'https://comfyanonymous.github.io/ComfyUI_examples/lora/'
},
{
name: 'inpaint_example',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Edit specific parts of images seamlessly.',
thumbnailVariant: 'compareSlider',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/inpaint/'
@@ -34,6 +38,7 @@ export const CORE_TEMPLATES = [
name: 'inpain_model_outpainting',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Extend images beyond their original boundaries.',
thumbnailVariant: 'compareSlider',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/inpaint/#outpainting'
@@ -42,6 +47,7 @@ export const CORE_TEMPLATES = [
name: 'embedding_example',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Use textual inversion for consistent styles',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/textual_inversion_embeddings/'
},
@@ -49,12 +55,14 @@ export const CORE_TEMPLATES = [
name: 'gligen_textbox_example',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Specify the location and size of objects.',
tutorialUrl: 'https://comfyanonymous.github.io/ComfyUI_examples/gligen/'
},
{
name: 'lora_multiple',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Combine multiple LoRA models for unique results.',
tutorialUrl: 'https://comfyanonymous.github.io/ComfyUI_examples/lora/'
}
]
@@ -68,6 +76,7 @@ export const CORE_TEMPLATES = [
name: 'flux_dev_checkpoint_example',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Create images using Flux development models.',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/flux/#flux-dev-1'
},
@@ -75,6 +84,7 @@ export const CORE_TEMPLATES = [
name: 'flux_schnell',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Generate images quickly with Flux Schnell.',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/flux/#flux-schnell-1'
},
@@ -82,6 +92,7 @@ export const CORE_TEMPLATES = [
name: 'flux_fill_inpaint_example',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Fill in missing parts of images.',
thumbnailVariant: 'compareSlider',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/flux/#fill-inpainting-model'
@@ -90,6 +101,7 @@ export const CORE_TEMPLATES = [
name: 'flux_fill_outpaint_example',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Extend images using Flux outpainting.',
thumbnailVariant: 'compareSlider',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/flux/#fill-inpainting-model'
@@ -98,6 +110,7 @@ export const CORE_TEMPLATES = [
name: 'flux_canny_model_example',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Generate images from edge detection.',
thumbnailVariant: 'hoverDissolve',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/flux/#canny-and-depth'
@@ -106,6 +119,7 @@ export const CORE_TEMPLATES = [
name: 'flux_depth_lora_example',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Create images with depth-aware LoRA.',
thumbnailVariant: 'hoverDissolve',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/flux/#canny-and-depth'
@@ -114,6 +128,8 @@ export const CORE_TEMPLATES = [
name: 'flux_redux_model_example',
mediaType: 'image',
mediaSubtype: 'webp',
description:
'Transfer style from a reference image to guide image generation with Flux.',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/flux/#redux'
}
@@ -128,6 +144,7 @@ export const CORE_TEMPLATES = [
name: 'controlnet_example',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Control image generation with reference images.',
thumbnailVariant: 'hoverDissolve',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/controlnet/'
@@ -136,6 +153,7 @@ export const CORE_TEMPLATES = [
name: '2_pass_pose_worship',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Generate images from pose references.',
thumbnailVariant: 'hoverDissolve',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/controlnet/#pose-controlnet'
@@ -144,6 +162,7 @@ export const CORE_TEMPLATES = [
name: 'depth_controlnet',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Create images with depth-aware generation.',
thumbnailVariant: 'hoverDissolve',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/controlnet/#t2i-adapter-vs-controlnets'
@@ -152,6 +171,7 @@ export const CORE_TEMPLATES = [
name: 'depth_t2i_adapter',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Quickly generate depth-aware images with a T2I adapter.',
thumbnailVariant: 'hoverDissolve',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/controlnet/#t2i-adapter-vs-controlnets'
@@ -160,6 +180,7 @@ export const CORE_TEMPLATES = [
name: 'mixing_controlnets',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Combine multiple ControlNet models together.',
thumbnailVariant: 'hoverDissolve',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/controlnet/#mixing-controlnets'
@@ -175,6 +196,7 @@ export const CORE_TEMPLATES = [
name: 'hiresfix_latent_workflow',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Enhance image quality in latent space.',
thumbnailVariant: 'zoomHover',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/2_pass_txt2img/'
@@ -183,6 +205,7 @@ export const CORE_TEMPLATES = [
name: 'esrgan_example',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Use upscale models to enhance image quality.',
thumbnailVariant: 'zoomHover',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/upscale_models/'
@@ -191,6 +214,7 @@ export const CORE_TEMPLATES = [
name: 'hiresfix_esrgan_workflow',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Use upscale models during intermediate steps.',
thumbnailVariant: 'zoomHover',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/2_pass_txt2img/#non-latent-upscaling'
@@ -199,6 +223,7 @@ export const CORE_TEMPLATES = [
name: 'latent_upscale_different_prompt_model',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Upscale and change prompt across passes',
thumbnailVariant: 'zoomHover',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/2_pass_txt2img/#more-examples'
@@ -210,28 +235,34 @@ export const CORE_TEMPLATES = [
title: 'Video',
type: 'video',
templates: [
{
name: 'ltxv_image_to_video',
mediaType: 'image',
mediaSubtype: 'webp',
tutorialUrl: 'https://comfyanonymous.github.io/ComfyUI_examples/ltxv/'
},
{
name: 'ltxv_text_to_video',
mediaType: 'image',
mediaSubtype: 'webp',
tutorialUrl: 'https://comfyanonymous.github.io/ComfyUI_examples/ltxv/'
description: 'Generate videos from text descriptions.',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/ltxv/#text-to-video'
},
{
name: 'ltxv_image_to_video',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Convert still images into videos.',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/ltxv/#image-to-video'
},
{
name: 'mochi_text_to_video_example',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Create videos with Mochi model.',
tutorialUrl: 'https://comfyanonymous.github.io/ComfyUI_examples/mochi/'
},
{
name: 'hunyuan_video_text_to_video',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Generate videos using Hunyuan model.',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/hunyuan_video/'
},
@@ -239,6 +270,7 @@ export const CORE_TEMPLATES = [
name: 'image_to_video',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Transform images into animated videos.',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/video/#image-to-video'
},
@@ -246,6 +278,8 @@ export const CORE_TEMPLATES = [
name: 'txt_to_image_to_video',
mediaType: 'image',
mediaSubtype: 'webp',
description:
'Generate images from text and then convert them into videos.',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/video/#image-to-video'
}
@@ -260,6 +294,7 @@ export const CORE_TEMPLATES = [
name: 'sd3.5_simple_example',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Generate images with SD 3.5.',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/sd3/#sd35'
},
@@ -267,6 +302,8 @@ export const CORE_TEMPLATES = [
name: 'sd3.5_large_canny_controlnet_example',
mediaType: 'image',
mediaSubtype: 'webp',
description:
'Use edge detection to guide image generation with SD 3.5.',
thumbnailVariant: 'hoverDissolve',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/sd3/#sd35-controlnets'
@@ -275,6 +312,7 @@ export const CORE_TEMPLATES = [
name: 'sd3.5_large_depth',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Create depth-aware images with SD 3.5.',
thumbnailVariant: 'hoverDissolve',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/sd3/#sd35-controlnets'
@@ -283,6 +321,8 @@ export const CORE_TEMPLATES = [
name: 'sd3.5_large_blur',
mediaType: 'image',
mediaSubtype: 'webp',
description:
'Generate images from blurred reference images with SD 3.5.',
thumbnailVariant: 'hoverDissolve',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/sd3/#sd35-controlnets'
@@ -298,18 +338,22 @@ export const CORE_TEMPLATES = [
name: 'sdxl_simple_example',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Create high-quality images with SDXL.',
tutorialUrl: 'https://comfyanonymous.github.io/ComfyUI_examples/sdxl/'
},
{
name: 'sdxl_refiner_prompt_example',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Enhance SDXL outputs with refiners.',
tutorialUrl: 'https://comfyanonymous.github.io/ComfyUI_examples/sdxl/'
},
{
name: 'sdxl_revision_text_prompts',
mediaType: 'image',
mediaSubtype: 'webp',
description:
'Transfer concepts from reference images to guide image generation with SDXL.',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/sdxl/#revision'
},
@@ -317,6 +361,8 @@ export const CORE_TEMPLATES = [
name: 'sdxl_revision_zero_positive',
mediaType: 'image',
mediaSubtype: 'webp',
description:
'Add text prompts alongside reference images to guide image generation with SDXL.',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/sdxl/#revision'
},
@@ -324,6 +370,7 @@ export const CORE_TEMPLATES = [
name: 'sdxlturbo_example',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Generate images in a single step with SDXL Turbo.',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/sdturbo/'
}
@@ -338,6 +385,7 @@ export const CORE_TEMPLATES = [
name: 'area_composition',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Control image composition with areas.',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/area_composition/'
},
@@ -345,6 +393,7 @@ export const CORE_TEMPLATES = [
name: 'area_composition_reversed',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Reverse area composition workflow.',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/area_composition/'
},
@@ -352,6 +401,7 @@ export const CORE_TEMPLATES = [
name: 'area_composition_square_area_for_subject',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Create consistent subject placement.',
tutorialUrl:
'https://comfyanonymous.github.io/ComfyUI_examples/area_composition/#increasing-consistency-of-images-with-area-composition'
}
@@ -366,6 +416,7 @@ export const CORE_TEMPLATES = [
name: 'stable_zero123_example',
mediaType: 'image',
mediaSubtype: 'webp',
description: 'Generate 3D views from single images.',
tutorialUrl: 'https://comfyanonymous.github.io/ComfyUI_examples/3d/'
}
]
@@ -379,6 +430,7 @@ export const CORE_TEMPLATES = [
name: 'stable_audio_example',
mediaType: 'audio',
mediaSubtype: 'flac',
description: 'Generate audio from text descriptions.',
tutorialUrl: 'https://comfyanonymous.github.io/ComfyUI_examples/audio/'
}
]

View File

@@ -7,6 +7,7 @@ import PromptDialogContent from '@/components/dialog/content/PromptDialogContent
import SettingDialogContent from '@/components/dialog/content/SettingDialogContent.vue'
import SettingDialogHeader from '@/components/dialog/header/SettingDialogHeader.vue'
import TemplateWorkflowsContent from '@/components/templates/TemplateWorkflowsContent.vue'
import TemplateWorkflowsDialogHeader from '@/components/templates/TemplateWorkflowsDialogHeader.vue'
import { t } from '@/i18n'
import { type ShowDialogOptions, useDialogStore } from '@/stores/dialogStore'
@@ -80,6 +81,12 @@ export const useDialogService = () => {
key: 'global-template-workflows',
title: t('templateWorkflows.title'),
component: TemplateWorkflowsContent,
headerComponent: TemplateWorkflowsDialogHeader,
dialogComponentProps: {
pt: {
content: { class: '!px-0' }
}
},
props
})
}

View File

@@ -26,7 +26,8 @@ export const useWorkflowTemplatesStore = defineStore(
templates: templates.map((name) => ({
name,
mediaType: 'image',
mediaSubtype: 'jpg'
mediaSubtype: 'jpg',
description: name
}))
})
)

View File

@@ -4,6 +4,7 @@ export interface TemplateInfo {
mediaType: string
mediaSubtype: string
thumbnailVariant?: string
description: string
}
export interface WorkflowTemplates {
@@ -11,7 +12,9 @@ export interface WorkflowTemplates {
templates: TemplateInfo[]
title: string
}
export interface TemplateGroup {
label: string
icon?: string
modules: WorkflowTemplates[]
}