mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-07 00:20:07 +00:00
feat: add provider logo overlays to workflow template thumbnails (#8365)
## 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>
This commit is contained in:
@@ -8,6 +8,8 @@ import type { NavGroupData, NavItemData } from '@/types/navTypes'
|
||||
import { generateCategoryId, getCategoryIcon } from '@/utils/categoryUtil'
|
||||
import { normalizeI18nKey } from '@/utils/formatUtil'
|
||||
|
||||
import { zLogoIndex } from '../schemas/templateSchema'
|
||||
import type { LogoIndex } from '../schemas/templateSchema'
|
||||
import type {
|
||||
TemplateGroup,
|
||||
TemplateInfo,
|
||||
@@ -31,6 +33,7 @@ export const useWorkflowTemplatesStore = defineStore(
|
||||
const customTemplates = shallowRef<{ [moduleName: string]: string[] }>({})
|
||||
const coreTemplates = shallowRef<WorkflowTemplates[]>([])
|
||||
const englishTemplates = shallowRef<WorkflowTemplates[]>([])
|
||||
const logoIndex = shallowRef<LogoIndex>({})
|
||||
const isLoaded = ref(false)
|
||||
const knownTemplateNames = ref(new Set<string>())
|
||||
|
||||
@@ -475,15 +478,18 @@ export const useWorkflowTemplatesStore = defineStore(
|
||||
customTemplates.value = await api.getWorkflowTemplates()
|
||||
const locale = i18n.global.locale.value
|
||||
|
||||
const [coreResult, englishResult] = await Promise.all([
|
||||
api.getCoreWorkflowTemplates(locale),
|
||||
isCloud && locale !== 'en'
|
||||
? api.getCoreWorkflowTemplates('en')
|
||||
: Promise.resolve([])
|
||||
])
|
||||
const [coreResult, englishResult, logoIndexResult] =
|
||||
await Promise.all([
|
||||
api.getCoreWorkflowTemplates(locale),
|
||||
isCloud && locale !== 'en'
|
||||
? api.getCoreWorkflowTemplates('en')
|
||||
: Promise.resolve([]),
|
||||
fetchLogoIndex()
|
||||
])
|
||||
|
||||
coreTemplates.value = coreResult
|
||||
englishTemplates.value = englishResult
|
||||
logoIndex.value = logoIndexResult
|
||||
|
||||
const coreNames = coreTemplates.value.flatMap((category) =>
|
||||
category.templates.map((template) => template.name)
|
||||
@@ -498,6 +504,36 @@ export const useWorkflowTemplatesStore = defineStore(
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchLogoIndex(): Promise<LogoIndex> {
|
||||
try {
|
||||
const response = await api.fetchApi('/templates/index_logo.json')
|
||||
const contentType = response.headers.get('content-type')
|
||||
if (!contentType?.includes('application/json')) return {}
|
||||
const data = await response.json()
|
||||
const result = zLogoIndex.safeParse(data)
|
||||
return result.success ? result.data : {}
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
function getLogoUrl(provider: string): string {
|
||||
const logoPath = logoIndex.value[provider]
|
||||
if (!logoPath) return ''
|
||||
|
||||
// Validate path to prevent directory traversal and ensure safe file extensions
|
||||
const safePathPattern = /^[a-zA-Z0-9_\-./]+\.(png|jpg|jpeg|svg|webp)$/i
|
||||
if (
|
||||
!safePathPattern.test(logoPath) ||
|
||||
logoPath.includes('..') ||
|
||||
logoPath.startsWith('/')
|
||||
) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return api.fileURL(`/templates/${logoPath}`)
|
||||
}
|
||||
|
||||
function getEnglishMetadata(templateName: string): {
|
||||
tags?: string[]
|
||||
category?: string
|
||||
@@ -534,7 +570,8 @@ export const useWorkflowTemplatesStore = defineStore(
|
||||
loadWorkflowTemplates,
|
||||
knownTemplateNames,
|
||||
getTemplateByName,
|
||||
getEnglishMetadata
|
||||
getEnglishMetadata,
|
||||
getLogoUrl
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export const zLogoIndex = z.record(z.string(), z.string())
|
||||
|
||||
export type LogoIndex = z.infer<typeof zLogoIndex>
|
||||
@@ -1,3 +1,16 @@
|
||||
export interface LogoInfo {
|
||||
/** Provider name(s) matching index_logo.json. String for single, array for stacked logos. */
|
||||
provider: string | string[]
|
||||
/** Custom label text. If omitted, defaults to provider names joined with " & " */
|
||||
label?: string
|
||||
/** Gap between stacked logos in pixels. Negative for overlap effect. Default: -6 */
|
||||
gap?: number
|
||||
/** Tailwind positioning classes */
|
||||
position?: string
|
||||
/** Opacity 0-1, default 0.85 */
|
||||
opacity?: number
|
||||
}
|
||||
|
||||
export interface TemplateInfo {
|
||||
name: string
|
||||
/**
|
||||
@@ -47,6 +60,10 @@ export interface TemplateInfo {
|
||||
* If not specified, the template will be included on all distributions.
|
||||
*/
|
||||
includeOnDistributions?: TemplateIncludeOnDistributionEnum[]
|
||||
/**
|
||||
* Logo overlays to display on the template thumbnail.
|
||||
*/
|
||||
logos?: LogoInfo[]
|
||||
}
|
||||
|
||||
export enum TemplateIncludeOnDistributionEnum {
|
||||
|
||||
Reference in New Issue
Block a user