feat(messages): add workflow link fields for multiple languages

This commit is contained in:
Fatih Kadir Akın
2026-01-28 14:01:31 +03:00
parent 86fde53dd3
commit 0e14d5920c
29 changed files with 249 additions and 48 deletions

View File

@@ -144,6 +144,10 @@
"mcpCommandPlaceholder": "npx -y @mcp/server-name",
"mcpToolsPlaceholder": "tool1, tool2",
"add": "إضافة",
"workflowLink": "رابط تشغيل سير العمل",
"workflowLinkDescription": "رابط URL حيث يمكن للمستخدمين اختبار أو تجربة سير العمل هذا (يظهر عندما يكون للأمر خطوات سابقة/تالية)",
"workflowLinkCreateNote": "احفظ الأمر أولاً، ثم أضف أوامر متصلة (الخطوات السابقة/التالية) لتفعيل هذا الحقل.",
"workflowLinkPlaceholder": "https://example.com/workflow-demo",
"advancedOptions": "خيارات متقدمة",
"searchContributors": "البحث بواسطة اسم المستخدم...",
"noUsersFound": "لم يتم العثور على مستخدمين",
@@ -1230,8 +1234,8 @@
},
"connectedPrompts": {
"title": "تدفق الأوامر",
"description": "بعض الأوامر تعمل كسير عمل متعدد الخطوات. قم بربط أوامرك لإنشاء تسلسل.",
"addPromptFlow": "هذا الأمر له خطوة تالية",
"testWorkflow": "تشغيل سير العمل",
"addPrevious": "إضافة سابق",
"addNext": "إضافة تالي",
"addPreviousTitle": "إضافة أمر سابق",

View File

@@ -144,6 +144,10 @@
"mcpCommandPlaceholder": "npx -y @mcp/server-name",
"mcpToolsPlaceholder": "tool1, tool2",
"add": "Əlavə et",
"workflowLink": "İş Axını Linki",
"workflowLinkDescription": "İstifadəçilərin bu iş axınını sınaya biləcəyi URL (əvvəlki/sonrakı addımları olan promptlar üçün göstərilir)",
"workflowLinkCreateNote": "Əvvəlcə promptu saxlayın, sonra bu sahəni aktivləşdirmək üçün əlaqəli promptlar əlavə edin.",
"workflowLinkPlaceholder": "https://example.com/workflow-demo",
"advancedOptions": "Təkmılləşdirilmiş Seçimlər",
"searchContributors": "İstifadəçi adına görə axtar...",
"noUsersFound": "İstifadəçi tapılmadı",
@@ -1230,8 +1234,8 @@
},
"connectedPrompts": {
"title": "Prompt Axını",
"description": "Bəzi promptlar çox addımlı iş axınları kimi işləyir. Ardıcıllıq yaratmaq üçün promptlarınızı birləşdirin.",
"addPromptFlow": "Bu promptun növbəti addımı var",
"testWorkflow": "İş Axınını Çalışdır",
"addPrevious": "Əvvəlkini Əlavə et",
"addNext": "Növbətini Əlavə et",
"addPreviousTitle": "Əvvəlki Promptu Əlavə et",

View File

@@ -144,6 +144,10 @@
"mcpCommandPlaceholder": "npx -y @mcp/server-name",
"mcpToolsPlaceholder": "tool1, tool2",
"add": "Hinzufügen",
"workflowLink": "Workflow-Link",
"workflowLinkDescription": "Eine URL, wo Benutzer diesen Workflow testen oder demonstrieren können (wird angezeigt, wenn der Prompt vorherige/nächste Schritte hat)",
"workflowLinkCreateNote": "Speichern Sie zuerst den Prompt, dann fügen Sie verbundene Prompts (vorherige/nächste Schritte) hinzu, um dieses Feld zu aktivieren.",
"workflowLinkPlaceholder": "https://example.com/workflow-demo",
"advancedOptions": "Erweiterte Optionen",
"searchContributors": "Nach Benutzernamen suchen...",
"noUsersFound": "Keine Benutzer gefunden",
@@ -1230,8 +1234,8 @@
},
"connectedPrompts": {
"title": "Prompt-Ablauf",
"description": "Einige Prompts funktionieren als mehrstufige Workflows. Verbinden Sie Ihre Prompts, um eine Sequenz zu erstellen.",
"addPromptFlow": "Dieser Prompt hat einen nächsten Schritt",
"testWorkflow": "Workflow ausführen",
"addPrevious": "Vorherigen hinzufügen",
"addNext": "Nächsten hinzufügen",
"addPreviousTitle": "Vorherigen Prompt hinzufügen",

View File

@@ -144,7 +144,11 @@
"mcpCommandPlaceholder": "npx -y @mcp/server-name",
"mcpToolsPlaceholder": "tool1, tool2",
"add": "Προσθήκη",
"advancedOptions": "Προχωρημένες Επιλογές",
"workflowLink": "Σύνδεσμος Ροής Εργασίας",
"workflowLinkDescription": "URL όπου οι χρήστες μπορούν να δοκιμάσουν αυτή τη ροή εργασίας",
"workflowLinkCreateNote": "Αποθηκεύστε πρώτα το prompt, μετά προσθέστε συνδεδεμένα prompts για να ενεργοποιήσετε αυτό το πεδίο.",
"workflowLinkPlaceholder": "https://example.com/workflow-demo",
"advancedOptions": "Σύνθετες Επιλογές",
"searchContributors": "Αναζήτηση με όνομα χρήστη...",
"noUsersFound": "Δεν βρέθηκαν χρήστες",
"worksBestWith": "Λειτουργεί καλύτερα με",
@@ -1230,8 +1234,8 @@
},
"connectedPrompts": {
"title": "Ροή Prompts",
"description": "Ορισμένα prompts λειτουργούν ως ροές εργασίας πολλαπλών βημάτων. Συνδέστε τα prompts σας για να δημιουργήσετε μια ακολουθία.",
"addPromptFlow": "Αυτό το prompt έχει επόμενο βήμα",
"testWorkflow": "Εκτέλεση Ροής Εργασίας",
"addPrevious": "Προσθήκη Προηγούμενου",
"addNext": "Προσθήκη Επόμενου",
"addPreviousTitle": "Προσθήκη Προηγούμενου Prompt",

View File

@@ -144,6 +144,10 @@
"mcpCommandPlaceholder": "npx -y @mcp/server-name",
"mcpToolsPlaceholder": "tool1, tool2",
"add": "Add",
"workflowLink": "Test Workflow Link",
"workflowLinkDescription": "A URL where users can test or demo this workflow (shown when prompt has previous/next steps)",
"workflowLinkCreateNote": "Save the prompt first, then add connected prompts (previous/next steps) to enable this field.",
"workflowLinkPlaceholder": "https://example.com/workflow-demo",
"advancedOptions": "Advanced Options",
"worksBestWith": "Works best with",
"mcpTools": "MCP Tools",
@@ -1230,8 +1234,8 @@
},
"connectedPrompts": {
"title": "Prompt Flow",
"description": "Some prompts work as multi-step workflows. Connect your prompts to create a sequence.",
"addPromptFlow": "This prompt has a next step",
"testWorkflow": "Run Workflow",
"addPrevious": "Add Previous",
"addNext": "Add Next",
"addPreviousTitle": "Add Previous Prompt",

View File

@@ -141,6 +141,10 @@
"mcpCommandPlaceholder": "npx -y @mcp/server-name",
"mcpToolsPlaceholder": "tool1, tool2",
"add": "Añadir",
"workflowLink": "Enlace del Flujo de Trabajo",
"workflowLinkDescription": "Una URL donde los usuarios pueden probar este flujo de trabajo",
"workflowLinkCreateNote": "Guarda el prompt primero, luego añade prompts conectados para habilitar este campo.",
"workflowLinkPlaceholder": "https://example.com/workflow-demo",
"advancedOptions": "Opciones Avanzadas",
"searchContributors": "Buscar por nombre de usuario...",
"noUsersFound": "No se encontraron usuarios",
@@ -1230,8 +1234,8 @@
},
"connectedPrompts": {
"title": "Flujo de Prompts",
"description": "Algunos prompts funcionan como flujos de trabajo de varios pasos. Conecta tus prompts para crear una secuencia.",
"addPromptFlow": "Este prompt tiene un paso siguiente",
"testWorkflow": "Ejecutar Flujo",
"addPrevious": "Añadir Anterior",
"addNext": "Añadir Siguiente",
"addPreviousTitle": "Añadir Prompt Anterior",

View File

@@ -144,6 +144,10 @@
"mcpCommandPlaceholder": "npx -y @mcp/server-name",
"mcpToolsPlaceholder": "tool1, tool2",
"add": "اضافه",
"workflowLink": "لینک گردش کار",
"workflowLinkDescription": "لینکی که کاربران می‌توانند این گردش کار را آزمایش کنند",
"workflowLinkCreateNote": "ابتدا پرامپت را ذخیره کنید، سپس پرامپت‌های متصل اضافه کنید.",
"workflowLinkPlaceholder": "https://example.com/workflow-demo",
"advancedOptions": "گزینه‌های پیشرفته",
"searchContributors": "جستجو بر اساس نام کاربری...",
"noUsersFound": "کاربری یافت نشد",
@@ -1230,8 +1234,8 @@
},
"connectedPrompts": {
"title": "جریان پرامپت",
"description": "برخی پرامپت‌ها به صورت جریان‌های کاری چند مرحله‌ای عمل می‌کنند. پرامپت‌هایتان را متصل کنید تا یک دنباله بسازید.",
"addPromptFlow": "این پرامپت مرحله بعدی دارد",
"testWorkflow": "اجرای گردش کار",
"addPrevious": "افزودن قبلی",
"addNext": "افزودن بعدی",
"addPreviousTitle": "افزودن پرامپت قبلی",

View File

@@ -144,6 +144,10 @@
"mcpCommandPlaceholder": "npx -y @mcp/server-name",
"mcpToolsPlaceholder": "tool1, tool2",
"add": "Ajouter",
"workflowLink": "Lien du Workflow",
"workflowLinkDescription": "URL où les utilisateurs peuvent tester ce workflow",
"workflowLinkCreateNote": "Enregistrez d'abord le prompt, puis ajoutez des prompts connectés pour activer ce champ.",
"workflowLinkPlaceholder": "https://example.com/workflow-demo",
"advancedOptions": "Options Avancées",
"searchContributors": "Rechercher par nom d'utilisateur...",
"noUsersFound": "Aucun utilisateur trouvé",
@@ -1230,8 +1234,8 @@
},
"connectedPrompts": {
"title": "Flux de Prompts",
"description": "Certains prompts fonctionnent comme des workflows multi-étapes. Connectez vos prompts pour créer une séquence.",
"addPromptFlow": "Ce prompt a une étape suivante",
"testWorkflow": "Exécuter le Workflow",
"addPrevious": "Ajouter Précédent",
"addNext": "Ajouter Suivant",
"addPreviousTitle": "Ajouter Prompt Précédent",

View File

@@ -144,6 +144,10 @@
"mcpCommandPlaceholder": "npx -y @mcp/server-name",
"mcpToolsPlaceholder": "tool1, tool2",
"add": "הוסף",
"workflowLink": "קישור לתהליך עבודה",
"workflowLinkDescription": "כתובת URL שבה משתמשים יכולים לבדוק את תהליך העבודה הזה",
"workflowLinkCreateNote": "שמור קודם את הפרומפט, ואז הוסף פרומפטים מקושרים כדי להפעיל שדה זה.",
"workflowLinkPlaceholder": "https://example.com/workflow-demo",
"advancedOptions": "אפשרויות מתקדמות",
"searchContributors": "חפש לפי שם משתמש...",
"noUsersFound": "לא נמצאו משתמשים",
@@ -1230,8 +1234,8 @@
},
"connectedPrompts": {
"title": "זרימת פרומפטים",
"description": "חלק מהפרומפטים עובדים כתהליכי עבודה מרובי שלבים. חבר את הפרומפטים שלך ליצירת רצף.",
"addPromptFlow": "לפרומפט הזה יש שלב הבא",
"testWorkflow": "הפעל תהליך עבודה",
"addPrevious": "הוסף קודם",
"addNext": "הוסף הבא",
"addPreviousTitle": "הוסף פרומפט קודם",

View File

@@ -144,6 +144,10 @@
"mcpCommandPlaceholder": "npx -y @mcp/server-name",
"mcpToolsPlaceholder": "tool1, tool2",
"add": "Aggiungi",
"workflowLink": "Link del Workflow",
"workflowLinkDescription": "URL dove gli utenti possono testare questo workflow",
"workflowLinkCreateNote": "Salva prima il prompt, poi aggiungi prompt collegati per abilitare questo campo.",
"workflowLinkPlaceholder": "https://example.com/workflow-demo",
"advancedOptions": "Opzioni Avanzate",
"searchContributors": "Cerca per nome utente...",
"noUsersFound": "Nessun utente trovato",
@@ -1230,8 +1234,8 @@
},
"connectedPrompts": {
"title": "Flusso Prompt",
"description": "Alcuni prompt funzionano come workflow a più passaggi. Collega i tuoi prompt per creare una sequenza.",
"addPromptFlow": "Questo prompt ha un passo successivo",
"testWorkflow": "Esegui Workflow",
"addPrevious": "Aggiungi Precedente",
"addNext": "Aggiungi Successivo",
"addPreviousTitle": "Aggiungi Prompt Precedente",

View File

@@ -141,6 +141,10 @@
"mcpCommandPlaceholder": "npx -y @mcp/server-name",
"mcpToolsPlaceholder": "tool1, tool2",
"add": "追加",
"workflowLink": "ワークフローリンク",
"workflowLinkDescription": "ユーザーがこのワークフローをテストできるURL",
"workflowLinkCreateNote": "まずプロンプトを保存し、次に接続されたプロンプトを追加してこのフィールドを有効にしてください。",
"workflowLinkPlaceholder": "https://example.com/workflow-demo",
"advancedOptions": "詳細オプション",
"searchContributors": "ユーザー名で検索...",
"noUsersFound": "ユーザーが見つかりません",
@@ -1230,8 +1234,8 @@
},
"connectedPrompts": {
"title": "プロンプトフロー",
"description": "一部のプロンプトは複数ステップのワークフローとして機能します。プロンプトを接続してシーケンスを作成しましょう。",
"addPromptFlow": "このプロンプトには次のステップがあります",
"testWorkflow": "ワークフローを実行",
"addPrevious": "前を追加",
"addNext": "次を追加",
"addPreviousTitle": "前のプロンプトを追加",

View File

@@ -144,6 +144,10 @@
"mcpCommandPlaceholder": "npx -y @mcp/server-name",
"mcpToolsPlaceholder": "tool1, tool2",
"add": "추가",
"workflowLink": "워크플로 링크",
"workflowLinkDescription": "사용자가 이 워크플로를 테스트할 수 있는 URL",
"workflowLinkCreateNote": "먼저 프롬프트를 저장한 다음 연결된 프롬프트를 추가하여 이 필드를 활성화하세요.",
"workflowLinkPlaceholder": "https://example.com/workflow-demo",
"advancedOptions": "고급 옵션",
"searchContributors": "사용자명으로 검색...",
"noUsersFound": "사용자를 찾을 수 없습니다",
@@ -1230,8 +1234,8 @@
},
"connectedPrompts": {
"title": "프롬프트 흐름",
"description": "일부 프롬프트는 다단계 워크플로로 작동합니다. 프롬프트를 연결하여 시퀀스를 만드세요.",
"addPromptFlow": "이 프롬프트에는 다음 단계가 있습니다",
"testWorkflow": "워크플로 실행",
"addPrevious": "이전 추가",
"addNext": "다음 추가",
"addPreviousTitle": "이전 프롬프트 추가",

View File

@@ -144,6 +144,10 @@
"mcpCommandPlaceholder": "npx -y @mcp/server-name",
"mcpToolsPlaceholder": "tool1, tool2",
"add": "Toevoegen",
"workflowLink": "Workflow Link",
"workflowLinkDescription": "URL waar gebruikers deze workflow kunnen testen",
"workflowLinkCreateNote": "Sla eerst de prompt op en voeg daarna verbonden prompts toe om dit veld in te schakelen.",
"workflowLinkPlaceholder": "https://example.com/workflow-demo",
"advancedOptions": "Geavanceerde opties",
"worksBestWith": "Werkt het beste met",
"mcpTools": "MCP-tools",
@@ -1230,8 +1234,8 @@
},
"connectedPrompts": {
"title": "Promptstroom",
"description": "Sommige prompts werken als meerstaps-workflows. Verbind je prompts om een reeks te maken.",
"addPromptFlow": "Deze prompt heeft een volgende stap",
"testWorkflow": "Workflow Uitvoeren",
"addPrevious": "Vorige toevoegen",
"addNext": "Volgende toevoegen",
"addPreviousTitle": "Vorige prompt toevoegen",

View File

@@ -144,6 +144,10 @@
"mcpCommandPlaceholder": "npx -y @mcp/server-name",
"mcpToolsPlaceholder": "tool1, tool2",
"add": "Adicionar",
"workflowLink": "Link do Workflow",
"workflowLinkDescription": "URL onde usuários podem testar este workflow",
"workflowLinkCreateNote": "Salve o prompt primeiro, depois adicione prompts conectados para habilitar este campo.",
"workflowLinkPlaceholder": "https://example.com/workflow-demo",
"advancedOptions": "Opções Avançadas",
"searchContributors": "Buscar por nome de usuário...",
"noUsersFound": "Nenhum usuário encontrado",
@@ -1230,8 +1234,8 @@
},
"connectedPrompts": {
"title": "Fluxo de Prompts",
"description": "Alguns prompts funcionam como fluxos de trabalho de várias etapas. Conecte seus prompts para criar uma sequência.",
"addPromptFlow": "Este prompt tem um próximo passo",
"testWorkflow": "Executar Workflow",
"addPrevious": "Adicionar Anterior",
"addNext": "Adicionar Próximo",
"addPreviousTitle": "Adicionar Prompt Anterior",

View File

@@ -144,6 +144,10 @@
"mcpCommandPlaceholder": "npx -y @mcp/server-name",
"mcpToolsPlaceholder": "tool1, tool2",
"add": "Добавить",
"workflowLink": "Ссылка на рабочий процесс",
"workflowLinkDescription": "URL, где пользователи могут протестировать этот рабочий процесс",
"workflowLinkCreateNote": "Сначала сохраните промпт, затем добавьте связанные промпты, чтобы активировать это поле.",
"workflowLinkPlaceholder": "https://example.com/workflow-demo",
"advancedOptions": "Расширенные настройки",
"searchContributors": "Поиск по имени пользователя...",
"noUsersFound": "Пользователи не найдены",
@@ -1230,8 +1234,8 @@
},
"connectedPrompts": {
"title": "Поток промптов",
"description": "Некоторые промпты работают как многоэтапные рабочие процессы. Соедините свои промпты для создания последовательности.",
"addPromptFlow": "У этого промпта есть следующий шаг",
"testWorkflow": "Запустить рабочий процесс",
"addPrevious": "Добавить предыдущий",
"addNext": "Добавить следующий",
"addPreviousTitle": "Добавить предыдущий промпт",

View File

@@ -144,6 +144,10 @@
"mcpCommandPlaceholder": "npx -y @mcp/server-name",
"mcpToolsPlaceholder": "tool1, tool2",
"add": "Ekle",
"workflowLink": "İş Akışı Bağlantısı",
"workflowLinkDescription": "Kullanıcıların bu iş akışını test edebileceği URL",
"workflowLinkCreateNote": "Önce promptu kaydedin, ardından bu alanı etkinleştirmek için bağlı promptlar ekleyin.",
"workflowLinkPlaceholder": "https://example.com/workflow-demo",
"advancedOptions": "Gelişmiş Seçenekler",
"searchContributors": "Kullanıcı adına göre ara...",
"noUsersFound": "Kullanıcı bulunamadı",
@@ -1230,8 +1234,8 @@
},
"connectedPrompts": {
"title": "Prompt Akışı",
"description": "Bazı promptlar çok adımlı iş akışları olarak çalışır. Bir sıra oluşturmak için promptlarınızı bağlayın.",
"addPromptFlow": "Bu promptun sonraki adımı var",
"addPromptFlow": "Bu promptun bir sonraki adımı var",
"testWorkflow": "İş Akışını Çalıştır",
"addPrevious": "Önceki Ekle",
"addNext": "Sonraki Ekle",
"addPreviousTitle": "Önceki Prompt Ekle",

View File

@@ -141,6 +141,10 @@
"mcpCommandPlaceholder": "npx -y @mcp/server-name",
"mcpToolsPlaceholder": "tool1, tool2",
"add": "添加",
"workflowLink": "工作流链接",
"workflowLinkDescription": "用户可以测试此工作流的URL",
"workflowLinkCreateNote": "先保存提示词,然后添加连接的提示词以启用此字段。",
"workflowLinkPlaceholder": "https://example.com/workflow-demo",
"advancedOptions": "高级选项",
"searchContributors": "按用户名搜索...",
"noUsersFound": "未找到用户",
@@ -1225,13 +1229,13 @@
"keyRegenerated": "API 密钥重新生成成功",
"keyRevoked": "API 密钥已撤销",
"publicByDefault": "默认公开提示词",
"publicByDefaultDescription": "通过 MCP 保存提示词时,默认设为公开而非私有。",
"publicByDefaultDescription": "通过MCP保存提示词时默认设为公开而非私有。",
"settingUpdated": "设置已更新"
},
"connectedPrompts": {
"title": "提示词流程",
"description": "某些提示词作为多步骤工作流运行。连接您的提示词以创建序列。",
"addPromptFlow": "此提示词有下一步",
"testWorkflow": "运行工作流",
"addPrevious": "添加上一个",
"addNext": "添加下一个",
"addPreviousTitle": "添加上一个提示词",

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "prompts" ADD COLUMN "workflowLink" TEXT;

View File

@@ -129,6 +129,7 @@ model Prompt {
collectedBy Collection[]
bestWithModels String[] // Model slugs this prompt works best with (max 3), e.g. ["gpt-4o", "claude-3-5-sonnet"]
bestWithMCP Json? // MCP configs array, e.g. [{command: "npx -y @mcp/server", tools: ["tool1"]}]
workflowLink String? // URL to test/demo the workflow when prompt has previous/next connections
@@index([authorId])
@@index([categoryId])

View File

@@ -83,6 +83,7 @@ export default defineConfig({
{ name: "CodeRabbit", className: 'py-1', logo: '/sponsors/coderabbit.svg', darkLogo: '/sponsors/coderabbit-dark.svg', url: "https://coderabbit.link/fatih" },
{ name: "Sentry", className: 'py-1', logo: '/sponsors/sentry.svg', darkLogo: '/sponsors/sentry-dark.svg', url: "https://sentry.io/?utm_source=prompts.chat" },
{ name: "MitteAI", logo: '/sponsors/mitte.svg', darkLogo: '/sponsors/mitte-dark.svg', url: "https://mitte.ai/?utm_source=prompts.chat" },
{ name: "eachlabs", className: 'py-[6px]', logo: '/sponsors/eachlabs.png', darkLogo: '/sponsors/eachlabs-dark.png', url: "https://www.eachlabs.ai/?utm_source=promptschat&utm_medium=referral" },
{ name: "warp.dev", className: 'py-2', logo: '/sponsors/warp.svg', url: "https://warp.dev/?utm_source=prompts.chat" },
],
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

View File

@@ -26,6 +26,7 @@ const updatePromptSchema = z.object({
command: z.string(),
tools: z.array(z.string()).optional(),
})).optional(),
workflowLink: z.string().url().optional().or(z.literal("")).nullable(),
});
// Get single prompt
@@ -161,7 +162,7 @@ export async function PATCH(
);
}
const { tagIds, contributorIds, categoryId, mediaUrl, title, bestWithModels, bestWithMCP, ...data } = parsed.data;
const { tagIds, contributorIds, categoryId, mediaUrl, title, bestWithModels, bestWithMCP, workflowLink, ...data } = parsed.data;
// Regenerate slug if title changed
let newSlug: string | undefined;
@@ -178,6 +179,7 @@ export async function PATCH(
...(mediaUrl !== undefined && { mediaUrl: mediaUrl || null }),
...(bestWithModels !== undefined && { bestWithModels }),
...(bestWithMCP !== undefined && { bestWithMCP }),
...(workflowLink !== undefined && { workflowLink: workflowLink || null }),
};
// Update prompt
@@ -275,6 +277,64 @@ export async function PATCH(
});
}
// Propagate workflow link to all prompts in the same workflow chain (not "related" ones)
if (workflowLink !== undefined) {
const newWorkflowLink = workflowLink || null;
// Get all flow connections (excluding "related")
const allFlowConnections = await db.promptConnection.findMany({
where: {
label: { not: "related" },
},
select: {
sourceId: true,
targetId: true,
},
});
// Build adjacency list for the workflow graph
const adjacency = new Map<string, Set<string>>();
allFlowConnections.forEach((conn) => {
if (!adjacency.has(conn.sourceId)) adjacency.set(conn.sourceId, new Set());
if (!adjacency.has(conn.targetId)) adjacency.set(conn.targetId, new Set());
adjacency.get(conn.sourceId)!.add(conn.targetId);
adjacency.get(conn.targetId)!.add(conn.sourceId);
});
// BFS to find all prompts in the same workflow chain
const workflowPromptIds = new Set<string>();
const queue = [id];
workflowPromptIds.add(id);
while (queue.length > 0) {
const current = queue.shift()!;
const neighbors = adjacency.get(current);
if (neighbors) {
neighbors.forEach((neighborId) => {
if (!workflowPromptIds.has(neighborId)) {
workflowPromptIds.add(neighborId);
queue.push(neighborId);
}
});
}
}
// Remove current prompt from update set (already updated above)
workflowPromptIds.delete(id);
// Update all prompts in the workflow with the same workflow link
if (workflowPromptIds.size > 0) {
await db.prompt.updateMany({
where: {
id: { in: Array.from(workflowPromptIds) },
},
data: {
workflowLink: newWorkflowLink,
},
});
}
}
// Revalidate prompts cache
revalidateTag("prompts", "max");

View File

@@ -28,6 +28,7 @@ const promptSchema = z.object({
command: z.string(),
tools: z.array(z.string()).optional(),
})).optional(),
workflowLink: z.string().url().optional().or(z.literal("")),
});
// Create prompt
@@ -51,7 +52,7 @@ export async function POST(request: Request) {
);
}
const { title, description, content, type, structuredFormat, categoryId, tagIds, contributorIds, isPrivate, mediaUrl, requiresMediaUpload, requiredMediaType, requiredMediaCount, bestWithModels, bestWithMCP } = parsed.data;
const { title, description, content, type, structuredFormat, categoryId, tagIds, contributorIds, isPrivate, mediaUrl, requiresMediaUpload, requiredMediaType, requiredMediaCount, bestWithModels, bestWithMCP, workflowLink } = parsed.data;
// Check if user is flagged (for auto-delisting and daily limit)
const currentUser = await db.user.findUnique({
@@ -183,6 +184,7 @@ export async function POST(request: Request) {
requiredMediaCount: requiresMediaUpload ? requiredMediaCount : null,
bestWithModels: bestWithModels || [],
bestWithMCP: bestWithMCP || [],
workflowLink: workflowLink || null,
authorId: session.user.id,
categoryId: categoryId || null,
// Auto-delist prompts from flagged users

View File

@@ -98,6 +98,7 @@ export default async function EditPromptPage({ params }: EditPromptPageProps) {
requiredMediaCount: prompt.requiredMediaCount || 1,
bestWithModels: (prompt as unknown as { bestWithModels?: string[] }).bestWithModels || [],
bestWithMCP: (prompt as unknown as { bestWithMCP?: { command: string; tools?: string[] }[] }).bestWithMCP || [],
workflowLink: (prompt as unknown as { workflowLink?: string }).workflowLink || "",
};
// Check if AI generation is enabled

View File

@@ -207,6 +207,17 @@ export default async function PromptPage({ params }: PromptPageProps) {
.map((conn) => conn.target)
.filter((p) => !p.isPrivate && !p.isUnlisted && !p.deletedAt);
// Check if prompt has flow connections (previous/next, not "related")
const flowConnectionCount = await db.promptConnection.count({
where: {
OR: [
{ sourceId: id, label: { not: "related" } },
{ targetId: id, label: { not: "related" } },
],
},
});
const hasFlowConnections = flowConnectionCount > 0;
if (!prompt) {
notFound();
}
@@ -654,6 +665,8 @@ export default async function PromptPage({ params }: PromptPageProps) {
isLoggedIn={!!session?.user}
currentUserId={session?.user?.id}
isAdmin={isAdmin}
workflowLink={(prompt as unknown as { workflowLink?: string | null }).workflowLink}
hasFlowConnections={hasFlowConnections}
/>
)}

View File

@@ -5,7 +5,7 @@ import { useTranslations } from "next-intl";
import { toast } from "sonner";
import { useRouter } from "next/navigation";
import * as d3 from "d3";
import { Link2, ArrowUp, ArrowDown, ChevronRight, Trash2 } from "lucide-react";
import { Link2, ArrowUp, ArrowDown, ChevronRight, Trash2, FastForward, ExternalLink } from "lucide-react";
import { Button } from "@/components/ui/button";
import { AddConnectionDialog } from "./add-connection-dialog";
import { getPromptUrl } from "@/lib/urls";
@@ -44,6 +44,7 @@ interface PromptConnectionsProps {
sectionOnly?: boolean; // Only render the expanded section
expanded?: boolean; // Controlled expanded state
onExpandChange?: (expanded: boolean) => void; // Callback when expanded state changes
workflowLink?: string | null; // URL to test the workflow
}
interface GraphNode {
@@ -453,6 +454,7 @@ export function PromptConnections({
sectionOnly = false,
expanded: controlledExpanded,
onExpandChange,
workflowLink,
}: PromptConnectionsProps) {
const t = useTranslations("connectedPrompts");
const router = useRouter();
@@ -1066,43 +1068,58 @@ export function PromptConnections({
if (canEdit && !showExpanded) return null;
return (
<div className="w-full mt-4 space-y-4 rounded-lg border bg-card p-4">
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div>
<div className="w-full mt-4 space-y-3">
{/* Header row - above the container */}
<div className="flex flex-col gap-2">
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div className="flex items-center gap-2">
<Link2 className="h-5 w-5 text-muted-foreground" />
<h3 className="font-semibold">{t("title")}</h3>
</div>
<p className="text-xs text-muted-foreground mt-1">{t("description")}</p>
</div>
{canEdit && (
<div className="flex gap-2">
{canEdit && (
<>
<Button
variant="outline"
size="sm"
onClick={() => {
setConnectionType("previous");
setDialogOpen(true);
}}
>
<ArrowUp className="h-4 w-4 sm:mr-1" />
<span className="hidden sm:inline">{t("addPrevious")}</span>
</Button>
<Button
variant="outline"
size="sm"
onClick={() => {
setConnectionType("next");
setDialogOpen(true);
}}
>
<ArrowDown className="h-4 w-4 sm:mr-1" />
<span className="hidden sm:inline">{t("addNext")}</span>
</Button>
</>
)}
{hasConnections && workflowLink && (
<Button
variant="outline"
size="sm"
onClick={() => {
setConnectionType("previous");
setDialogOpen(true);
}}
className="bg-emerald-700/80 hover:bg-emerald-700 text-white gap-1.5"
onClick={() => window.open(workflowLink, '_blank', 'noopener,noreferrer')}
>
<ArrowUp className="h-4 w-4 sm:mr-1" />
<span className="hidden sm:inline">{t("addPrevious")}</span>
</Button>
<Button
variant="outline"
size="sm"
onClick={() => {
setConnectionType("next");
setDialogOpen(true);
}}
>
<ArrowDown className="h-4 w-4 sm:mr-1" />
<span className="hidden sm:inline">{t("addNext")}</span>
<FastForward className="h-3.5 w-3.5" />
<span className="hidden sm:inline">{t("testWorkflow")}</span>
<ExternalLink className="h-3 w-3" />
</Button>
)}
</div>
)}
</div>
</div>
{/* Content container */}
<div className="rounded-lg border bg-card p-4">
{!hasConnections ? (
<p className="text-sm text-muted-foreground">{t("noConnections")}</p>
) : hasFullFlow ? (
@@ -1133,6 +1150,7 @@ export function PromptConnections({
)}
</div>
)}
</div>
{canEdit && (
<AddConnectionDialog

View File

@@ -12,6 +12,8 @@ interface PromptFlowSectionProps {
isLoggedIn: boolean;
currentUserId?: string;
isAdmin?: boolean;
workflowLink?: string | null;
hasFlowConnections?: boolean;
}
export function PromptFlowSection({
@@ -22,6 +24,7 @@ export function PromptFlowSection({
isLoggedIn,
currentUserId,
isAdmin,
workflowLink,
}: PromptFlowSectionProps) {
const [expanded, setExpanded] = useState(false);
@@ -51,6 +54,7 @@ export function PromptFlowSection({
canEdit={canEdit}
currentUserId={currentUserId}
isAdmin={isAdmin}
workflowLink={workflowLink}
sectionOnly
expanded={expanded}
onExpandChange={setExpanded}

View File

@@ -365,6 +365,7 @@ const createPromptSchema = (t: (key: string) => string) => z.object({
command: z.string(),
tools: z.array(z.string()).optional(),
})).optional(),
workflowLink: z.string().url().optional().or(z.literal("")),
}).superRefine((data, ctx) => {
if (data.type === "SKILL") {
const frontmatterError = validateSkillFrontmatter(data.content);
@@ -459,6 +460,7 @@ export function PromptForm({ categories, tags, initialData, initialContributors
requiredMediaCount: initialData?.requiredMediaCount || 1,
bestWithModels: initialData?.bestWithModels || [],
bestWithMCP: initialData?.bestWithMCP || [],
workflowLink: initialData?.workflowLink || "",
},
});
@@ -1083,6 +1085,7 @@ export function PromptForm({ categories, tags, initialData, initialContributors
</Button>
</div>
</div>
</div>
)}
</div>
@@ -1433,6 +1436,34 @@ export function PromptForm({ categories, tags, initialData, initialContributors
</div>
)}
{/* ===== WORKFLOW LINK SECTION ===== */}
<div className="space-y-3 py-6 border-t">
<FormField
control={form.control}
name="workflowLink"
render={({ field }) => (
<FormItem>
<FormLabel>{t("workflowLink")}</FormLabel>
<FormDescription className="text-xs">
{mode === "create"
? t("workflowLinkCreateNote")
: t("workflowLinkDescription")
}
</FormDescription>
<FormControl>
<Input
placeholder={t("workflowLinkPlaceholder")}
{...field}
disabled={mode === "create"}
className={mode === "create" ? "opacity-50" : ""}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex justify-end gap-4 pt-2">
<Button type="button" variant="outline" onClick={() => router.back()}>
{tCommon("cancel")}