import { useMemo, useState } from 'react'; import { useI18n, useT } from '../i18n'; import { localizePromptTemplateCategory, localizePromptTemplateSummary, } from '../i18n/content'; import type { PromptTemplateSummary } from '../types'; import { Icon } from './Icon'; interface Props { surface: 'image' | 'video'; templates: PromptTemplateSummary[]; onPreview: (tpl: PromptTemplateSummary) => void; } // Curated prompt-template gallery — one tab per surface (image / video). // Layout mirrors the Examples tab: a category filter row + a responsive // card grid that lazy-loads remote thumbnails (the upstream README hosts // images on CMS / Cloudflare Stream, both public). Each card opens a // preview modal with the full prompt body and attribution. export function PromptTemplatesTab({ surface, templates, onPreview }: Props) { const { locale, t } = useI18n(); const [filter, setFilter] = useState(''); const [category, setCategory] = useState('All'); const surfaceScoped = useMemo( () => templates.filter((tpl) => tpl.surface === surface), [templates, surface], ); const categories = useMemo(() => { const set = new Set(); for (const tpl of surfaceScoped) set.add(tpl.category || 'General'); return ['All', ...Array.from(set).sort()]; }, [surfaceScoped]); const filtered = useMemo(() => { const q = filter.trim().toLowerCase(); return surfaceScoped.filter((tpl) => { if (category !== 'All' && (tpl.category || 'General') !== category) { return false; } if (!q) return true; const localized = localizePromptTemplateSummary(locale, tpl); return ( tpl.title.toLowerCase().includes(q) || tpl.summary.toLowerCase().includes(q) || (tpl.tags ?? []).some((tag) => tag.toLowerCase().includes(q)) || localized.title.toLowerCase().includes(q) || localized.summary.toLowerCase().includes(q) || localized.category.toLowerCase().includes(q) || (localized.tags ?? []).some((tag) => tag.toLowerCase().includes(q)) ); }); }, [surfaceScoped, filter, category, locale]); if (surfaceScoped.length === 0) { return (
{surface === 'image' ? t('promptTemplates.emptyImage') : t('promptTemplates.emptyVideo')}
); } return (
setFilter(e.target.value)} /> {t('promptTemplates.countLabel', { n: filtered.length })}
{filtered.length === 0 ? (
{t('promptTemplates.emptyNoMatch')}
) : (
{filtered.map((tpl) => { const localized = localizePromptTemplateSummary(locale, tpl); return ( onPreview(localized)} /> ); })}
)}
{t('promptTemplates.attributionFooter')}
); } function PromptTemplateCard({ tpl, onPreview, }: { tpl: PromptTemplateSummary; onPreview: () => void; }) { const t = useT(); const sourceLabel = tpl.source.author ? `${tpl.source.author} · ${tpl.source.repo.split('/').pop()}` : tpl.source.repo.split('/').pop(); return ( ); }