mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
feat(web): wire design-system gallery to create panel and prompt templates
Single entry-point flow: picking a design system from the gallery now
flows through to the create panel and prompt-template galleries instead
of just stamping config and stopping.
- DesignSystemsTab: replace whole-card click-to-select with an explicit
"Use for new project" button on each card. Thumb stays clickable for
preview; cursor only becomes "pointer" on the thumb and the action
button so the card body no longer suggests a hidden click target.
- EntryView: new selectDesignSystemFromGallery handler that updates the
workspace default DS and, when the DS surface is image/video, jumps to
the matching prompt-template top tab (image-templates / video-templates).
- NewProjectPanel: when the workspace default DS changes from the gallery,
mirror it into the picker primary slot, drop multi-select, and clear
the dsSelectionTouched flag so the existing default-sync effect keeps
things aligned afterwards. Media-aligned DS additionally switches the
create tab to image/video so the sidebar matches the gallery.
- PromptTemplatesTab: accept designSystemId/designSystems and narrow the
visible cards to categories implied by the DS metadata when no explicit
category filter is set. Show a dismissible hint
("Showing templates related to ...") with a "Show all templates"
escape hatch. Templates themselves stay surface-grouped; only the
visible card set narrows.
- New util utils/promptTemplateDsCategories.ts maps DS title/category/
summary to a curated set of prompt-template categories, kept here so
PromptTemplatesTab does not couple to per-template schema.
- i18n: add promptTemplates.dsNarrowedHint, promptTemplates.showAllTemplates,
and ds.selectForProject across all 20 locales (+ types.ts).
- index.css: styles for .ds-card-actions / .ds-card-select-btn and
.prompt-templates-ds-hint / -clear; switch ds-card cursor from pointer
to default and move pointer onto .ds-card-thumb.
Validated with pnpm guard, pnpm --filter @open-design/web typecheck, and
pnpm --filter @open-design/web test (392/392 pass, incl. locale-key parity).
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -223,19 +223,7 @@ function DesignSystemCard({
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={`ds-card ${active ? 'active' : ''}`}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={onSelect}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
onSelect();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div ref={ref} className={`ds-card ${active ? 'active' : ''}`}>
|
||||
<div
|
||||
className="ds-card-thumb"
|
||||
onClick={(e) => {
|
||||
@@ -298,6 +286,19 @@ function DesignSystemCard({
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="ds-card-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="ds-card-select-btn"
|
||||
data-testid="design-system-select"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onSelect();
|
||||
}}
|
||||
>
|
||||
{t('ds.selectForProject')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -299,6 +299,18 @@ export function EntryView({
|
||||
setPreviewSystemId(id);
|
||||
}
|
||||
|
||||
/** Persist workspace default, mirror sidebar picker state, and jump to media galleries when relevant. */
|
||||
function selectDesignSystemFromGallery(id: string) {
|
||||
onChangeDefaultDesignSystem(id);
|
||||
const ds = designSystems.find((d) => d.id === id);
|
||||
const surf = ds?.surface ?? 'web';
|
||||
if (surf === 'image') {
|
||||
setTopTab('image-templates');
|
||||
} else if (surf === 'video') {
|
||||
setTopTab('video-templates');
|
||||
}
|
||||
}
|
||||
|
||||
const previewSystem = useMemo(
|
||||
() => (previewSystemId ? designSystems.find((d) => d.id === previewSystemId) ?? null : null),
|
||||
[designSystems, previewSystemId],
|
||||
@@ -634,7 +646,7 @@ export function EntryView({
|
||||
<DesignSystemsTab
|
||||
systems={designSystems}
|
||||
selectedId={defaultDesignSystemId}
|
||||
onSelect={onChangeDefaultDesignSystem}
|
||||
onSelect={selectDesignSystemFromGallery}
|
||||
onPreview={previewDesignSystem}
|
||||
/>
|
||||
) : null}
|
||||
@@ -654,6 +666,8 @@ export function EntryView({
|
||||
<PromptTemplatesTab
|
||||
surface="image"
|
||||
templates={promptTemplates}
|
||||
designSystemId={defaultDesignSystemId}
|
||||
designSystems={designSystems}
|
||||
onPreview={setPreviewPromptTemplate}
|
||||
/>
|
||||
) : null}
|
||||
@@ -661,6 +675,8 @@ export function EntryView({
|
||||
<PromptTemplatesTab
|
||||
surface="video"
|
||||
templates={promptTemplates}
|
||||
designSystemId={defaultDesignSystemId}
|
||||
designSystems={designSystems}
|
||||
onPreview={setPreviewPromptTemplate}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -294,6 +294,27 @@ export function NewProjectPanel({
|
||||
setSelectedDsIds(ids);
|
||||
}
|
||||
|
||||
// Selecting a design system from the gallery is an explicit intent that
|
||||
// should win over any earlier in-panel picker tweaks: mirror it into the
|
||||
// primary slot, drop multi-select, and clear the touched flag so the
|
||||
// existing default-sync effect above keeps things aligned afterwards.
|
||||
useEffect(() => {
|
||||
if (!defaultDesignSystemId) return;
|
||||
setSelectedDsIds([defaultDesignSystemId]);
|
||||
setDsMulti(false);
|
||||
setDsSelectionTouched(false);
|
||||
}, [defaultDesignSystemId]);
|
||||
|
||||
// Media-oriented design systems pair with their create tabs so the sidebar
|
||||
// matches what the image/video template galleries imply.
|
||||
useEffect(() => {
|
||||
if (!defaultDesignSystemId) return;
|
||||
const ds = designSystems.find((d) => d.id === defaultDesignSystemId);
|
||||
const surf = ds?.surface ?? 'web';
|
||||
if (surf === 'image') setTab('image');
|
||||
else if (surf === 'video') setTab('video');
|
||||
}, [defaultDesignSystemId, designSystems]);
|
||||
|
||||
useEffect(() => {
|
||||
const el = tabsRef.current;
|
||||
if (!el) return;
|
||||
|
||||
@@ -4,7 +4,12 @@ import {
|
||||
localizePromptTemplateCategory,
|
||||
localizePromptTemplateSummary,
|
||||
} from '../i18n/content';
|
||||
import type { PromptTemplateSource, PromptTemplateSummary } from '../types';
|
||||
import type {
|
||||
DesignSystemSummary,
|
||||
PromptTemplateSource,
|
||||
PromptTemplateSummary,
|
||||
} from '../types';
|
||||
import { inferPromptTemplateCategoriesForDs } from '../utils/promptTemplateDsCategories';
|
||||
import { Icon } from './Icon';
|
||||
|
||||
// Stable, human-readable provider name used by the source filter and the
|
||||
@@ -27,6 +32,11 @@ function providerLabel(source: PromptTemplateSource): string {
|
||||
interface Props {
|
||||
surface: 'image' | 'video';
|
||||
templates: PromptTemplateSummary[];
|
||||
// When the workspace default design system is media-aligned, pass it in to
|
||||
// narrow the gallery to categories implied by the DS metadata. Templates
|
||||
// themselves stay surface-grouped; only the visible card set narrows.
|
||||
designSystemId?: string | null;
|
||||
designSystems?: DesignSystemSummary[];
|
||||
onPreview: (tpl: PromptTemplateSummary) => void;
|
||||
}
|
||||
|
||||
@@ -35,11 +45,34 @@ interface Props {
|
||||
// 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) {
|
||||
export function PromptTemplatesTab({
|
||||
surface,
|
||||
templates,
|
||||
designSystemId,
|
||||
designSystems,
|
||||
onPreview,
|
||||
}: Props) {
|
||||
const { locale, t } = useI18n();
|
||||
const [filter, setFilter] = useState('');
|
||||
const [category, setCategory] = useState<string>('All');
|
||||
const [source, setSource] = useState<string>('All');
|
||||
// When the user explicitly clears the DS narrowing, the hint disappears
|
||||
// until they pick a different DS (which remounts the inferred set).
|
||||
const [showAll, setShowAll] = useState(false);
|
||||
|
||||
const activeDs = useMemo(() => {
|
||||
if (!designSystemId || !designSystems) return null;
|
||||
return designSystems.find((d) => d.id === designSystemId) ?? null;
|
||||
}, [designSystemId, designSystems]);
|
||||
|
||||
const dsNarrowCategories = useMemo(() => {
|
||||
if (!activeDs) return null;
|
||||
const inferred = inferPromptTemplateCategoriesForDs(activeDs);
|
||||
if (!inferred || inferred.length === 0) return null;
|
||||
return new Set(inferred.map((c) => c.toLowerCase()));
|
||||
}, [activeDs]);
|
||||
|
||||
const dsNarrowActive = !!dsNarrowCategories && !showAll && category === 'All';
|
||||
|
||||
const surfaceScoped = useMemo(
|
||||
() => templates.filter((tpl) => tpl.surface === surface),
|
||||
@@ -67,6 +100,13 @@ export function PromptTemplatesTab({ surface, templates, onPreview }: Props) {
|
||||
if (category !== 'All' && (tpl.category || 'General') !== category) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
dsNarrowActive
|
||||
&& dsNarrowCategories
|
||||
&& !dsNarrowCategories.has((tpl.category || 'General').toLowerCase())
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (source !== 'All' && providerLabel(tpl.source) !== source) {
|
||||
return false;
|
||||
}
|
||||
@@ -83,7 +123,7 @@ export function PromptTemplatesTab({ surface, templates, onPreview }: Props) {
|
||||
|| providerLabel(tpl.source).toLowerCase().includes(q)
|
||||
);
|
||||
});
|
||||
}, [surfaceScoped, filter, category, source, locale]);
|
||||
}, [surfaceScoped, filter, category, source, locale, dsNarrowActive, dsNarrowCategories]);
|
||||
|
||||
if (surfaceScoped.length === 0) {
|
||||
return (
|
||||
@@ -97,6 +137,18 @@ export function PromptTemplatesTab({ surface, templates, onPreview }: Props) {
|
||||
|
||||
return (
|
||||
<div className="tab-panel prompt-templates-panel">
|
||||
{dsNarrowActive && activeDs ? (
|
||||
<div className="prompt-templates-ds-hint">
|
||||
<span>{t('promptTemplates.dsNarrowedHint', { title: activeDs.title })}</span>
|
||||
<button
|
||||
type="button"
|
||||
className="prompt-templates-ds-hint-clear"
|
||||
onClick={() => setShowAll(true)}
|
||||
>
|
||||
{t('promptTemplates.showAllTemplates')}
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="tab-panel-toolbar">
|
||||
<input
|
||||
placeholder={t('promptTemplates.searchPlaceholder')}
|
||||
|
||||
@@ -173,6 +173,8 @@ export const ar: Dict = {
|
||||
'promptTemplates.allSources': 'جميع المصادر',
|
||||
'promptTemplates.sourceFilterAria': 'تصفية حسب المصدر',
|
||||
'promptTemplates.attributionFooter': 'مقتبس من مكتبات الأوامر العامة. كل بطاقة تشير إلى المؤلف الأصلي.',
|
||||
'promptTemplates.dsNarrowedHint': 'عرض القوالب المتعلقة بـ «{title}».',
|
||||
'promptTemplates.showAllTemplates': 'عرض كل القوالب',
|
||||
'promptTemplates.openPreviewTitle': 'فتح الأمر والمعاينة',
|
||||
'promptTemplates.sourcePrefix': 'المصدر:',
|
||||
'promptTemplates.fetchError': 'تعذر تحميل جسم هذا القالب.',
|
||||
@@ -399,6 +401,7 @@ export const ar: Dict = {
|
||||
'ds.tokens': 'الرموز',
|
||||
'ds.specToggle': 'DESIGN.md',
|
||||
'ds.specLoading': 'جاري تحميل DESIGN.md...',
|
||||
'ds.selectForProject': 'استخدام في مشروع جديد',
|
||||
|
||||
'avatar.title': 'الحساب والإعدادات',
|
||||
'avatar.localCli': 'CLI محلي',
|
||||
|
||||
@@ -170,6 +170,8 @@ export const de: Dict = {
|
||||
'promptTemplates.emptyVideo': 'Noch keine Video-Prompt-Templates installiert.',
|
||||
'promptTemplates.emptyNoMatch': 'Keine Templates passen zu Ihrer Suche.',
|
||||
'promptTemplates.attributionFooter': 'Aus öffentlichen Prompt-Bibliotheken adaptiert. Jede Karte verlinkt zum ursprünglichen Autor.',
|
||||
'promptTemplates.dsNarrowedHint': 'Zeigt Vorlagen im Kontext von „{title}“.',
|
||||
'promptTemplates.showAllTemplates': 'Alle Vorlagen anzeigen',
|
||||
'promptTemplates.openPreviewTitle': 'Prompt und Vorschau öffnen',
|
||||
'promptTemplates.sourcePrefix': 'Quelle:',
|
||||
'promptTemplates.fetchError': 'Dieser Template-Inhalt konnte nicht geladen werden.',
|
||||
@@ -353,6 +355,7 @@ export const de: Dict = {
|
||||
'ds.tokens': 'Tokens',
|
||||
'ds.specToggle': 'DESIGN.md',
|
||||
'ds.specLoading': 'DESIGN.md wird geladen…',
|
||||
'ds.selectForProject': 'Für neues Projekt verwenden',
|
||||
|
||||
'avatar.title': 'Konto & Einstellungen',
|
||||
'avatar.localCli': 'Lokale CLI',
|
||||
|
||||
@@ -169,6 +169,8 @@ export const en: Dict = {
|
||||
'promptTemplates.emptyVideo': 'No video prompt templates installed yet.',
|
||||
'promptTemplates.emptyNoMatch': 'No templates match your search.',
|
||||
'promptTemplates.attributionFooter': 'Adapted from public prompt libraries. Each card links back to the original author.',
|
||||
'promptTemplates.dsNarrowedHint': 'Showing templates related to “{title}”.',
|
||||
'promptTemplates.showAllTemplates': 'Show all templates',
|
||||
'promptTemplates.openPreviewTitle': 'Open prompt and preview',
|
||||
'promptTemplates.sourcePrefix': 'Source:',
|
||||
'promptTemplates.fetchError': 'Could not load this template body.',
|
||||
@@ -410,6 +412,7 @@ export const en: Dict = {
|
||||
'ds.tokens': 'Tokens',
|
||||
'ds.specToggle': 'DESIGN.md',
|
||||
'ds.specLoading': 'Loading DESIGN.md…',
|
||||
'ds.selectForProject': 'Use for new project',
|
||||
|
||||
'avatar.title': 'Account & settings',
|
||||
'avatar.localCli': 'Local CLI',
|
||||
|
||||
@@ -171,6 +171,8 @@ export const esES: Dict = {
|
||||
'promptTemplates.emptyNoMatch': 'Ninguna plantilla coincide con tu búsqueda.',
|
||||
'promptTemplates.attributionFooter':
|
||||
'Adaptadas de bibliotecas públicas de prompts. Cada tarjeta enlaza al autor original.',
|
||||
'promptTemplates.dsNarrowedHint': 'Mostrando plantillas relacionadas con «{title}».',
|
||||
'promptTemplates.showAllTemplates': 'Mostrar todas las plantillas',
|
||||
'promptTemplates.openPreviewTitle': 'Abrir prompt y vista previa',
|
||||
'promptTemplates.sourcePrefix': 'Fuente:',
|
||||
'promptTemplates.fetchError': 'No se pudo cargar el cuerpo de esta plantilla.',
|
||||
@@ -354,6 +356,7 @@ export const esES: Dict = {
|
||||
'ds.tokens': 'Tokens',
|
||||
'ds.specToggle': 'DESIGN.md',
|
||||
'ds.specLoading': 'Cargando DESIGN.md…',
|
||||
'ds.selectForProject': 'Usar en proyecto nuevo',
|
||||
|
||||
'avatar.title': 'Cuenta y ajustes',
|
||||
'avatar.localCli': 'CLI local',
|
||||
|
||||
@@ -170,6 +170,8 @@ export const fa: Dict = {
|
||||
'promptTemplates.emptyNoMatch': 'هیچ قالبی با جستجوی شما مطابقت ندارد.',
|
||||
'promptTemplates.attributionFooter':
|
||||
'برگرفته از کتابخانههای عمومی پرامپت. هر کارت به نویسنده اصلی لینک دارد.',
|
||||
'promptTemplates.dsNarrowedHint': 'نمایش قالبهای مرتبط با «{title}».',
|
||||
'promptTemplates.showAllTemplates': 'نمایش همه قالبها',
|
||||
'promptTemplates.openPreviewTitle': 'باز کردن پرامپت و پیشنمایش',
|
||||
'promptTemplates.sourcePrefix': 'منبع:',
|
||||
'promptTemplates.fetchError': 'بارگذاری متن این قالب ممکن نبود.',
|
||||
@@ -410,6 +412,7 @@ export const fa: Dict = {
|
||||
'ds.tokens': 'توکنها',
|
||||
'ds.specToggle': 'DESIGN.md',
|
||||
'ds.specLoading': 'بارگذاری DESIGN.md…',
|
||||
'ds.selectForProject': 'استفاده در پروژه جدید',
|
||||
|
||||
'avatar.title': 'حساب و تنظیمات',
|
||||
'avatar.localCli': 'CLI محلی',
|
||||
|
||||
@@ -173,6 +173,8 @@ export const fr: Dict = {
|
||||
'promptTemplates.allSources': 'Toutes les sources',
|
||||
'promptTemplates.sourceFilterAria': 'Filtrer par source',
|
||||
'promptTemplates.attributionFooter': 'Adapté de bibliothèques de prompts publiques. Chaque carte renvoie vers l\'auteur original.',
|
||||
'promptTemplates.dsNarrowedHint': 'Affiche les modèles liés à « {title} ».',
|
||||
'promptTemplates.showAllTemplates': 'Afficher tous les modèles',
|
||||
'promptTemplates.openPreviewTitle': 'Ouvrir le prompt et l\'aperçu',
|
||||
'promptTemplates.sourcePrefix': 'Source :',
|
||||
'promptTemplates.fetchError': 'Impossible de charger le contenu de ce modèle.',
|
||||
@@ -399,6 +401,7 @@ export const fr: Dict = {
|
||||
'ds.tokens': 'Jetons',
|
||||
'ds.specToggle': 'DESIGN.md',
|
||||
'ds.specLoading': 'Chargement du DESIGN.md…',
|
||||
'ds.selectForProject': 'Utiliser pour un nouveau projet',
|
||||
|
||||
'avatar.title': 'Compte et paramètres',
|
||||
'avatar.localCli': 'CLI local',
|
||||
|
||||
@@ -171,6 +171,8 @@ export const hu: Dict = {
|
||||
'promptTemplates.emptyVideo': 'Még nincs telepített videósablon.',
|
||||
'promptTemplates.emptyNoMatch': 'Egy sablon sem felel meg a keresésnek.',
|
||||
'promptTemplates.attributionFooter': 'Nyilvános prompt-tárakból átvéve. Minden kártya az eredeti szerzőre mutat.',
|
||||
'promptTemplates.dsNarrowedHint': 'A(z) „{title}” rendszerhez kapcsolódó sablonok láthatók.',
|
||||
'promptTemplates.showAllTemplates': 'Összes sablon megjelenítése',
|
||||
'promptTemplates.openPreviewTitle': 'Prompt és előnézet megnyitása',
|
||||
'promptTemplates.sourcePrefix': 'Forrás:',
|
||||
'promptTemplates.fetchError': 'A sablon törzse nem tölthető be.',
|
||||
@@ -399,6 +401,7 @@ export const hu: Dict = {
|
||||
'ds.tokens': 'Tokenek',
|
||||
'ds.specToggle': 'DESIGN.md',
|
||||
'ds.specLoading': 'DESIGN.md betöltése…',
|
||||
'ds.selectForProject': 'Új projektben használom',
|
||||
|
||||
'avatar.title': 'Fiók és beállítások',
|
||||
'avatar.localCli': 'Helyi CLI',
|
||||
|
||||
@@ -168,6 +168,8 @@ export const id: Dict = {
|
||||
'promptTemplates.emptyVideo': 'Belum ada templat prompt video.',
|
||||
'promptTemplates.emptyNoMatch': 'Tidak ada templat yang cocok dengan pencarianmu.',
|
||||
'promptTemplates.attributionFooter': 'Diadaptasi dari library prompt publik. Tiap kartu menautkan ke author asli.',
|
||||
'promptTemplates.dsNarrowedHint': 'Menampilkan templat terkait "{title}".',
|
||||
'promptTemplates.showAllTemplates': 'Tampilkan semua templat',
|
||||
'promptTemplates.openPreviewTitle': 'Buka prompt dan pratinjau',
|
||||
'promptTemplates.sourcePrefix': 'Sumber:',
|
||||
'promptTemplates.fetchError': 'Tidak bisa memuat isi templat ini.',
|
||||
@@ -404,6 +406,7 @@ export const id: Dict = {
|
||||
'ds.tokens': 'Token',
|
||||
'ds.specToggle': 'Spesifikasi',
|
||||
'ds.specLoading': 'Memuat spesifikasi...',
|
||||
'ds.selectForProject': 'Gunakan untuk proyek baru',
|
||||
|
||||
'avatar.title': 'Akun & pengaturan',
|
||||
'avatar.localCli': 'CLI lokal',
|
||||
|
||||
@@ -170,6 +170,8 @@ export const ja: Dict = {
|
||||
'promptTemplates.emptyVideo': '動画プロンプトテンプレートがまだインストールされていません。',
|
||||
'promptTemplates.emptyNoMatch': '検索に一致するテンプレートがありません。',
|
||||
'promptTemplates.attributionFooter': '公開プロンプトライブラリから引用。各カードは元の作者にリンクしています。',
|
||||
'promptTemplates.dsNarrowedHint': '「{title}」に関連するテンプレートを表示しています。',
|
||||
'promptTemplates.showAllTemplates': 'すべてのテンプレートを表示',
|
||||
'promptTemplates.openPreviewTitle': 'プロンプトとプレビューを開く',
|
||||
'promptTemplates.sourcePrefix': 'ソース:',
|
||||
'promptTemplates.fetchError': 'テンプレートの本文を読み込めませんでした。',
|
||||
@@ -352,6 +354,7 @@ export const ja: Dict = {
|
||||
'ds.tokens': 'トークン',
|
||||
'ds.specToggle': 'DESIGN.md',
|
||||
'ds.specLoading': 'DESIGN.md を読み込み中…',
|
||||
'ds.selectForProject': '新規プロジェクトで使う',
|
||||
|
||||
'avatar.title': 'アカウントと設定',
|
||||
'avatar.localCli': 'ローカル CLI',
|
||||
|
||||
@@ -171,6 +171,8 @@ export const ko: Dict = {
|
||||
'promptTemplates.emptyVideo': '설치된 비디오 프롬프트 템플릿이 없습니다.',
|
||||
'promptTemplates.emptyNoMatch': '검색어와 일치하는 템플릿이 없습니다.',
|
||||
'promptTemplates.attributionFooter': '공개 프롬프트 라이브러리를 기반으로 합니다. 각 카드는 원작자 페이지로 연결됩니다.',
|
||||
'promptTemplates.dsNarrowedHint': '「{title}」과 관련된 템플릿만 표시합니다.',
|
||||
'promptTemplates.showAllTemplates': '모든 템플릿 표시',
|
||||
'promptTemplates.openPreviewTitle': '프롬프트 열기 및 미리보기',
|
||||
'promptTemplates.sourcePrefix': '출처:',
|
||||
'promptTemplates.fetchError': '이 템플릿의 본문을 불러올 수 없습니다.',
|
||||
@@ -399,6 +401,7 @@ export const ko: Dict = {
|
||||
'ds.tokens': '토큰',
|
||||
'ds.specToggle': 'DESIGN.md',
|
||||
'ds.specLoading': 'DESIGN.md 불러오는 중…',
|
||||
'ds.selectForProject': '새 프로젝트에 사용',
|
||||
|
||||
'avatar.title': '계정 및 설정',
|
||||
'avatar.localCli': '로컬 CLI',
|
||||
|
||||
@@ -171,6 +171,8 @@ export const pl: Dict = {
|
||||
'promptTemplates.emptyVideo': 'Nie zainstalowano jeszcze szablonów promptów wideo.',
|
||||
'promptTemplates.emptyNoMatch': 'Brak szablonów pasujących do wyszukiwania.',
|
||||
'promptTemplates.attributionFooter': 'Zaadaptowano z publicznych bibliotek promptów. Każda karta linkuje do oryginalnego autora.',
|
||||
'promptTemplates.dsNarrowedHint': 'Wyświetlane są szablony powiązane z „{title}”.',
|
||||
'promptTemplates.showAllTemplates': 'Pokaż wszystkie szablony',
|
||||
'promptTemplates.openPreviewTitle': 'Otwórz prompt i podgląd',
|
||||
'promptTemplates.sourcePrefix': 'Źródło:',
|
||||
'promptTemplates.fetchError': 'Nie udało się załadować treści szablonu.',
|
||||
@@ -399,6 +401,7 @@ export const pl: Dict = {
|
||||
'ds.tokens': 'Tokeny',
|
||||
'ds.specToggle': 'DESIGN.md',
|
||||
'ds.specLoading': 'Ładowanie DESIGN.md…',
|
||||
'ds.selectForProject': 'Użyj w nowym projekcie',
|
||||
|
||||
'avatar.title': 'Konto i ustawienia',
|
||||
'avatar.localCli': 'Lokalne CLI',
|
||||
|
||||
@@ -168,6 +168,8 @@ export const ptBR: Dict = {
|
||||
'promptTemplates.emptyVideo': 'Nenhum template de prompt de vídeo instalado.',
|
||||
'promptTemplates.emptyNoMatch': 'Nenhum template corresponde à busca.',
|
||||
'promptTemplates.attributionFooter': 'Adaptado de bibliotecas públicas de prompts. Cada card aponta para o autor original.',
|
||||
'promptTemplates.dsNarrowedHint': 'Mostrando modelos relacionados a «{title}».',
|
||||
'promptTemplates.showAllTemplates': 'Mostrar todos os modelos',
|
||||
'promptTemplates.openPreviewTitle': 'Abrir prompt e prévia',
|
||||
'promptTemplates.sourcePrefix': 'Fonte:',
|
||||
'promptTemplates.fetchError': 'Não foi possível carregar o corpo deste template.',
|
||||
@@ -409,6 +411,7 @@ export const ptBR: Dict = {
|
||||
'ds.tokens': 'Tokens',
|
||||
'ds.specToggle': 'DESIGN.md',
|
||||
'ds.specLoading': 'Carregando DESIGN.md…',
|
||||
'ds.selectForProject': 'Usar em novo projeto',
|
||||
|
||||
'avatar.title': 'Conta e configurações',
|
||||
'avatar.localCli': 'CLI local',
|
||||
|
||||
@@ -168,6 +168,8 @@ export const ru: Dict = {
|
||||
'promptTemplates.emptyVideo': 'Шаблоны промптов видео не установлены.',
|
||||
'promptTemplates.emptyNoMatch': 'Нет шаблонов, соответствующих поиску.',
|
||||
'promptTemplates.attributionFooter': 'Адаптировано из публичных библиотек промптов. Каждая карточка ссылается на исходного автора.',
|
||||
'promptTemplates.dsNarrowedHint': 'Показаны шаблоны, связанные с «{title}».',
|
||||
'promptTemplates.showAllTemplates': 'Показать все шаблоны',
|
||||
'promptTemplates.openPreviewTitle': 'Открыть промпт и предпросмотр',
|
||||
'promptTemplates.sourcePrefix': 'Источник:',
|
||||
'promptTemplates.fetchError': 'Не удалось загрузить текст шаблона.',
|
||||
@@ -409,6 +411,7 @@ export const ru: Dict = {
|
||||
'ds.tokens': 'Токены',
|
||||
'ds.specToggle': 'DESIGN.md',
|
||||
'ds.specLoading': 'Загрузка DESIGN.md…',
|
||||
'ds.selectForProject': 'Использовать в новом проекте',
|
||||
|
||||
'avatar.title': 'Аккаунт и настройки',
|
||||
'avatar.localCli': 'Локальный CLI',
|
||||
|
||||
@@ -165,6 +165,8 @@ export const tr: Dict = {
|
||||
'promptTemplates.emptyVideo': 'Henüz video istemi şablonu yüklenmedi.',
|
||||
'promptTemplates.emptyNoMatch': 'Aramanıza uygun şablon bulunamadı.',
|
||||
'promptTemplates.attributionFooter': 'Herkese açık istem kütüphanesinden adape edildi. Her kart orijinal yazara bağlantılıdır.',
|
||||
'promptTemplates.dsNarrowedHint': '“{title}” ile ilişkili şablonlar gösteriliyor.',
|
||||
'promptTemplates.showAllTemplates': 'Tüm şablonları göster',
|
||||
'promptTemplates.openPreviewTitle': 'istemi aç ve önizle',
|
||||
'promptTemplates.sourcePrefix': 'Kaynak:',
|
||||
'promptTemplates.fetchError': 'Bu şablon gövdesi yüklenemedi.',
|
||||
@@ -392,6 +394,7 @@ export const tr: Dict = {
|
||||
'ds.tokens': 'Tokenler',
|
||||
'ds.specToggle': 'DESIGN.md',
|
||||
'ds.specLoading': 'DESIGN.md yükleniyor…',
|
||||
'ds.selectForProject': 'Yeni projede kullan',
|
||||
|
||||
'avatar.title': 'Hesap & ayarlar',
|
||||
'avatar.localCli': 'Yerel CLI',
|
||||
|
||||
@@ -172,6 +172,8 @@ export const uk: Dict = {
|
||||
'promptTemplates.emptyVideo': 'Шаблонів підказок для відео ще не встановлено.',
|
||||
'promptTemplates.emptyNoMatch': 'Немає шаблонів, що відповідають вашому пошуку.',
|
||||
'promptTemplates.attributionFooter': 'Адаптовано з публічних бібліотек підказок. Кожна картка посилається на автора.',
|
||||
'promptTemplates.dsNarrowedHint': 'Показано шаблони, пов’язані з «{title}».',
|
||||
'promptTemplates.showAllTemplates': 'Усі шаблони',
|
||||
'promptTemplates.openPreviewTitle': 'Відкрити підказку та попередній перегляд',
|
||||
'promptTemplates.sourcePrefix': 'Джерело:',
|
||||
'promptTemplates.fetchError': 'Не вдалося завантажити текст цього шаблону.',
|
||||
@@ -410,6 +412,7 @@ export const uk: Dict = {
|
||||
'ds.tokens': 'Токени',
|
||||
'ds.specToggle': 'DESIGN.md',
|
||||
'ds.specLoading': 'Завантаження DESIGN.md…',
|
||||
'ds.selectForProject': 'Використати в новому проєкті',
|
||||
|
||||
'avatar.title': 'Обліковий запис та налаштування',
|
||||
'avatar.localCli': 'Локальний CLI',
|
||||
|
||||
@@ -167,6 +167,8 @@ export const zhCN: Dict = {
|
||||
'promptTemplates.emptyVideo': '还没有安装视频 Prompt 模板。',
|
||||
'promptTemplates.emptyNoMatch': '没有匹配的模板。',
|
||||
'promptTemplates.attributionFooter': '改编自公开 Prompt 库,每张卡片都链接到原作者。',
|
||||
'promptTemplates.dsNarrowedHint': '正在显示与「{title}」相关的模板。',
|
||||
'promptTemplates.showAllTemplates': '显示全部模板',
|
||||
'promptTemplates.openPreviewTitle': '打开 Prompt 与预览',
|
||||
'promptTemplates.sourcePrefix': '来源:',
|
||||
'promptTemplates.fetchError': '无法加载此模板正文。',
|
||||
@@ -404,6 +406,7 @@ export const zhCN: Dict = {
|
||||
'ds.tokens': 'Token',
|
||||
'ds.specToggle': 'DESIGN.md',
|
||||
'ds.specLoading': '正在加载 DESIGN.md…',
|
||||
'ds.selectForProject': '用于新项目',
|
||||
|
||||
'avatar.title': '账户与设置',
|
||||
'avatar.localCli': '本机 CLI',
|
||||
|
||||
@@ -167,6 +167,8 @@ export const zhTW: Dict = {
|
||||
'promptTemplates.emptyVideo': '還沒有安裝影片 Prompt 範本。',
|
||||
'promptTemplates.emptyNoMatch': '沒有符合的範本。',
|
||||
'promptTemplates.attributionFooter': '改編自公開 Prompt 庫,每張卡片都連結到原作者。',
|
||||
'promptTemplates.dsNarrowedHint': '正在顯示與「{title}」相關的模板。',
|
||||
'promptTemplates.showAllTemplates': '顯示全部模板',
|
||||
'promptTemplates.openPreviewTitle': '開啟 Prompt 與預覽',
|
||||
'promptTemplates.sourcePrefix': '來源:',
|
||||
'promptTemplates.fetchError': '無法載入此範本文字。',
|
||||
@@ -404,6 +406,7 @@ export const zhTW: Dict = {
|
||||
'ds.tokens': 'Token',
|
||||
'ds.specToggle': 'DESIGN.md',
|
||||
'ds.specLoading': '正在載入 DESIGN.md…',
|
||||
'ds.selectForProject': '用於新專案',
|
||||
|
||||
'avatar.title': '帳號與設定',
|
||||
'avatar.localCli': '本機 CLI',
|
||||
|
||||
@@ -358,6 +358,8 @@ export interface Dict {
|
||||
'promptTemplates.emptyVideo': string;
|
||||
'promptTemplates.emptyNoMatch': string;
|
||||
'promptTemplates.attributionFooter': string;
|
||||
'promptTemplates.dsNarrowedHint': string;
|
||||
'promptTemplates.showAllTemplates': string;
|
||||
'promptTemplates.openPreviewTitle': string;
|
||||
'promptTemplates.sourcePrefix': string;
|
||||
'promptTemplates.fetchError': string;
|
||||
@@ -472,6 +474,7 @@ export interface Dict {
|
||||
'ds.tokens': string;
|
||||
'ds.specToggle': string;
|
||||
'ds.specLoading': string;
|
||||
'ds.selectForProject': string;
|
||||
|
||||
// Avatar menu (project topbar)
|
||||
'avatar.title': string;
|
||||
|
||||
+55
-1
@@ -4324,7 +4324,7 @@ button.connector-action.is-loading {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
cursor: default;
|
||||
transition: border-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
|
||||
}
|
||||
.ds-card:hover {
|
||||
@@ -4347,6 +4347,7 @@ button.connector-action.is-loading {
|
||||
background: var(--bg-subtle);
|
||||
overflow: hidden;
|
||||
border-bottom: 1px solid var(--border);
|
||||
cursor: pointer;
|
||||
}
|
||||
.ds-card-thumb iframe {
|
||||
width: 200%;
|
||||
@@ -4467,6 +4468,31 @@ button.connector-action.is-loading {
|
||||
.ds-card-swatches > span + span {
|
||||
border-left: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.ds-card-actions {
|
||||
display: flex;
|
||||
justify-content: stretch;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.ds-card-select-btn {
|
||||
flex: 1;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
padding: 8px 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--bg-subtle);
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
transition: border-color 0.15s ease, background 0.15s ease;
|
||||
}
|
||||
.ds-card-select-btn:hover {
|
||||
border-color: var(--accent);
|
||||
background: var(--accent-tint);
|
||||
color: var(--accent);
|
||||
}
|
||||
.ds-card.active .ds-card-select-btn {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
/* Legacy list classes kept for any consumer outside the tab — the gallery
|
||||
itself no longer renders these. Safe to remove once nothing references
|
||||
@@ -8735,6 +8761,34 @@ body.entry-resizing { cursor: col-resize; user-select: none; }
|
||||
Prompt template gallery
|
||||
============================================================ */
|
||||
.prompt-templates-panel { display: flex; flex-direction: column; gap: 16px; }
|
||||
.prompt-templates-ds-hint {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 10px 14px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--bg-subtle);
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
line-height: 1.4;
|
||||
}
|
||||
.prompt-templates-ds-hint-clear {
|
||||
margin-left: auto;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
padding: 4px 10px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--bg-panel);
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
}
|
||||
.prompt-templates-ds-hint-clear:hover {
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
}
|
||||
.prompt-templates-count {
|
||||
margin-left: auto;
|
||||
color: var(--text-muted);
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import type { DesignSystemSummary } from '../types';
|
||||
|
||||
/**
|
||||
* Maps a design system's authored metadata to prompt-template gallery categories.
|
||||
* Used when the workspace default design system is image/video-aligned so the
|
||||
* template tabs can narrow results without per-template schema coupling.
|
||||
*/
|
||||
export function inferPromptTemplateCategoriesForDs(
|
||||
ds: DesignSystemSummary,
|
||||
): string[] | null {
|
||||
const blob = `${ds.category} ${ds.title} ${ds.summary}`.toLowerCase();
|
||||
const out = new Set<string>();
|
||||
const add = (cats: string[]) => {
|
||||
for (const c of cats) out.add(c);
|
||||
};
|
||||
|
||||
if (/anime|manga|illustration|creative|artistic|editorial/i.test(blob)) {
|
||||
add([
|
||||
'Anime',
|
||||
'Anime / Manga',
|
||||
'Illustration',
|
||||
'Profile / Avatar',
|
||||
'Social Media Post',
|
||||
]);
|
||||
}
|
||||
if (/game|gaming|\bgui\b|\bui\b|interface/i.test(blob)) {
|
||||
add(['Game UI', 'App / Web Design']);
|
||||
}
|
||||
if (/e-?commerce|retail|shopping|product|saas|marketplace|store/i.test(blob)) {
|
||||
add(['Product', 'Social Media Post', 'Marketing', 'App / Web Design']);
|
||||
}
|
||||
if (/fintech|finance|crypto|payment|bank|stripe/i.test(blob)) {
|
||||
add(['App / Web Design', 'Data', 'Marketing', 'Branding']);
|
||||
}
|
||||
if (/developer|tool|api|backend|data|engineering|llm|ai\b/i.test(blob)) {
|
||||
add(['App / Web Design', 'Data', 'General']);
|
||||
}
|
||||
if (
|
||||
/video|cinematic|film|motion|advertis|marketing|media|social|meme|travel|vfx|fantasy|short form/i.test(
|
||||
blob,
|
||||
)
|
||||
) {
|
||||
add([
|
||||
'Cinematic',
|
||||
'Motion Graphics',
|
||||
'Advertising',
|
||||
'Marketing',
|
||||
'Social / Meme',
|
||||
'Travel',
|
||||
'VFX / Fantasy',
|
||||
'Short Form',
|
||||
]);
|
||||
}
|
||||
if (/automotive|car|vehicle|motor/i.test(blob)) {
|
||||
add(['Product', 'Cinematic', 'Advertising']);
|
||||
}
|
||||
if (/\bbrand/i.test(blob)) {
|
||||
add(['Branding']);
|
||||
}
|
||||
if (/infographic|data\s+viz|chart|diagram/i.test(blob)) {
|
||||
add(['Infographic', 'Data']);
|
||||
}
|
||||
if (/profile|avatar|portrait/i.test(blob)) {
|
||||
add(['Profile / Avatar']);
|
||||
}
|
||||
|
||||
return out.size > 0 ? [...out] : null;
|
||||
}
|
||||
Reference in New Issue
Block a user