diff --git a/messages/ar.json b/messages/ar.json index 0da71a7d..d0df06bd 100644 --- a/messages/ar.json +++ b/messages/ar.json @@ -39,6 +39,7 @@ "promptmasters": "Promptmasters", "prompts": "الأوامر", "skills": "المهارات", + "workflows": "سير العمل", "categories": "الفئات", "tags": "الوسوم", "settings": "الإعدادات", @@ -50,7 +51,8 @@ "ide": "Typed-Prompts IDE", "developers": "المطورون", "book": "كتاب", - "forKids": "للأطفال" + "forKids": "للأطفال", + "more": "المزيد" }, "brand": { "copyLogoSvg": "نسخ شعار SVG", @@ -848,6 +850,13 @@ "subscribeToCategories": "اشترك في التصنيفات لرؤية الأوامر هنا", "viewAllCategories": "عرض جميع التصنيفات" }, + "workflows": { + "title": "سير العمل", + "description": "أوامر ذات تدفقات واتصالات متسلسلة", + "noWorkflows": "لا توجد سير عمل بعد", + "noWorkflowsDescription": "سير العمل هي أوامر تتصل بأوامر أخرى بالتسلسل. أنشئ أمرًا وأضف اتصالات لبناء سير عمل.", + "browsePrompts": "تصفح الأوامر" + }, "collection": { "title": "مجموعتي", "description": "الأوامر التي حفظتها لوقت لاحق", @@ -1198,6 +1207,7 @@ "noConnections": "لا توجد أوامر متصلة بعد. أضف اتصالات لإنشاء سلسلة أوامر.", "previousSteps": "الخطوات السابقة", "nextSteps": "الخطوات التالية", + "fullFlow": "سير العمل الكامل", "searchPrompt": "بحث عن أمر", "searchPlaceholder": "البحث بالعنوان...", "selectedPrompt": "الأمر المحدد", diff --git a/messages/az.json b/messages/az.json index bbec5172..3e27871b 100644 --- a/messages/az.json +++ b/messages/az.json @@ -39,6 +39,7 @@ "promptmasters": "Promptmasterlər", "prompts": "Promptlar", "skills": "Bacarıqlar", + "workflows": "İş axınları", "categories": "Kateqoriyalar", "tags": "Etiketlər", "settings": "Parametrlər", @@ -50,7 +51,8 @@ "ide": "Typed-Prompts IDE", "developers": "Tərtibatçılar", "book": "Kitab", - "forKids": "Uşaqlar Üçün" + "forKids": "Uşaqlar Üçün", + "more": "Daha çox" }, "brand": { "copyLogoSvg": "Logo SVG-ni Kopyala", @@ -848,6 +850,13 @@ "subscribeToCategories": "Burada promptları görmək üçün kateqoriyalara abunə olun", "viewAllCategories": "Bütün Kateqoriyalara Bax" }, + "workflows": { + "title": "İş axınları", + "description": "Ardıcıl axınları və əlaqələri olan promptlar", + "noWorkflows": "Hələ iş axını yoxdur", + "noWorkflowsDescription": "İş axınları digər promptlara ardıcıl olaraq bağlanan promptlardır. İş axını yaratmaq üçün prompt yaradın və əlaqələr əlavə edin.", + "browsePrompts": "Promptlara Bax" + }, "collection": { "title": "Kolleksiyam", "description": "Sonra üçün saxladığınız promptlar", @@ -1197,7 +1206,8 @@ "addNextDescription": "İş axınında bundan sonra gələn promptu seçin.", "noConnections": "Hələ bağlı prompt yoxdur. Prompt zənciri yaratmaq üçün əlaqələr əlavə edin.", "previousSteps": "Əvvəlki addımlar", - "nextSteps": "Növbəti addımlar", + "nextSteps": "Sonrakı addımlar", + "fullFlow": "Tam Axın", "searchPrompt": "Prompt Axtar", "searchPlaceholder": "Başlığa görə axtar...", "selectedPrompt": "Seçilmiş Prompt", diff --git a/messages/de.json b/messages/de.json index bdf8eaa3..2f275fb5 100644 --- a/messages/de.json +++ b/messages/de.json @@ -38,7 +38,8 @@ "feed": "Feed", "promptmasters": "Promptmasters", "prompts": "Prompts", - "skills": "Skills", + "skills": "Fähigkeiten", + "workflows": "Workflows", "categories": "Kategorien", "tags": "Tags", "settings": "Einstellungen", @@ -50,7 +51,8 @@ "ide": "Typed-Prompts IDE", "developers": "Entwickler", "book": "Buch", - "forKids": "Für Kinder" + "forKids": "Für Kinder", + "more": "Mehr" }, "brand": { "copyLogoSvg": "Logo SVG kopieren", @@ -848,6 +850,13 @@ "subscribeToCategories": "Abonnieren Sie Kategorien, um hier Prompts zu sehen", "viewAllCategories": "Alle Kategorien anzeigen" }, + "workflows": { + "title": "Workflows", + "description": "Prompts mit sequentiellen Abläufen und Verbindungen", + "noWorkflows": "Noch keine Workflows", + "noWorkflowsDescription": "Workflows sind Prompts, die in einer Sequenz mit anderen Prompts verbunden sind. Erstellen Sie einen Prompt und fügen Sie Verbindungen hinzu, um einen Workflow zu erstellen.", + "browsePrompts": "Prompts durchsuchen" + }, "collection": { "title": "Meine Sammlung", "description": "Prompts, die Sie für später gespeichert haben", @@ -1198,6 +1207,7 @@ "noConnections": "Noch keine verbundenen Prompts. Fügen Sie Verbindungen hinzu, um eine Prompt-Kette zu erstellen.", "previousSteps": "Vorherige Schritte", "nextSteps": "Nächste Schritte", + "fullFlow": "Vollständiger Workflow", "searchPrompt": "Prompt suchen", "searchPlaceholder": "Nach Titel suchen...", "selectedPrompt": "Ausgewählter Prompt", diff --git a/messages/el.json b/messages/el.json index eaf10236..cca552e5 100644 --- a/messages/el.json +++ b/messages/el.json @@ -39,6 +39,7 @@ "promptmasters": "Promptmasters", "prompts": "Prompts", "skills": "Δεξιότητες", + "workflows": "Ροές εργασίας", "categories": "Κατηγορίες", "tags": "Ετικέτες", "settings": "Ρυθμίσεις", @@ -50,7 +51,8 @@ "ide": "Typed-Prompts IDE", "developers": "Προγραμματιστές", "book": "Βιβλίο", - "forKids": "Για Παιδιά" + "forKids": "Για Παιδιά", + "more": "Περισσότερα" }, "brand": { "copyLogoSvg": "Αντιγραφή Logo SVG", @@ -848,6 +850,13 @@ "subscribeToCategories": "Εγγραφείτε σε κατηγορίες για να δείτε prompts εδώ", "viewAllCategories": "Προβολή όλων των κατηγοριών" }, + "workflows": { + "title": "Ροές εργασίας", + "description": "Prompts με διαδοχικές ροές και συνδέσεις", + "noWorkflows": "Δεν υπάρχουν ακόμα ροές εργασίας", + "noWorkflowsDescription": "Οι ροές εργασίας είναι prompts που συνδέονται με άλλα prompts σε ακολουθία. Δημιουργήστε ένα prompt και προσθέστε συνδέσεις για να δημιουργήσετε μια ροή εργασίας.", + "browsePrompts": "Περιήγηση Prompts" + }, "collection": { "title": "Η Συλλογή μου", "description": "Prompts που αποθηκεύσατε για αργότερα", @@ -1198,6 +1207,7 @@ "noConnections": "Δεν υπάρχουν συνδεδεμένα prompts ακόμα. Προσθέστε συνδέσεις για να δημιουργήσετε μια αλυσίδα prompts.", "previousSteps": "Προηγούμενα βήματα", "nextSteps": "Επόμενα βήματα", + "fullFlow": "Πλήρης ροή εργασίας", "searchPrompt": "Αναζήτηση Prompt", "searchPlaceholder": "Αναζήτηση με τίτλο...", "selectedPrompt": "Επιλεγμένο Prompt", diff --git a/messages/en.json b/messages/en.json index a8db13f6..285c625f 100644 --- a/messages/en.json +++ b/messages/en.json @@ -39,6 +39,7 @@ "promptmasters": "Promptmasters", "prompts": "Prompts", "skills": "Skills", + "workflows": "Workflows", "categories": "Categories", "tags": "Tags", "settings": "Settings", @@ -50,7 +51,8 @@ "ide": "Typed-Prompts IDE", "developers": "Developers", "book": "Book", - "forKids": "For Kids" + "forKids": "For Kids", + "more": "More" }, "brand": { "copyLogoSvg": "Copy Logo SVG", @@ -848,6 +850,13 @@ "subscribeToCategories": "Subscribe to categories to see prompts here", "viewAllCategories": "View All Categories" }, + "workflows": { + "title": "Workflows", + "description": "Prompts with sequential flows and connections", + "noWorkflows": "No workflows yet", + "noWorkflowsDescription": "Workflows are prompts that connect to other prompts in a sequence. Create a prompt and add connections to build a workflow.", + "browsePrompts": "Browse Prompts" + }, "collection": { "title": "My Collection", "description": "Prompts you've saved for later", @@ -1198,6 +1207,7 @@ "noConnections": "No connected prompts yet. Add connections to create a prompt chain.", "previousSteps": "Previous steps", "nextSteps": "Next steps", + "fullFlow": "Complete Workflow", "searchPrompt": "Search Prompt", "searchPlaceholder": "Search by title...", "selectedPrompt": "Selected Prompt", diff --git a/messages/es.json b/messages/es.json index 08506f38..bca6a0b1 100644 --- a/messages/es.json +++ b/messages/es.json @@ -39,6 +39,7 @@ "promptmasters": "Promptmasters", "prompts": "Prompts", "skills": "Habilidades", + "workflows": "Flujos de trabajo", "categories": "Categorías", "tags": "Etiquetas", "settings": "Configuración", @@ -50,7 +51,8 @@ "ide": "Typed-Prompts IDE", "developers": "Desarrolladores", "book": "Libro", - "forKids": "Para Niños" + "forKids": "Para Niños", + "more": "Más" }, "brand": { "copyLogoSvg": "Copiar Logo SVG", @@ -848,6 +850,13 @@ "subscribeToCategories": "Suscríbete a categorías para ver prompts aquí", "viewAllCategories": "Ver Todas las Categorías" }, + "workflows": { + "title": "Flujos de trabajo", + "description": "Prompts con flujos secuenciales y conexiones", + "noWorkflows": "Aún no hay flujos de trabajo", + "noWorkflowsDescription": "Los flujos de trabajo son prompts que se conectan a otros prompts en secuencia. Crea un prompt y agrega conexiones para construir un flujo de trabajo.", + "browsePrompts": "Explorar Prompts" + }, "collection": { "title": "Mi Colección", "description": "Prompts que has guardado para después", @@ -1198,6 +1207,7 @@ "noConnections": "Aún no hay prompts conectados. Añade conexiones para crear una cadena de prompts.", "previousSteps": "Pasos anteriores", "nextSteps": "Siguientes pasos", + "fullFlow": "Flujo completo", "searchPrompt": "Buscar Prompt", "searchPlaceholder": "Buscar por título...", "selectedPrompt": "Prompt Seleccionado", diff --git a/messages/fa.json b/messages/fa.json index 698dda2f..be759562 100644 --- a/messages/fa.json +++ b/messages/fa.json @@ -39,6 +39,7 @@ "promptmasters": "پرامپت‌مسترها", "prompts": "پرامپت‌ها", "skills": "مهارت‌ها", + "workflows": "گردش کار", "categories": "دسته‌بندی‌ها", "tags": "برچسب‌ها", "settings": "تنظیمات", @@ -50,7 +51,8 @@ "ide": "Typed-Prompts IDE", "developers": "توسعه‌دهندگان", "book": "کتاب", - "forKids": "برای کودکان" + "forKids": "برای کودکان", + "more": "بیشتر" }, "brand": { "copyLogoSvg": "کپی SVG لوگو", @@ -848,6 +850,13 @@ "subscribeToCategories": "برای دیدن پرامپت‌ها، دسته‌بندی‌ها را دنبال کنید", "viewAllCategories": "مشاهده همه دسته‌بندی‌ها" }, + "workflows": { + "title": "گردش کار", + "description": "پرامپت‌ها با جریان‌ها و اتصالات ترتیبی", + "noWorkflows": "هنوز گردش کاری وجود ندارد", + "noWorkflowsDescription": "گردش کار پرامپت‌هایی هستند که به ترتیب به پرامپت‌های دیگر متصل می‌شوند. یک پرامپت ایجاد کنید و اتصالات اضافه کنید تا گردش کار بسازید.", + "browsePrompts": "مرور پرامپت‌ها" + }, "collection": { "title": "مجموعه من", "description": "پرامپت‌هایی که برای بعد ذخیره کردید", @@ -1198,6 +1207,7 @@ "noConnections": "هنوز پرامپت متصلی نیست. اتصالاتی اضافه کنید تا یک زنجیره پرامپت بسازید.", "previousSteps": "مراحل قبلی", "nextSteps": "مراحل بعدی", + "fullFlow": "گردش کار کامل", "searchPrompt": "جستجوی پرامپت", "searchPlaceholder": "جستجو بر اساس عنوان...", "selectedPrompt": "پرامپت انتخاب شده", diff --git a/messages/fr.json b/messages/fr.json index 1ff1a5c7..bc3abdec 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -39,6 +39,7 @@ "promptmasters": "Promptmasters", "prompts": "Prompts", "skills": "Compétences", + "workflows": "Flux de travail", "categories": "Catégories", "tags": "Tags", "settings": "Paramètres", @@ -50,7 +51,8 @@ "ide": "Typed-Prompts IDE", "developers": "Développeurs", "book": "Livre", - "forKids": "Pour Enfants" + "forKids": "Pour Enfants", + "more": "Plus" }, "brand": { "copyLogoSvg": "Copier le logo SVG", @@ -848,6 +850,13 @@ "subscribeToCategories": "Abonnez-vous à des catégories pour voir des prompts ici", "viewAllCategories": "Voir Toutes les Catégories" }, + "workflows": { + "title": "Flux de travail", + "description": "Prompts avec des flux et connexions séquentiels", + "noWorkflows": "Pas encore de flux de travail", + "noWorkflowsDescription": "Les flux de travail sont des prompts qui se connectent à d'autres prompts en séquence. Créez un prompt et ajoutez des connexions pour construire un flux de travail.", + "browsePrompts": "Parcourir les Prompts" + }, "collection": { "title": "Ma Collection", "description": "Prompts que vous avez sauvegardés pour plus tard", @@ -1198,6 +1207,7 @@ "noConnections": "Pas encore de prompts connectés. Ajoutez des connexions pour créer une chaîne de prompts.", "previousSteps": "Étapes précédentes", "nextSteps": "Étapes suivantes", + "fullFlow": "Flux complet", "searchPrompt": "Rechercher un Prompt", "searchPlaceholder": "Rechercher par titre...", "selectedPrompt": "Prompt Sélectionné", diff --git a/messages/he.json b/messages/he.json index a1a9b376..0a8d9d68 100644 --- a/messages/he.json +++ b/messages/he.json @@ -38,7 +38,8 @@ "feed": "פיד", "promptmasters": "מאסטרים", "prompts": "פרומפטים", - "skills": "מיומנויות", + "skills": "כישורים", + "workflows": "תהליכי עבודה", "categories": "קטגוריות", "tags": "תגיות", "settings": "הגדרות", @@ -50,7 +51,8 @@ "ide": "Typed-Prompts IDE", "developers": "מפתחים", "book": "ספר", - "forKids": "לילדים" + "forKids": "לילדים", + "more": "עוד" }, "brand": { "copyLogoSvg": "העתק לוגו SVG", @@ -848,6 +850,13 @@ "subscribeToCategories": "הירשם לקטגוריות כדי לראות פרומפטים כאן", "viewAllCategories": "צפה בכל הקטגוריות" }, + "workflows": { + "title": "תהליכי עבודה", + "description": "פרומפטים עם זרימות וחיבורים רציפים", + "noWorkflows": "עדיין אין תהליכי עבודה", + "noWorkflowsDescription": "תהליכי עבודה הם פרומפטים שמתחברים לפרומפטים אחרים ברצף. צור פרומפט והוסף חיבורים כדי לבנות תהליך עבודה.", + "browsePrompts": "עיין בפרומפטים" + }, "collection": { "title": "האוסף שלי", "description": "פרומפטים ששמרת למועד מאוחר יותר", @@ -1197,7 +1206,8 @@ "addNextDescription": "בחר פרומפט שבא אחרי זה בתהליך העבודה.", "noConnections": "אין עדיין פרומפטים מקושרים. הוסף קישורים ליצירת שרשרת פרומפטים.", "previousSteps": "שלבים קודמים", - "nextSteps": "שלבים הבאים", + "nextSteps": "השלבים הבאים", + "fullFlow": "תהליך עבודה מלא", "searchPrompt": "חפש פרומפט", "searchPlaceholder": "חפש לפי כותרת...", "selectedPrompt": "פרומפט נבחר", diff --git a/messages/it.json b/messages/it.json index 1f002afe..14d84e97 100644 --- a/messages/it.json +++ b/messages/it.json @@ -39,6 +39,7 @@ "promptmasters": "Promptmaster", "prompts": "Prompt", "skills": "Competenze", + "workflows": "Flussi di lavoro", "categories": "Categorie", "tags": "Tag", "settings": "Impostazioni", @@ -50,7 +51,8 @@ "ide": "Typed-Prompts IDE", "developers": "Sviluppatori", "book": "Libro", - "forKids": "Per Bambini" + "forKids": "Per Bambini", + "more": "Altro" }, "brand": { "copyLogoSvg": "Copia Logo SVG", @@ -848,6 +850,13 @@ "subscribeToCategories": "Iscriviti alle categorie per vedere i prompt qui", "viewAllCategories": "Vedi Tutte le Categorie" }, + "workflows": { + "title": "Flussi di lavoro", + "description": "Prompt con flussi e connessioni sequenziali", + "noWorkflows": "Ancora nessun flusso di lavoro", + "noWorkflowsDescription": "I flussi di lavoro sono prompt che si connettono ad altri prompt in sequenza. Crea un prompt e aggiungi connessioni per costruire un flusso di lavoro.", + "browsePrompts": "Sfoglia Prompt" + }, "collection": { "title": "La Mia Collezione", "description": "Prompt che hai salvato per dopo", @@ -1197,7 +1206,8 @@ "addNextDescription": "Seleziona un prompt che viene dopo questo nel workflow.", "noConnections": "Nessun prompt collegato ancora. Aggiungi connessioni per creare una catena di prompt.", "previousSteps": "Passaggi precedenti", - "nextSteps": "Passaggi successivi", + "nextSteps": "Passi successivi", + "fullFlow": "Flusso completo", "searchPrompt": "Cerca Prompt", "searchPlaceholder": "Cerca per titolo...", "selectedPrompt": "Prompt Selezionato", diff --git a/messages/ja.json b/messages/ja.json index 243d40f4..9d51b7f9 100644 --- a/messages/ja.json +++ b/messages/ja.json @@ -39,6 +39,7 @@ "promptmasters": "Promptmasters", "prompts": "プロンプト", "skills": "スキル", + "workflows": "ワークフロー", "categories": "カテゴリー", "tags": "タグ", "settings": "設定", @@ -50,7 +51,8 @@ "ide": "Typed-Prompts IDE", "developers": "開発者", "book": "本", - "forKids": "子ども向け" + "forKids": "子ども向け", + "more": "もっと見る" }, "brand": { "copyLogoSvg": "ロゴSVGをコピー", @@ -848,6 +850,13 @@ "subscribeToCategories": "カテゴリーを購読してプロンプトを表示", "viewAllCategories": "すべてのカテゴリーを見る" }, + "workflows": { + "title": "ワークフロー", + "description": "シーケンシャルなフローと接続を持つプロンプト", + "noWorkflows": "まだワークフローがありません", + "noWorkflowsDescription": "ワークフローは、他のプロンプトに順番に接続するプロンプトです。プロンプトを作成し、接続を追加してワークフローを構築してください。", + "browsePrompts": "プロンプトを見る" + }, "collection": { "title": "マイコレクション", "description": "保存したプロンプト", @@ -1198,6 +1207,7 @@ "noConnections": "接続されたプロンプトはまだありません。プロンプトチェーンを作成するには接続を追加してください。", "previousSteps": "前のステップ", "nextSteps": "次のステップ", + "fullFlow": "完全なフロー", "searchPrompt": "プロンプトを検索", "searchPlaceholder": "タイトルで検索...", "selectedPrompt": "選択したプロンプト", diff --git a/messages/ko.json b/messages/ko.json index 6c8faeb1..3ca8b6ac 100644 --- a/messages/ko.json +++ b/messages/ko.json @@ -39,6 +39,7 @@ "promptmasters": "프롬프트마스터", "prompts": "프롬프트", "skills": "스킬", + "workflows": "워크플로우", "categories": "카테고리", "tags": "태그", "settings": "설정", @@ -50,7 +51,8 @@ "ide": "Typed-Prompts IDE", "developers": "개발자", "book": "책", - "forKids": "어린이용" + "forKids": "어린이용", + "more": "더 보기" }, "brand": { "copyLogoSvg": "로고 SVG 복사", @@ -848,6 +850,13 @@ "subscribeToCategories": "카테고리를 구독하여 여기서 프롬프트를 확인하세요", "viewAllCategories": "모든 카테고리 보기" }, + "workflows": { + "title": "워크플로우", + "description": "순차적 흐름과 연결이 있는 프롬프트", + "noWorkflows": "아직 워크플로우가 없습니다", + "noWorkflowsDescription": "워크플로우는 다른 프롬프트에 순차적으로 연결되는 프롬프트입니다. 프롬프트를 만들고 연결을 추가하여 워크플로우를 구축하세요.", + "browsePrompts": "프롬프트 둘러보기" + }, "collection": { "title": "내 컬렉션", "description": "나중을 위해 저장한 프롬프트", @@ -1198,6 +1207,7 @@ "noConnections": "아직 연결된 프롬프트가 없습니다. 프롬프트 체인을 만들려면 연결을 추가하세요.", "previousSteps": "이전 단계", "nextSteps": "다음 단계", + "fullFlow": "전체 흐름", "searchPrompt": "프롬프트 검색", "searchPlaceholder": "제목으로 검색...", "selectedPrompt": "선택된 프롬프트", diff --git a/messages/nl.json b/messages/nl.json index af26c407..b4c29d4f 100644 --- a/messages/nl.json +++ b/messages/nl.json @@ -39,6 +39,7 @@ "promptmasters": "Promptmasters", "prompts": "Prompts", "skills": "Vaardigheden", + "workflows": "Workflows", "categories": "Categorieën", "tags": "Tags", "settings": "Instellingen", @@ -50,7 +51,8 @@ "ide": "Typed-Prompts IDE", "developers": "Ontwikkelaars", "book": "Boek", - "forKids": "Voor kinderen" + "forKids": "Voor kinderen", + "more": "Meer" }, "brand": { "copyLogoSvg": "Logo SVG kopiëren", @@ -848,6 +850,13 @@ "subscribeToCategories": "Abonneer op categorieën om hier prompts te zien", "viewAllCategories": "Alle categorieën bekijken" }, + "workflows": { + "title": "Workflows", + "description": "Prompts met sequentiële stromen en verbindingen", + "noWorkflows": "Nog geen workflows", + "noWorkflowsDescription": "Workflows zijn prompts die in volgorde verbonden zijn met andere prompts. Maak een prompt en voeg verbindingen toe om een workflow te bouwen.", + "browsePrompts": "Prompts bekijken" + }, "collection": { "title": "Mijn collectie", "description": "Prompts die je hebt opgeslagen voor later", @@ -1198,6 +1207,7 @@ "noConnections": "Nog geen verbonden prompts. Voeg verbindingen toe om een promptketen te maken.", "previousSteps": "Vorige stappen", "nextSteps": "Volgende stappen", + "fullFlow": "Volledige workflow", "searchPrompt": "Prompt zoeken", "searchPlaceholder": "Zoeken op titel...", "selectedPrompt": "Geselecteerde prompt", diff --git a/messages/pt.json b/messages/pt.json index da0516ef..467951f3 100644 --- a/messages/pt.json +++ b/messages/pt.json @@ -39,6 +39,7 @@ "promptmasters": "Promptmasters", "prompts": "Prompts", "skills": "Habilidades", + "workflows": "Fluxos de trabalho", "categories": "Categorias", "tags": "Tags", "settings": "Configurações", @@ -50,7 +51,8 @@ "ide": "Typed-Prompts IDE", "developers": "Desenvolvedores", "book": "Livro", - "forKids": "Para Crianças" + "forKids": "Para Crianças", + "more": "Mais" }, "brand": { "copyLogoSvg": "Copiar Logo SVG", @@ -848,6 +850,13 @@ "subscribeToCategories": "Inscreva-se em categorias para ver prompts aqui", "viewAllCategories": "Ver Todas as Categorias" }, + "workflows": { + "title": "Fluxos de trabalho", + "description": "Prompts com fluxos e conexões sequenciais", + "noWorkflows": "Ainda não há fluxos de trabalho", + "noWorkflowsDescription": "Fluxos de trabalho são prompts que se conectam a outros prompts em sequência. Crie um prompt e adicione conexões para construir um fluxo de trabalho.", + "browsePrompts": "Explorar Prompts" + }, "collection": { "title": "Minha Coleção", "description": "Prompts que você salvou para depois", @@ -1197,7 +1206,8 @@ "addNextDescription": "Selecione um prompt que vem depois deste no fluxo de trabalho.", "noConnections": "Ainda não há prompts conectados. Adicione conexões para criar uma cadeia de prompts.", "previousSteps": "Etapas anteriores", - "nextSteps": "Próximas etapas", + "nextSteps": "Próximos passos", + "fullFlow": "Fluxo completo", "searchPrompt": "Pesquisar Prompt", "searchPlaceholder": "Pesquisar por título...", "selectedPrompt": "Prompt Selecionado", diff --git a/messages/ru.json b/messages/ru.json index 989759c4..6ab95466 100644 --- a/messages/ru.json +++ b/messages/ru.json @@ -39,6 +39,7 @@ "promptmasters": "Мастера промптов", "prompts": "Промпты", "skills": "Навыки", + "workflows": "Рабочие процессы", "categories": "Категории", "tags": "Теги", "settings": "Настройки", @@ -50,7 +51,8 @@ "ide": "Typed-Prompts IDE", "developers": "Разработчикам", "book": "Книга", - "forKids": "Для детей" + "forKids": "Для детей", + "more": "Ещё" }, "brand": { "copyLogoSvg": "Скопировать логотип SVG", @@ -848,6 +850,13 @@ "subscribeToCategories": "Подпишитесь на категории, чтобы видеть промпты здесь", "viewAllCategories": "Все категории" }, + "workflows": { + "title": "Рабочие процессы", + "description": "Промпты с последовательными потоками и связями", + "noWorkflows": "Пока нет рабочих процессов", + "noWorkflowsDescription": "Рабочие процессы — это промпты, которые последовательно связаны с другими промптами. Создайте промпт и добавьте связи для построения рабочего процесса.", + "browsePrompts": "Обзор промптов" + }, "collection": { "title": "Моя Коллекция", "description": "Промпты, которые вы сохранили на потом", @@ -1198,6 +1207,7 @@ "noConnections": "Связанных промптов пока нет. Добавьте связи для создания цепочки промптов.", "previousSteps": "Предыдущие шаги", "nextSteps": "Следующие шаги", + "fullFlow": "Полный рабочий процесс", "searchPrompt": "Поиск промпта", "searchPlaceholder": "Поиск по названию...", "selectedPrompt": "Выбранный промпт", diff --git a/messages/tr.json b/messages/tr.json index e31768fa..57743188 100644 --- a/messages/tr.json +++ b/messages/tr.json @@ -39,6 +39,7 @@ "promptmasters": "Promptmasters", "prompts": "Promptlar", "skills": "Yetenekler", + "workflows": "Akışlar", "categories": "Kategoriler", "tags": "Etiketler", "settings": "Ayarlar", @@ -50,7 +51,8 @@ "ide": "Typed-Prompts IDE", "developers": "Geliştiriciler", "book": "Kitap", - "forKids": "Çocuklar İçin" + "forKids": "Çocuklar İçin", + "more": "Daha Fazla" }, "brand": { "copyLogoSvg": "Logo SVG'sini Kopyala", @@ -848,6 +850,13 @@ "subscribeToCategories": "Burada promptları görmek için kategorilere abone olun", "viewAllCategories": "Tüm Kategorileri Gör" }, + "workflows": { + "title": "Akışlar", + "description": "Sıralı akışlara ve bağlantılara sahip promptlar", + "noWorkflows": "Henüz akış yok", + "noWorkflowsDescription": "Akışlar, diğer promptlara sıralı olarak bağlanan promptlardır. Bir akış oluşturmak için bir prompt oluşturun ve bağlantılar ekleyin.", + "browsePrompts": "Promptlara Göz At" + }, "collection": { "title": "Koleksiyonum", "description": "Sonra için kaydettiğiniz promptlar", @@ -1198,6 +1207,7 @@ "noConnections": "Henüz bağlı prompt yok. Prompt zinciri oluşturmak için bağlantılar ekleyin.", "previousSteps": "Önceki adımlar", "nextSteps": "Sonraki adımlar", + "fullFlow": "Tam Akış", "searchPrompt": "Prompt Ara", "searchPlaceholder": "Başlığa göre ara...", "selectedPrompt": "Seçili Prompt", diff --git a/messages/zh.json b/messages/zh.json index db468c93..93ca58bd 100644 --- a/messages/zh.json +++ b/messages/zh.json @@ -39,6 +39,7 @@ "promptmasters": "Promptmasters", "prompts": "提示词", "skills": "技能", + "workflows": "工作流程", "categories": "分类", "tags": "标签", "settings": "设置", @@ -50,7 +51,8 @@ "ide": "Typed-Prompts IDE", "developers": "开发者", "book": "书籍", - "forKids": "儿童版" + "forKids": "儿童版", + "more": "更多" }, "brand": { "copyLogoSvg": "复制Logo SVG", @@ -848,6 +850,13 @@ "subscribeToCategories": "订阅分类以在此查看提示词", "viewAllCategories": "查看所有分类" }, + "workflows": { + "title": "工作流程", + "description": "具有顺序流程和连接的提示词", + "noWorkflows": "暂无工作流程", + "noWorkflowsDescription": "工作流程是按顺序连接到其他提示词的提示词。创建一个提示词并添加连接以构建工作流程。", + "browsePrompts": "浏览提示词" + }, "collection": { "title": "我的收藏", "description": "您保存的提示词", @@ -1198,6 +1207,7 @@ "noConnections": "尚无关联提示词。添加关联以创建提示词链。", "previousSteps": "上一步", "nextSteps": "下一步", + "fullFlow": "完整流程", "searchPrompt": "搜索提示词", "searchPlaceholder": "按标题搜索...", "selectedPrompt": "已选提示词", diff --git a/prisma/seed-flows.ts b/prisma/seed-flows.ts new file mode 100644 index 00000000..18959f81 --- /dev/null +++ b/prisma/seed-flows.ts @@ -0,0 +1,327 @@ +import { PrismaClient } from "@prisma/client"; +import bcrypt from "bcryptjs"; + +const prisma = new PrismaClient(); + +/** + * Generate a cuid-like ID + */ +function generateId(): string { + const timestamp = Date.now().toString(36); + const randomPart = Math.random().toString(36).substring(2, 15); + return `cm${timestamp}${randomPart}`.substring(0, 25); +} + +/** + * Seed script for testing prompt flows with branchy structure + * Run with: npx ts-node prisma/seed-flows.ts + */ +async function main() { + console.log("🌱 Seeding flow test data for user f1..."); + + const password = await bcrypt.hash("password123", 12); + + // Create or get user f1 + const user = await prisma.user.upsert({ + where: { username: "f" }, + update: {}, + create: { + email: "f@prompts.chat", + username: "f", + name: "Flow Tester", + password: password, + role: "USER", + locale: "en", + }, + }); + + console.log("✅ User f1 ready"); + + // Create a category for flow prompts + const category = await prisma.category.upsert({ + where: { slug: "workflows" }, + update: {}, + create: { + name: "Workflows", + slug: "workflows", + icon: "🔗", + order: 100, + }, + }); + + // Create prompts for a branchy flow structure + // + // Flow structure: + // + // [1. Start: Idea Generation] + // | + // (brainstorm complete) + // | + // [2. Outline Creation] + // | + // +---------------+---------------+ + // | | + // (technical path) (creative path) + // | | + // [3a. Technical Deep Dive] [3b. Creative Exploration] + // | | + // (analysis done) (ideas ready) + // | | + // [4a. Implementation Plan] [4b. Story Development] + // | | + // +---------------+---------------+ + // | + // (merge results) + // | + // [5. Final Review] + // | + // (review complete) + // | + // [6. Publication] + // + + // Pre-generate stable IDs for the flow prompts + const flowIds = { + "flow-1-start": generateId(), + "flow-2-outline": generateId(), + "flow-3a-technical": generateId(), + "flow-3b-creative": generateId(), + "flow-4a-implementation": generateId(), + "flow-4b-story": generateId(), + "flow-5-review": generateId(), + "flow-6-publish": generateId(), + }; + + const flowPrompts = [ + { + id: "flow-1-start", + title: "Idea Generation", + slug: "idea-generation", + description: "Start your workflow by brainstorming and generating initial ideas", + content: `You are a creative brainstorming assistant. Help the user generate innovative ideas for their project. + +1. Ask clarifying questions about the topic +2. Generate 5-10 diverse ideas +3. Rate each idea on feasibility and impact +4. Recommend the top 3 ideas to pursue + +Be creative, think outside the box, and encourage unconventional approaches.`, + }, + { + id: "flow-2-outline", + title: "Step 2: Outline Creation", + slug: "idea-generation-step-2-outline-creation", + description: "Create a structured outline from the generated ideas", + content: `Based on the ideas generated in the previous step, create a detailed outline. + +Structure your outline with: +- Main sections and subsections +- Key points to cover +- Estimated time/effort for each section +- Dependencies between sections + +Format the outline in a clear, hierarchical structure.`, + }, + { + id: "flow-3a-technical", + title: "Step 3a: Technical Deep Dive", + slug: "idea-generation-step-3a-technical-deep-dive", + description: "Technical path: Analyze technical requirements and feasibility", + content: `Perform a technical analysis of the outlined project. + +Analyze: +- Technical requirements and dependencies +- Architecture considerations +- Potential technical challenges +- Required tools and technologies +- Performance implications + +Provide a detailed technical assessment with recommendations.`, + }, + { + id: "flow-3b-creative", + title: "Step 3b: Creative Exploration", + slug: "idea-generation-step-3b-creative-exploration", + description: "Creative path: Explore creative directions and storytelling", + content: `Explore the creative dimensions of the outlined project. + +Focus on: +- Narrative and storytelling elements +- Visual and aesthetic considerations +- Emotional impact and user engagement +- Unique creative angles +- Inspiration from other works + +Generate creative concepts that bring the project to life.`, + }, + { + id: "flow-4a-implementation", + title: "Step 4a: Implementation Plan", + slug: "idea-generation-step-4a-implementation-plan", + description: "Create a detailed implementation plan based on technical analysis", + content: `Create a comprehensive implementation plan. + +Include: +- Phase breakdown with milestones +- Task list with priorities +- Resource allocation +- Risk mitigation strategies +- Timeline estimates +- Success metrics + +Format as an actionable project plan.`, + }, + { + id: "flow-4b-story", + title: "Step 4b: Story Development", + slug: "idea-generation-step-4b-story-development", + description: "Develop the narrative and content based on creative exploration", + content: `Develop the full story and content based on the creative exploration. + +Develop: +- Complete narrative arc +- Character or element descriptions +- Key scenes or moments +- Dialogue or copy +- Visual descriptions +- Emotional beats + +Create compelling, engaging content.`, + }, + { + id: "flow-5-review", + title: "Step 5: Final Review", + slug: "idea-generation-step-5-final-review", + description: "Review and merge all work streams for final quality check", + content: `Perform a comprehensive final review merging all work streams. + +Review checklist: +- Technical feasibility confirmed +- Creative vision aligned +- All requirements met +- Quality standards achieved +- Consistency across all elements +- Ready for publication + +Provide a final assessment with any last recommendations.`, + }, + { + id: "flow-6-publish", + title: "Step 6: Publication", + slug: "idea-generation-step-6-publication", + description: "Final step: Prepare and execute publication", + content: `Prepare the final deliverable for publication. + +Final steps: +- Format for target platform +- Create accompanying materials +- Set up distribution +- Prepare announcement +- Schedule publication +- Monitor initial reception + +Congratulations on completing the workflow!`, + }, + ]; + + // Create prompts + console.log("📝 Creating flow prompts..."); + const createdPrompts: Record = {}; + + for (const p of flowPrompts) { + const existing = await prisma.prompt.findFirst({ where: { slug: p.slug } }); + + if (existing) { + createdPrompts[p.id] = existing.id; + console.log(` ⏭️ Prompt "${p.title}" already exists (id: ${existing.id})`); + } else { + const promptId = flowIds[p.id as keyof typeof flowIds]; + const prompt = await prisma.prompt.create({ + data: { + id: promptId, + title: p.title, + slug: p.slug, + description: p.description, + content: p.content, + type: "TEXT", + authorId: user.id, + categoryId: category.id, + isPrivate: false, + isUnlisted: false, + }, + }); + createdPrompts[p.id] = prompt.id; + console.log(` ✅ Created "${p.title}" (id: ${prompt.id})`); + } + } + + // Create connections (the flow structure) + console.log("\n🔗 Creating flow connections..."); + + const connections = [ + // Main flow: Start -> Outline + { source: "flow-1-start", target: "flow-2-outline", label: "brainstorm complete", order: 0 }, + + // Branch: Outline -> Technical AND Creative + { source: "flow-2-outline", target: "flow-3a-technical", label: "technical path", order: 0 }, + { source: "flow-2-outline", target: "flow-3b-creative", label: "creative path", order: 1 }, + + // Technical branch continues + { source: "flow-3a-technical", target: "flow-4a-implementation", label: "analysis done", order: 0 }, + + // Creative branch continues + { source: "flow-3b-creative", target: "flow-4b-story", label: "ideas ready", order: 0 }, + + // Both branches merge to review + { source: "flow-4a-implementation", target: "flow-5-review", label: "merge results", order: 0 }, + { source: "flow-4b-story", target: "flow-5-review", label: "merge results", order: 1 }, + + // Final step + { source: "flow-5-review", target: "flow-6-publish", label: "review complete", order: 0 }, + ]; + + for (const conn of connections) { + const sourceId = createdPrompts[conn.source]; + const targetId = createdPrompts[conn.target]; + + if (!sourceId || !targetId) { + console.log(` ⚠️ Skipping connection: missing prompt IDs`); + continue; + } + + // Check if connection already exists + const existing = await prisma.promptConnection.findUnique({ + where: { sourceId_targetId: { sourceId, targetId } }, + }); + + if (existing) { + console.log(` ⏭️ Connection already exists: ${conn.source} -> ${conn.target}`); + } else { + const connectionId = generateId(); + await prisma.promptConnection.create({ + data: { + id: connectionId, + sourceId, + targetId, + label: conn.label, + order: conn.order, + }, + }); + console.log(` ✅ Created: ${conn.source} -> ${conn.target} (${conn.label}) (id: ${connectionId})`); + } + } + + console.log("\n🎉 Flow seed complete!"); + console.log("\n📋 Test user: f1@prompts.chat (password: password123)"); + console.log("\n🔗 Flow structure created:"); + console.log(" Start -> Outline -> [Technical, Creative] -> [Implementation, Story] -> Review -> Publish"); +} + +main() + .catch((e) => { + console.error("❌ Flow seeding failed:", e); + process.exit(1); + }) + .finally(async () => { + await prisma.$disconnect(); + }); diff --git a/src/app/[username]/page.tsx b/src/app/[username]/page.tsx index 35170598..6ceb35b9 100644 --- a/src/app/[username]/page.tsx +++ b/src/app/[username]/page.tsx @@ -107,9 +107,12 @@ export default async function UserProfilePage({ params, searchParams }: UserProf const validFilterDateEnd = filterDateEnd && !isNaN(filterDateEnd.getTime()) ? filterDateEnd : null; // Build where clause - show private prompts only if owner (unlisted prompts are visible on profiles) + // Exclude intermediate flow prompts (only show first prompts or standalone) + // Note: "related" connections are AI-suggested similar prompts, not flow connections const where = { authorId: user.id, deletedAt: null, + incomingConnections: { none: { label: { not: "related" } } }, ...(isOwner ? {} : { isPrivate: false }), ...(validFilterDateStart && validFilterDateEnd ? { createdAt: { @@ -143,7 +146,7 @@ export default async function UserProfilePage({ params, searchParams }: UserProf }, }, _count: { - select: { votes: true, contributors: true }, + select: { votes: true, contributors: true, outgoingConnections: true, incomingConnections: true }, }, }; diff --git a/src/app/api/prompts/[id]/flow/route.ts b/src/app/api/prompts/[id]/flow/route.ts new file mode 100644 index 00000000..863869a2 --- /dev/null +++ b/src/app/api/prompts/[id]/flow/route.ts @@ -0,0 +1,170 @@ +import { NextRequest, NextResponse } from "next/server"; +import { auth } from "@/lib/auth"; +import { db } from "@/lib/db"; + +interface FlowNode { + id: string; + title: string; + slug: string | null; + description: string | null; + content: string; + type: string; + authorId: string; + authorUsername: string; + authorAvatar: string | null; +} + +interface FlowEdge { + source: string; + target: string; + label: string; +} + +interface RouteParams { + params: Promise<{ id: string }>; +} + +/** + * Get the full flow graph for a prompt. + * This traverses to find all connected prompts and returns the complete graph. + */ +export async function GET(request: NextRequest, { params }: RouteParams) { + const { id } = await params; + + try { + const prompt = await db.prompt.findUnique({ + where: { id, deletedAt: null }, + select: { id: true, title: true, slug: true, isPrivate: true, authorId: true }, + }); + + if (!prompt) { + return NextResponse.json({ error: "Prompt not found" }, { status: 404 }); + } + + const session = await auth(); + const userId = session?.user?.id; + + // Helper to check if user can see a prompt + const canSee = (p: { isPrivate: boolean; authorId: string }) => + !p.isPrivate || p.authorId === userId; + + const nodes: Map = new Map(); + const edges: FlowEdge[] = []; + const visited = new Set(); + + // BFS to find all connected nodes (both directions) + const queue: string[] = [id]; + + while (queue.length > 0) { + const currentId = queue.shift()!; + if (visited.has(currentId)) continue; + visited.add(currentId); + + const p = await db.prompt.findUnique({ + where: { id: currentId, deletedAt: null }, + select: { + id: true, + title: true, + slug: true, + description: true, + content: true, + type: true, + isPrivate: true, + authorId: true, + author: { + select: { + username: true, + avatar: true, + }, + }, + }, + }); + + if (!p || !canSee(p)) continue; + + nodes.set(p.id, { + id: p.id, + title: p.title, + slug: p.slug, + description: p.description, + content: p.content, + type: p.type, + authorId: p.authorId, + authorUsername: p.author.username, + authorAvatar: p.author.avatar, + }); + + // Get outgoing connections + const outgoing = await db.promptConnection.findMany({ + where: { + sourceId: currentId, + label: { not: "related" }, + target: { deletedAt: null } + }, + orderBy: { order: "asc" }, + include: { + target: { + select: { id: true, title: true, slug: true, isPrivate: true, authorId: true }, + }, + }, + }); + + for (const conn of outgoing) { + if (canSee(conn.target)) { + edges.push({ + source: currentId, + target: conn.targetId, + label: conn.label, + }); + if (!visited.has(conn.targetId)) { + queue.push(conn.targetId); + } + } + } + + // Get incoming connections + const incoming = await db.promptConnection.findMany({ + where: { + targetId: currentId, + label: { not: "related" }, + source: { deletedAt: null } + }, + orderBy: { order: "asc" }, + include: { + source: { + select: { id: true, title: true, slug: true, isPrivate: true, authorId: true }, + }, + }, + }); + + for (const conn of incoming) { + if (canSee(conn.source)) { + // Only add edge if not already added + const edgeExists = edges.some(e => e.source === conn.sourceId && e.target === currentId); + if (!edgeExists) { + edges.push({ + source: conn.sourceId, + target: currentId, + label: conn.label, + }); + } + if (!visited.has(conn.sourceId)) { + queue.push(conn.sourceId); + } + } + } + } + + return NextResponse.json({ + nodes: Array.from(nodes.values()), + edges, + currentPromptId: id, + }); + } catch (error) { + console.error("Failed to fetch flow:", error); + return NextResponse.json( + { error: "Failed to fetch flow" }, + { status: 500 } + ); + } +} diff --git a/src/app/api/prompts/route.ts b/src/app/api/prompts/route.ts index 354c6da1..fa5095a3 100644 --- a/src/app/api/prompts/route.ts +++ b/src/app/api/prompts/route.ts @@ -319,6 +319,9 @@ export async function GET(request: Request) { isPrivate: false, isUnlisted: false, // Exclude unlisted prompts from public API deletedAt: null, // Exclude soft-deleted prompts + // Exclude intermediate flow prompts (only show first prompts or standalone) + // Note: "related" connections are AI-suggested similar prompts, not flow connections + incomingConnections: { none: { label: { not: "related" } } }, }; if (type) { @@ -391,7 +394,7 @@ export async function GET(request: Request) { }, }, _count: { - select: { votes: true, contributors: true }, + select: { votes: true, contributors: true, outgoingConnections: true, incomingConnections: true }, }, }, }), diff --git a/src/app/prompts/[id]/page.tsx b/src/app/prompts/[id]/page.tsx index ac7b3b5d..0b68d63f 100644 --- a/src/app/prompts/[id]/page.tsx +++ b/src/app/prompts/[id]/page.tsx @@ -652,6 +652,8 @@ export default async function PromptPage({ params }: PromptPageProps) { canEdit={canEdit} isOwner={isOwner} isLoggedIn={!!session?.user} + currentUserId={session?.user?.id} + isAdmin={isAdmin} /> )} diff --git a/src/app/prompts/page.tsx b/src/app/prompts/page.tsx index 4227390d..c5e7c061 100644 --- a/src/app/prompts/page.tsx +++ b/src/app/prompts/page.tsx @@ -116,7 +116,7 @@ function getCachedPrompts( }, }, _count: { - select: { votes: true, contributors: true }, + select: { votes: true, contributors: true, outgoingConnections: true, incomingConnections: true }, }, }, }), @@ -189,6 +189,9 @@ export default async function PromptsPage({ searchParams }: PromptsPageProps) { isPrivate: false, isUnlisted: false, // Exclude unlisted prompts deletedAt: null, // Exclude soft-deleted prompts + // Exclude intermediate flow prompts (only show first prompts or standalone) + // Note: "related" connections are AI-suggested similar prompts, not flow connections + incomingConnections: { none: { label: { not: "related" } } }, }; if (params.q) { diff --git a/src/app/skills/page.tsx b/src/app/skills/page.tsx index b17fceb0..73322d54 100644 --- a/src/app/skills/page.tsx +++ b/src/app/skills/page.tsx @@ -75,7 +75,7 @@ function getCachedSkills( }, }, _count: { - select: { votes: true, contributors: true }, + select: { votes: true, contributors: true, outgoingConnections: true, incomingConnections: true }, }, }, }), diff --git a/src/app/workflows/page.tsx b/src/app/workflows/page.tsx new file mode 100644 index 00000000..4b46f4ed --- /dev/null +++ b/src/app/workflows/page.tsx @@ -0,0 +1,160 @@ +import { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; +import { unstable_cache } from "next/cache"; +import { InfinitePromptList } from "@/components/prompts/infinite-prompt-list"; +import { db } from "@/lib/db"; + +export const metadata: Metadata = { + title: "Workflows", + description: "Browse prompts with sequential flows and connections", +}; + +// Query for workflows list (cached) +function getCachedWorkflows( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + orderBy: any, + perPage: number, + searchQuery?: string +) { + const cacheKey = JSON.stringify({ orderBy, perPage, searchQuery }); + + return unstable_cache( + async () => { + const where: Record = { + isPrivate: false, + isUnlisted: false, + deletedAt: null, + type: { not: "SKILL" }, + // Only include prompts with actual flow connections (not "related" connections) + outgoingConnections: { + some: { + label: { not: "related" }, + }, + }, + incomingConnections: { + none: { + label: { not: "related" }, + }, + }, + }; + + if (searchQuery) { + where.OR = [ + { title: { contains: searchQuery, mode: "insensitive" } }, + { content: { contains: searchQuery, mode: "insensitive" } }, + { description: { contains: searchQuery, mode: "insensitive" } }, + ]; + } + + const [workflowsRaw, totalCount] = await Promise.all([ + db.prompt.findMany({ + where, + orderBy, + skip: 0, + take: perPage, + include: { + author: { + select: { + id: true, + name: true, + username: true, + avatar: true, + verified: true, + }, + }, + category: { + include: { + parent: { + select: { id: true, name: true, slug: true }, + }, + }, + }, + tags: { + include: { + tag: true, + }, + }, + contributors: { + select: { + id: true, + username: true, + name: true, + avatar: true, + }, + }, + _count: { + select: { votes: true, contributors: true, outgoingConnections: true, incomingConnections: true }, + }, + }, + }), + db.prompt.count({ where }), + ]); + + return { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + workflows: workflowsRaw.map((p: any) => ({ + ...p, + voteCount: p._count.votes, + contributorCount: p._count.contributors, + contributors: p.contributors, + })), + total: totalCount, + }; + }, + ["workflows", cacheKey], + { tags: ["prompts", "connections"] } + )(); +} + +interface WorkflowsPageProps { + searchParams: Promise<{ + q?: string; + sort?: string; + }>; +} + +export default async function WorkflowsPage({ searchParams }: WorkflowsPageProps) { + const t = await getTranslations("workflows"); + const tNav = await getTranslations("nav"); + const tSearch = await getTranslations("search"); + const params = await searchParams; + + const perPage = 24; + + // Build order by clause + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let orderBy: any = { createdAt: "desc" }; + if (params.sort === "oldest") { + orderBy = { createdAt: "asc" }; + } else if (params.sort === "upvotes") { + orderBy = { votes: { _count: "desc" } }; + } + + const result = await getCachedWorkflows(orderBy, perPage, params.q); + const workflows = result.workflows; + const total = result.total; + + return ( +
+
+
+

{tNav("workflows")}

+ {tSearch("found", { count: total })} +
+
+ +

+ {t("description")} +

+ + +
+ ); +} diff --git a/src/components/layout/header.tsx b/src/components/layout/header.tsx index 5bb65ebc..43657f41 100644 --- a/src/components/layout/header.tsx +++ b/src/components/layout/header.tsx @@ -52,6 +52,7 @@ import { Chromium, Hammer, BookOpen, + MoreHorizontal, } from "lucide-react"; import { useTheme } from "next-themes"; import { Button } from "@/components/ui/button"; @@ -206,6 +207,13 @@ export function Header({ authProvider = "credentials", allowRegistration = true > {t("nav.skills")} + setMobileMenuOpen(false)} + className="flex items-center gap-3 px-3 py-2.5 rounded-md text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-accent transition-colors" + > + {t("nav.workflows")} + setMobileMenuOpen(false)} @@ -361,23 +369,81 @@ export function Header({ authProvider = "credentials", allowRegistration = true {t("nav.skills")} + {t("nav.workflows")} + + {/* Categories, Tags, Promptmasters - visible on lg+ screens */} + {t("nav.categories")} {t("nav.tags")} {t("nav.promptmasters")} + {/* Three-dot dropdown for Categories, Tags, Promptmasters on md screens */} + + + + + + + + {t("nav.categories")} + + + + + {t("nav.tags")} + + + + + {t("nav.promptmasters")} + + + {!branding.useCloneBranding && ( + <> + + + + + {t("nav.book")} + + + + + + + {t("nav.forKids")} + + + + + )} + + + + {t("nav.developers")} + + + + {/* Spacer */} @@ -387,7 +453,7 @@ export function Header({ authProvider = "credentials", allowRegistration = true
{/* Book link */} {!branding.useCloneBranding && ( - + )} +
+ + ); + })()} + + ); +} + export function PromptConnections({ promptId, promptTitle, canEdit, + currentUserId, + isAdmin, buttonOnly = false, sectionOnly = false, expanded: controlledExpanded, @@ -79,16 +464,29 @@ export function PromptConnections({ const [connectionType, setConnectionType] = useState<"previous" | "next">("next"); const [isExpanded, setIsExpanded] = useState(false); + // Full flow graph state + const [flowNodes, setFlowNodes] = useState([]); + const [flowEdges, setFlowEdges] = useState([]); + const fetchConnections = useCallback(async () => { try { - const res = await fetch(`/api/prompts/${promptId}/connections`, { - cache: "no-store", - }); - if (res.ok) { - const data = await res.json(); + // Fetch both regular connections and full flow + const [connRes, flowRes] = await Promise.all([ + fetch(`/api/prompts/${promptId}/connections`, { cache: "no-store" }), + fetch(`/api/prompts/${promptId}/flow`, { cache: "no-store" }), + ]); + + if (connRes.ok) { + const data = await connRes.json(); setOutgoing(data.outgoing || []); setIncoming(data.incoming || []); } + + if (flowRes.ok) { + const flowData = await flowRes.json(); + setFlowNodes(flowData.nodes || []); + setFlowEdges(flowData.edges || []); + } } catch (err) { console.error("Failed to fetch connections:", err); } finally { @@ -100,6 +498,42 @@ export function PromptConnections({ fetchConnections(); }, [fetchConnections]); + // Handle removing a node from the flow (deletes all its connections) + const handleRemoveFromFlow = useCallback(async (node: FlowGraphNode) => { + if (!confirm(`Remove "${node.title}" from this flow? This will delete all connections to/from this prompt.`)) { + return; + } + + try { + // Find all edges connected to this node + const edgesToDelete = flowEdges.filter( + e => e.source === node.id || e.target === node.id + ); + + // Delete each connection via API + for (const edge of edgesToDelete) { + // We need to find the connection ID - fetch connections for the source prompt + const res = await fetch(`/api/prompts/${edge.source}/connections`, { cache: "no-store" }); + if (res.ok) { + const data = await res.json(); + const conn = data.outgoing?.find((c: OutgoingConnection) => c.targetId === edge.target); + if (conn) { + await fetch(`/api/prompts/${edge.source}/connections/${conn.id}`, { + method: "DELETE", + cache: "no-store", + }); + } + } + } + + toast.success("Removed from flow"); + fetchConnections(); + } catch (err) { + console.error("Failed to remove from flow:", err); + toast.error("Failed to remove from flow"); + } + }, [flowEdges, fetchConnections]); + const handleDeleteConnection = useCallback(async (connectionId: string) => { try { const res = await fetch( @@ -589,6 +1023,7 @@ export function PromptConnections({ } const hasConnections = outgoing.length > 0 || incoming.length > 0; + const hasFullFlow = flowNodes.length > 1 && flowEdges.length > 0; // Show to everyone if there are connections, otherwise only show to owners/admins if (!canEdit && !hasConnections) { @@ -670,7 +1105,22 @@ export function PromptConnections({ {!hasConnections ? (

{t("noConnections")}

+ ) : hasFullFlow ? ( + /* Full Flow Graph Display with D3 */ +
+

{t("fullFlow")}

+ router.push(getPromptLink(node))} + onNodeDelete={canEdit ? handleRemoveFromFlow : undefined} + /> +
) : ( + /* Original D3 Spider Diagram for simple connections */
{incoming.length > 0 && (

{t("previousSteps")}

@@ -755,6 +1205,19 @@ export function PromptConnections({ {!hasConnections ? (

{t("noConnections")}

+ ) : hasFullFlow ? ( +
+

{t("fullFlow")}

+ router.push(getPromptLink(node))} + onNodeDelete={canEdit ? handleRemoveFromFlow : undefined} + /> +
) : (
{incoming.length > 0 && ( diff --git a/src/components/prompts/prompt-flow-section.tsx b/src/components/prompts/prompt-flow-section.tsx index 2f76b200..9425eab3 100644 --- a/src/components/prompts/prompt-flow-section.tsx +++ b/src/components/prompts/prompt-flow-section.tsx @@ -10,6 +10,8 @@ interface PromptFlowSectionProps { canEdit: boolean; isOwner: boolean; isLoggedIn: boolean; + currentUserId?: string; + isAdmin?: boolean; } export function PromptFlowSection({ @@ -18,6 +20,8 @@ export function PromptFlowSection({ canEdit, isOwner, isLoggedIn, + currentUserId, + isAdmin, }: PromptFlowSectionProps) { const [expanded, setExpanded] = useState(false); @@ -29,6 +33,8 @@ export function PromptFlowSection({ promptId={promptId} promptTitle={promptTitle} canEdit={canEdit} + currentUserId={currentUserId} + isAdmin={isAdmin} buttonOnly expanded={expanded} onExpandChange={setExpanded} @@ -43,6 +49,8 @@ export function PromptFlowSection({ promptId={promptId} promptTitle={promptTitle} canEdit={canEdit} + currentUserId={currentUserId} + isAdmin={isAdmin} sectionOnly expanded={expanded} onExpandChange={setExpanded} diff --git a/src/components/ui/masonry.tsx b/src/components/ui/masonry.tsx index 6a901584..d50b182b 100644 --- a/src/components/ui/masonry.tsx +++ b/src/components/ui/masonry.tsx @@ -8,6 +8,7 @@ interface MasonryProps { default: number; md?: number; lg?: number; + xl?: number; }; gap?: number; className?: string; @@ -44,7 +45,9 @@ export function Masonry({ useEffect(() => { const updateColumns = () => { const width = window.innerWidth; - if (width >= 1024 && columnCount.lg) { + if (width >= 1280 && columnCount.xl) { + setColumns(columnCount.xl); + } else if (width >= 1024 && columnCount.lg) { setColumns(columnCount.lg); } else if (width >= 768 && columnCount.md) { setColumns(columnCount.md); @@ -104,7 +107,29 @@ export function Masonry({ calculatePositions(); }, [columns, isRTL, calculatePositions]); - // Use ResizeObserver for image loading + // Recalculate positions on window resize (container width may change) + useEffect(() => { + const handleResize = () => { + calculatePositions(); + }; + + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); + }, [calculatePositions]); + + // Use ResizeObserver on container for layout changes + useEffect(() => { + if (!containerRef.current) return; + + const observer = new ResizeObserver(() => { + calculatePositions(); + }); + + observer.observe(containerRef.current); + return () => observer.disconnect(); + }, [calculatePositions]); + + // Use ResizeObserver for image loading and item size changes useEffect(() => { const observer = new ResizeObserver(() => { calculatePositions();