mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-20 06:44:32 +00:00
## Summary Add support for overlaying provider logos on workflow template thumbnails at runtime. ## Changes - **What**: - Add `LogoInfo` interface and `logos` field to `TemplateInfo` type - Create `LogoOverlay.vue` component for rendering positioned logos - Fetch logo index from `templates/index_logo.json` in store - Add `getLogoUrl` helper to `useTemplateWorkflows` composable - Integrate `LogoOverlay` into `WorkflowTemplateSelectorDialog` ## Review Focus - Logo positioning uses Tailwind classes (e.g. `absolute bottom-2 right-2`) - Supports multiple logos per template with configurable size/opacity - Gracefully handles missing logos (returns empty string, renders nothing) - Templates must explicitly declare logos - no magic inference from models ## Dependencies Requires separate PR in workflow_templates repo to: 1. Update `index.schema.json` with logos definition 2. Add `logos` field to templates in `index.json` ## Screenshots (if applicable) <img width="869" height="719" alt="image" src="https://github.com/user-attachments/assets/65ed1ee4-fbb4-42c9-95d4-7e37813b3655" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8365-feat-add-provider-logo-overlays-to-workflow-template-thumbnails-2f66d73d365081309236c6b991cb6f7b) by [Unito](https://www.unito.io) --------- Co-authored-by: Subagent 5 <subagent@example.com> Co-authored-by: Amp <amp@ampcode.com>
118 lines
3.1 KiB
Vue
118 lines
3.1 KiB
Vue
<template>
|
|
<div
|
|
v-for="logo in validLogos"
|
|
:key="logo.key"
|
|
:class="
|
|
cn('pointer-events-none absolute z-10', logo.position ?? defaultPosition)
|
|
"
|
|
>
|
|
<div
|
|
v-show="!hasAllFailed(logo.providers)"
|
|
data-testid="logo-pill"
|
|
class="flex items-center gap-1.5 rounded-full bg-black/20 py-1 pr-2"
|
|
:style="{ opacity: logo.opacity ?? 0.85 }"
|
|
>
|
|
<div class="ml-0.5 flex items-center">
|
|
<img
|
|
v-for="(provider, providerIndex) in logo.providers"
|
|
:key="provider"
|
|
data-testid="logo-img"
|
|
:src="logo.urls[providerIndex]"
|
|
:alt="provider"
|
|
class="h-6 w-6 rounded-full border-2 border-white object-cover"
|
|
:class="{ relative: providerIndex > 0 }"
|
|
:style="
|
|
providerIndex > 0 ? { marginLeft: `${logo.gap ?? -6}px` } : {}
|
|
"
|
|
draggable="false"
|
|
@error="onImageError(provider)"
|
|
/>
|
|
</div>
|
|
<span class="text-sm font-medium text-white">
|
|
{{ logo.label }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, ref } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
|
|
import type { LogoInfo } from '@/platform/workflow/templates/types/template'
|
|
import { cn } from '@/utils/tailwindUtil'
|
|
|
|
const { t, locale } = useI18n()
|
|
|
|
function formatProviderList(providers: string[]): string {
|
|
const localeValue = String(locale.value)
|
|
try {
|
|
return new Intl.ListFormat(localeValue, {
|
|
style: 'long',
|
|
type: 'conjunction'
|
|
}).format(providers)
|
|
} catch {
|
|
return providers.join(t('templates.logoProviderSeparator'))
|
|
}
|
|
}
|
|
|
|
const {
|
|
logos,
|
|
getLogoUrl,
|
|
defaultPosition = 'top-2 left-2'
|
|
} = defineProps<{
|
|
logos: LogoInfo[]
|
|
getLogoUrl: (provider: string) => string
|
|
defaultPosition?: string
|
|
}>()
|
|
|
|
const failedLogos = ref(new Set<string>())
|
|
|
|
function onImageError(provider: string) {
|
|
failedLogos.value = new Set([...failedLogos.value, provider])
|
|
}
|
|
|
|
function hasAllFailed(providers: string[]): boolean {
|
|
return providers.every((p) => failedLogos.value.has(p))
|
|
}
|
|
|
|
interface ValidatedLogo {
|
|
key: string
|
|
providers: string[]
|
|
urls: string[]
|
|
label: string
|
|
position: string | undefined
|
|
opacity: number | undefined
|
|
gap: number | undefined
|
|
}
|
|
|
|
const validLogos = computed<ValidatedLogo[]>(() => {
|
|
const result: ValidatedLogo[] = []
|
|
|
|
logos.forEach((logo, index) => {
|
|
const providers = Array.isArray(logo.provider)
|
|
? logo.provider
|
|
: [logo.provider]
|
|
const urls = providers.map((p) => getLogoUrl(p))
|
|
const validProviders = providers.filter((_, i) => urls[i])
|
|
const validUrls = urls.filter((url) => url)
|
|
|
|
if (validProviders.length === 0) return
|
|
|
|
const providerKey = validProviders.join('-')
|
|
const layoutKey = `${logo.position ?? ''}-${logo.opacity ?? ''}-${logo.gap ?? ''}`
|
|
result.push({
|
|
key: providerKey ? `${providerKey}-${layoutKey}` : `logo-${index}`,
|
|
providers: validProviders,
|
|
urls: validUrls,
|
|
label: logo.label ?? formatProviderList(validProviders),
|
|
position: logo.position,
|
|
opacity: logo.opacity,
|
|
gap: logo.gap
|
|
})
|
|
})
|
|
|
|
return result
|
|
})
|
|
</script>
|