mirror of
https://github.com/f/awesome-chatgpt-prompts.git
synced 2026-02-12 15:52:47 +00:00
chore(messages): update audio upload messages for multiple languages
This commit is contained in:
@@ -186,7 +186,7 @@
|
||||
"text": "✨ السحر يحدث هنا... الذكاء الاصطناعي سيكتب شيئاً رائعاً!",
|
||||
"imageUpload": "ارفع صورة مثال",
|
||||
"videoUpload": "ارفع فيديو/GIF مثال",
|
||||
"audio": "معاينة الإخراج الصوتي"
|
||||
"audioUpload": "ارفع ملف صوتي مثال"
|
||||
},
|
||||
"insertVariable": "إدراج متغير",
|
||||
"variableName": "اسم المتغير",
|
||||
@@ -259,15 +259,19 @@
|
||||
"mediaUrlDescription": "أدخل رابط ملف الوسائط لهذا الأمر",
|
||||
"mediaImage": "صورة الوسائط",
|
||||
"mediaVideo": "فيديو الوسائط",
|
||||
"mediaAudio": "صوت الوسائط",
|
||||
"clickToUpload": "انقر لرفع صورة",
|
||||
"clickToUploadVideo": "انقر لرفع فيديو",
|
||||
"clickToUploadAudio": "انقر لرفع ملف صوتي",
|
||||
"uploading": "جارٍ الرفع...",
|
||||
"maxFileSize": "الحد الأقصى لحجم الملف: 4 ميجابايت (JPEG، PNG، GIF، WebP)",
|
||||
"maxVideoSize": "الحد الأقصى لحجم الملف: 4 ميجابايت (MP4)",
|
||||
"maxAudioSize": "الحد الأقصى لحجم الملف: 4 ميجابايت (MP3، WAV، OGG)",
|
||||
"fileTooLarge": "الملف كبير جداً. الحد الأقصى للحجم هو 4 ميجابايت.",
|
||||
"videoTooLarge": "الفيديو كبير جداً. الحد الأقصى للحجم هو 4 ميجابايت.",
|
||||
"invalidFileType": "نوع ملف غير صالح. يُسمح فقط بـ JPEG و PNG و GIF و WebP.",
|
||||
"invalidVideoType": "نوع فيديو غير صالح. يُسمح فقط بفيديوهات MP4.",
|
||||
"invalidAudioType": "نوع صوت غير صالح. يُسمح فقط بملفات MP3 و WAV و OGG.",
|
||||
"uploadMedia": "رفع الوسائط",
|
||||
"generateMedia": "إنشاء",
|
||||
"chooseGenerator": "اختر المولد",
|
||||
@@ -285,8 +289,10 @@
|
||||
"generateWith": "التوليد بالذكاء الاصطناعي",
|
||||
"generateImage": "توليد صورة",
|
||||
"generateVideo": "توليد فيديو",
|
||||
"generateAudio": "توليد صوت",
|
||||
"generateImageDescription": "أنشئ صورة فريدة لأمرك باستخدام الذكاء الاصطناعي — مثالي لعرض أمرك للمجتمع.",
|
||||
"generateVideoDescription": "أنشئ فيديو فريد لأمرك باستخدام الذكاء الاصطناعي — مثالي لعرض أمرك للمجتمع.",
|
||||
"generateAudioDescription": "أنشئ صوت/موسيقى فريدة لأمرك باستخدام الذكاء الاصطناعي — مثالي لعرض أمرك للمجتمع.",
|
||||
"generationComplete": "اكتملت العملية",
|
||||
"generationFailed": "فشلت العملية",
|
||||
"mediaAddedToPrompt": "تمت إضافة الوسائط إلى أمرك.",
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
"text": "✨ Hier passiert die Magie... Ihre KI wird etwas Brillantes schreiben!",
|
||||
"imageUpload": "Beispielbild hochladen",
|
||||
"videoUpload": "Beispielvideo/GIF hochladen",
|
||||
"audio": "Audio-Vorschau"
|
||||
"audioUpload": "Beispiel-Audio hochladen"
|
||||
},
|
||||
"structuredFormat": "Format",
|
||||
"structuredFormatDescription": "Wählen Sie das Format für Ihren strukturierten Prompt",
|
||||
@@ -251,15 +251,19 @@
|
||||
"mediaUrlDescription": "Geben Sie die URL zur Mediendatei für diesen Prompt ein",
|
||||
"mediaImage": "Medienbild",
|
||||
"mediaVideo": "Medienvideo",
|
||||
"mediaAudio": "Medien-Audio",
|
||||
"clickToUpload": "Klicken Sie, um ein Bild hochzuladen",
|
||||
"clickToUploadVideo": "Klicken Sie, um ein Video hochzuladen",
|
||||
"clickToUploadAudio": "Klicken zum Hochladen einer Audiodatei",
|
||||
"uploading": "Wird hochgeladen...",
|
||||
"maxFileSize": "Max. Dateigröße: 4MB (JPEG, PNG, GIF, WebP)",
|
||||
"maxVideoSize": "Max. Dateigröße: 4MB (MP4)",
|
||||
"maxAudioSize": "Max. Dateigröße: 4MB (MP3, WAV, OGG)",
|
||||
"fileTooLarge": "Datei zu groß. Maximale Größe ist 4MB.",
|
||||
"videoTooLarge": "Video zu groß. Maximale Größe ist 4MB.",
|
||||
"invalidFileType": "Ungültiger Dateityp. Nur JPEG, PNG, GIF und WebP sind erlaubt.",
|
||||
"invalidVideoType": "Ungültiger Videotyp. Nur MP4-Videos sind erlaubt.",
|
||||
"invalidAudioType": "Ungültiger Audiotyp. Nur MP3-, WAV- und OGG-Dateien sind erlaubt.",
|
||||
"uploadMedia": "Medien hochladen",
|
||||
"generateMedia": "Generieren",
|
||||
"chooseGenerator": "Generator wählen",
|
||||
@@ -282,8 +286,10 @@
|
||||
"generateWith": "Mit KI generieren",
|
||||
"generateImage": "Bild generieren",
|
||||
"generateVideo": "Video generieren",
|
||||
"generateAudio": "Audio generieren",
|
||||
"generateImageDescription": "Generieren Sie ein einzigartiges Bild für Ihren Prompt mit KI — perfekt, um Ihren Prompt der Community zu präsentieren.",
|
||||
"generateVideoDescription": "Generieren Sie ein einzigartiges Video für Ihren Prompt mit KI — perfekt, um Ihren Prompt der Community zu präsentieren.",
|
||||
"generateAudioDescription": "Generieren Sie einzigartiges Audio/Musik für Ihren Prompt mit KI — perfekt, um Ihren Prompt der Community zu präsentieren.",
|
||||
"close": "Schließen",
|
||||
"mediaGeneration": {
|
||||
"connecting": "Verbindung zum Server...",
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
"text": "✨ Η μαγεία συμβαίνει εδώ... το AI σας θα γράψει κάτι εξαιρετικό!",
|
||||
"imageUpload": "Ανεβάστε παράδειγμα εικόνας εξόδου",
|
||||
"videoUpload": "Ανεβάστε παράδειγμα βίντεο/GIF εξόδου",
|
||||
"audio": "Προεπισκόπηση εξόδου ήχου"
|
||||
"audioUpload": "Ανέβασμα παράδειγμα ήχου"
|
||||
},
|
||||
"structuredFormat": "Μορφή",
|
||||
"structuredFormatDescription": "Επιλέξτε τη μορφή για το δομημένο prompt σας",
|
||||
@@ -251,15 +251,19 @@
|
||||
"mediaUrlDescription": "Εισάγετε URL για το αρχείο μέσου αυτού του prompt",
|
||||
"mediaImage": "Παράδειγμα εικόνας μέσου",
|
||||
"mediaVideo": "Παράδειγμα βίντεο μέσου",
|
||||
"mediaAudio": "Ήχος Μέσων",
|
||||
"clickToUpload": "Κλικ για ανέβασμα εικόνας",
|
||||
"clickToUploadVideo": "Κλικ για ανέβασμα βίντεο",
|
||||
"clickToUploadAudio": "Κάντε κλικ για ανέβασμα αρχείου ήχου",
|
||||
"uploading": "Ανέβασμα...",
|
||||
"maxFileSize": "Μέγιστο μέγεθος αρχείου: 4MB (JPEG, PNG, GIF, WebP)",
|
||||
"maxVideoSize": "Μέγιστο μέγεθος αρχείου: 4MB (MP4)",
|
||||
"maxAudioSize": "Μέγ. μέγεθος: 4MB (MP3, WAV, OGG)",
|
||||
"fileTooLarge": "Το αρχείο είναι πολύ μεγάλο. Μέγιστο μέγεθος 4MB.",
|
||||
"videoTooLarge": "Το βίντεο είναι πολύ μεγάλο. Μέγιστο μέγεθος 4MB.",
|
||||
"invalidFileType": "Μη έγκυρος τύπος αρχείου. Επιτρέπονται μόνο JPEG, PNG, GIF και WebP.",
|
||||
"invalidVideoType": "Μη έγκυρος τύπος βίντεο. Επιτρέπονται μόνο βίντεο MP4.",
|
||||
"invalidAudioType": "Μη έγκυρος τύπος ήχου. Επιτρέπονται μόνο αρχεία MP3, WAV και OGG.",
|
||||
"uploadMedia": "Ανέβασμα Μέσου",
|
||||
"generateMedia": "Δημιουργία",
|
||||
"chooseGenerator": "Επιλογή Γεννήτριας",
|
||||
@@ -282,8 +286,10 @@
|
||||
"generateWith": "Δημιουργία με AI",
|
||||
"generateImage": "Δημιουργία Εικόνας",
|
||||
"generateVideo": "Δημιουργία Βίντεο",
|
||||
"generateAudio": "Δημιουργία Ήχου",
|
||||
"generateImageDescription": "Δημιουργήστε μια μοναδική εικόνα για το prompt σας με AI — ιδανικό για να παρουσιάσετε το prompt σας στην κοινότητα.",
|
||||
"generateVideoDescription": "Δημιουργήστε ένα μοναδικό βίντεο για το prompt σας με AI — ιδανικό για να παρουσιάσετε το prompt σας στην κοινότητα.",
|
||||
"generateAudioDescription": "Δημιουργήστε μοναδικό ήχο/μουσική για το prompt σας με AI — ιδανικό για να παρουσιάσετε το prompt σας στην κοινότητα.",
|
||||
"close": "Κλείσιμο",
|
||||
"mediaGeneration": {
|
||||
"connecting": "Σύνδεση στον διακομιστή...",
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
"text": "✨ Magic happens here... your AI will write something brilliant!",
|
||||
"imageUpload": "Upload an example image output",
|
||||
"videoUpload": "Upload an example video/GIF output",
|
||||
"audio": "Audio output preview"
|
||||
"audioUpload": "Upload an example audio output"
|
||||
},
|
||||
"structuredFormat": "Format",
|
||||
"structuredFormatDescription": "Select the format for your structured prompt",
|
||||
@@ -251,15 +251,19 @@
|
||||
"mediaUrlDescription": "Enter a URL to the media file for this prompt",
|
||||
"mediaImage": "Example Media Image",
|
||||
"mediaVideo": "Example Media Video",
|
||||
"mediaAudio": "Example Media Audio",
|
||||
"clickToUpload": "Click to upload an image",
|
||||
"clickToUploadVideo": "Click to upload a video",
|
||||
"clickToUploadAudio": "Click to upload an audio file",
|
||||
"uploading": "Uploading...",
|
||||
"maxFileSize": "Max file size: 4MB (JPEG, PNG, GIF, WebP)",
|
||||
"maxVideoSize": "Max file size: 4MB (MP4)",
|
||||
"maxAudioSize": "Max file size: 4MB (MP3, WAV, OGG)",
|
||||
"fileTooLarge": "File too large. Maximum size is 4MB.",
|
||||
"videoTooLarge": "Video too large. Maximum size is 4MB.",
|
||||
"invalidFileType": "Invalid file type. Only JPEG, PNG, GIF, and WebP are allowed.",
|
||||
"invalidVideoType": "Invalid video type. Only MP4 videos are allowed.",
|
||||
"invalidAudioType": "Invalid audio type. Only MP3, WAV, and OGG files are allowed.",
|
||||
"uploadMedia": "Upload Media",
|
||||
"generateMedia": "Generate",
|
||||
"chooseGenerator": "Choose Generator",
|
||||
@@ -282,8 +286,10 @@
|
||||
"generateWith": "Generate with AI",
|
||||
"generateImage": "Generate Image",
|
||||
"generateVideo": "Generate Video",
|
||||
"generateAudio": "Generate Audio",
|
||||
"generateImageDescription": "Generate a unique image for your prompt using AI — perfect for showcasing your prompt to the community.",
|
||||
"generateVideoDescription": "Generate a unique video for your prompt using AI — perfect for showcasing your prompt to the community.",
|
||||
"generateAudioDescription": "Generate unique audio/music for your prompt using AI — perfect for showcasing your prompt to the community.",
|
||||
"close": "Close",
|
||||
"mediaGeneration": {
|
||||
"connecting": "Connecting to server...",
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
"text": "✨ La magia sucede aquí... ¡tu IA escribirá algo brillante!",
|
||||
"imageUpload": "Sube una imagen de ejemplo",
|
||||
"videoUpload": "Sube un video/GIF de ejemplo",
|
||||
"audio": "Vista previa de audio"
|
||||
"audioUpload": "Subir ejemplo de audio"
|
||||
},
|
||||
"insertVariable": "Insertar Variable",
|
||||
"variableName": "Nombre de Variable",
|
||||
@@ -259,15 +259,19 @@
|
||||
"mediaUrlDescription": "Ingresa una URL al archivo de medios para este prompt",
|
||||
"mediaImage": "Imagen de Medios",
|
||||
"mediaVideo": "Video de Medios",
|
||||
"mediaAudio": "Audio de Medios",
|
||||
"clickToUpload": "Haz clic para subir una imagen",
|
||||
"clickToUploadVideo": "Haz clic para subir un video",
|
||||
"clickToUploadAudio": "Haz clic para subir un archivo de audio",
|
||||
"uploading": "Subiendo...",
|
||||
"maxFileSize": "Tamaño máximo: 4MB (JPEG, PNG, GIF, WebP)",
|
||||
"maxVideoSize": "Tamaño máximo: 4MB (MP4)",
|
||||
"maxAudioSize": "Tamaño máx.: 4MB (MP3, WAV, OGG)",
|
||||
"fileTooLarge": "Archivo demasiado grande. El tamaño máximo es 4MB.",
|
||||
"videoTooLarge": "Video demasiado grande. El tamaño máximo es 4MB.",
|
||||
"invalidFileType": "Tipo de archivo inválido. Solo se permiten JPEG, PNG, GIF y WebP.",
|
||||
"invalidVideoType": "Tipo de video inválido. Solo se permiten videos MP4.",
|
||||
"invalidAudioType": "Tipo de audio no válido. Solo se permiten archivos MP3, WAV y OGG.",
|
||||
"uploadMedia": "Subir Multimedia",
|
||||
"generateMedia": "Generar",
|
||||
"chooseGenerator": "Elegir Generador",
|
||||
@@ -290,8 +294,10 @@
|
||||
"generateWith": "Generar con IA",
|
||||
"generateImage": "Generar Imagen",
|
||||
"generateVideo": "Generar Video",
|
||||
"generateAudio": "Generar Audio",
|
||||
"generateImageDescription": "Genera una imagen única para tu prompt usando IA — perfecto para mostrar tu prompt a la comunidad.",
|
||||
"generateVideoDescription": "Genera un video único para tu prompt usando IA — perfecto para mostrar tu prompt a la comunidad.",
|
||||
"generateAudioDescription": "Genera audio/música única para tu prompt usando IA — perfecto para mostrar tu prompt a la comunidad.",
|
||||
"close": "Cerrar",
|
||||
"mediaGeneration": {
|
||||
"connecting": "Conectando al servidor...",
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
"text": "✨ La magie opère ici... votre IA écrira quelque chose de brillant !",
|
||||
"imageUpload": "Téléchargez une image exemple",
|
||||
"videoUpload": "Téléchargez une vidéo/GIF exemple",
|
||||
"audio": "Aperçu audio"
|
||||
"audioUpload": "Télécharger un exemple audio"
|
||||
},
|
||||
"structuredFormat": "Format",
|
||||
"structuredFormatDescription": "Sélectionnez le format pour votre prompt structuré",
|
||||
@@ -251,15 +251,19 @@
|
||||
"mediaUrlDescription": "Entrez l'URL du fichier média pour ce prompt",
|
||||
"mediaImage": "Image Média",
|
||||
"mediaVideo": "Vidéo Média",
|
||||
"mediaAudio": "Audio Média",
|
||||
"clickToUpload": "Cliquez pour télécharger une image",
|
||||
"clickToUploadVideo": "Cliquez pour télécharger une vidéo",
|
||||
"clickToUploadAudio": "Cliquez pour télécharger un fichier audio",
|
||||
"uploading": "Téléchargement...",
|
||||
"maxFileSize": "Taille max: 4Mo (JPEG, PNG, GIF, WebP)",
|
||||
"maxVideoSize": "Taille max: 4Mo (MP4)",
|
||||
"maxAudioSize": "Taille max: 4 Mo (MP3, WAV, OGG)",
|
||||
"fileTooLarge": "Fichier trop volumineux. La taille maximale est de 4Mo.",
|
||||
"videoTooLarge": "Vidéo trop volumineuse. La taille maximale est de 4Mo.",
|
||||
"invalidFileType": "Type de fichier invalide. Seuls JPEG, PNG, GIF et WebP sont autorisés.",
|
||||
"invalidVideoType": "Type de vidéo invalide. Seules les vidéos MP4 sont autorisées.",
|
||||
"invalidAudioType": "Type audio invalide. Seuls les fichiers MP3, WAV et OGG sont autorisés.",
|
||||
"uploadMedia": "Télécharger un média",
|
||||
"generateMedia": "Générer",
|
||||
"chooseGenerator": "Choisir un générateur",
|
||||
@@ -277,8 +281,10 @@
|
||||
"generateWith": "Générer avec l'IA",
|
||||
"generateImage": "Générer une Image",
|
||||
"generateVideo": "Générer une Vidéo",
|
||||
"generateAudio": "Générer Audio",
|
||||
"generateImageDescription": "Générez une image unique pour votre prompt avec l'IA — parfait pour présenter votre prompt à la communauté.",
|
||||
"generateVideoDescription": "Générez une vidéo unique pour votre prompt avec l'IA — parfait pour présenter votre prompt à la communauté.",
|
||||
"generateAudioDescription": "Générez un audio/musique unique pour votre prompt avec l'IA — parfait pour présenter votre prompt à la communauté.",
|
||||
"generationComplete": "Génération Terminée",
|
||||
"generationFailed": "Échec de la Génération",
|
||||
"mediaAddedToPrompt": "Le média a été ajouté à votre prompt.",
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
"text": "✨ הקסם קורה כאן... ה-AI שלך יכתוב משהו מבריק!",
|
||||
"imageUpload": "העלה תמונת פלט לדוגמה",
|
||||
"videoUpload": "העלה וידאו/GIF פלט לדוגמה",
|
||||
"audio": "תצוגה מקדימה של פלט אודיו"
|
||||
"audioUpload": "העלה דוגמת אודיו"
|
||||
},
|
||||
"structuredFormat": "פורמט",
|
||||
"structuredFormatDescription": "בחר את הפורמט לפרומפט המובנה שלך",
|
||||
@@ -251,15 +251,19 @@
|
||||
"mediaUrlDescription": "הזן כתובת לקובץ המדיה של הפרומפט הזה",
|
||||
"mediaImage": "תמונת מדיה לדוגמה",
|
||||
"mediaVideo": "סרטון מדיה לדוגמה",
|
||||
"mediaAudio": "אודיו מדיה",
|
||||
"clickToUpload": "לחץ להעלאת תמונה",
|
||||
"clickToUploadVideo": "לחץ להעלאת סרטון",
|
||||
"clickToUploadAudio": "לחץ להעלאת קובץ אודיו",
|
||||
"uploading": "מעלה...",
|
||||
"maxFileSize": "גודל קובץ מקסימלי: 4MB (JPEG, PNG, GIF, WebP)",
|
||||
"maxVideoSize": "גודל קובץ מקסימלי: 4MB (MP4)",
|
||||
"maxAudioSize": "גודל מקסימלי: 4MB (MP3, WAV, OGG)",
|
||||
"fileTooLarge": "הקובץ גדול מדי. גודל מקסימלי הוא 4MB.",
|
||||
"videoTooLarge": "הסרטון גדול מדי. גודל מקסימלי הוא 4MB.",
|
||||
"invalidFileType": "סוג קובץ לא חוקי. רק JPEG, PNG, GIF ו-WebP מותרים.",
|
||||
"invalidVideoType": "סוג סרטון לא חוקי. רק סרטוני MP4 מותרים.",
|
||||
"invalidAudioType": "סוג אודיו לא חוקי. רק קבצי MP3, WAV ו-OGG מותרים.",
|
||||
"uploadMedia": "העלאת מדיה",
|
||||
"generateMedia": "יצירה",
|
||||
"chooseGenerator": "בחר מחולל",
|
||||
@@ -282,8 +286,10 @@
|
||||
"generateWith": "יצירה עם AI",
|
||||
"generateImage": "יצירת תמונה",
|
||||
"generateVideo": "יצירת וידאו",
|
||||
"generateAudio": "יצירת אודיו",
|
||||
"generateImageDescription": "צור תמונה ייחודית לפרומפט שלך באמצעות AI — מושלם להצגת הפרומפט שלך לקהילה.",
|
||||
"generateVideoDescription": "צור וידאו ייחודי לפרומפט שלך באמצעות AI — מושלם להצגת הפרומפט שלך לקהילה.",
|
||||
"generateAudioDescription": "צור אודיו/מוזיקה ייחודיים לפרומפט שלך באמצעות AI — מושלם להצגת הפרומפט שלך לקהילה.",
|
||||
"close": "סגור",
|
||||
"mediaGeneration": {
|
||||
"connecting": "מתחבר לשרת...",
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
"text": "✨ La magia accade qui... la tua IA scriverà qualcosa di brillante!",
|
||||
"imageUpload": "Carica un'immagine di esempio",
|
||||
"videoUpload": "Carica un video/GIF di esempio",
|
||||
"audio": "Anteprima audio"
|
||||
"audioUpload": "Carica esempio audio"
|
||||
},
|
||||
"structuredFormat": "Formato",
|
||||
"structuredFormatDescription": "Seleziona il formato per il tuo prompt strutturato",
|
||||
@@ -251,15 +251,19 @@
|
||||
"mediaUrlDescription": "Inserisci un URL al file multimediale per questo prompt",
|
||||
"mediaImage": "Immagine Media",
|
||||
"mediaVideo": "Video Media",
|
||||
"mediaAudio": "Audio Media",
|
||||
"clickToUpload": "Clicca per caricare un'immagine",
|
||||
"clickToUploadVideo": "Clicca per caricare un video",
|
||||
"clickToUploadAudio": "Clicca per caricare un file audio",
|
||||
"uploading": "Caricamento...",
|
||||
"maxFileSize": "Dimensione max: 4MB (JPEG, PNG, GIF, WebP)",
|
||||
"maxVideoSize": "Dimensione max: 4MB (MP4)",
|
||||
"maxAudioSize": "Dim. max: 4MB (MP3, WAV, OGG)",
|
||||
"fileTooLarge": "File troppo grande. La dimensione massima è 4MB.",
|
||||
"videoTooLarge": "Video troppo grande. La dimensione massima è 4MB.",
|
||||
"invalidFileType": "Tipo di file non valido. Sono consentiti solo JPEG, PNG, GIF e WebP.",
|
||||
"invalidVideoType": "Tipo di video non valido. Sono consentiti solo video MP4.",
|
||||
"invalidAudioType": "Tipo audio non valido. Sono consentiti solo file MP3, WAV e OGG.",
|
||||
"uploadMedia": "Carica Media",
|
||||
"generateMedia": "Genera",
|
||||
"chooseGenerator": "Scegli Generatore",
|
||||
@@ -282,8 +286,10 @@
|
||||
"generateWith": "Genera con AI",
|
||||
"generateImage": "Genera Immagine",
|
||||
"generateVideo": "Genera Video",
|
||||
"generateAudio": "Genera Audio",
|
||||
"generateImageDescription": "Genera un'immagine unica per il tuo prompt usando l'IA — perfetto per mostrare il tuo prompt alla community.",
|
||||
"generateVideoDescription": "Genera un video unico per il tuo prompt usando l'IA — perfetto per mostrare il tuo prompt alla community.",
|
||||
"generateAudioDescription": "Genera audio/musica unici per il tuo prompt usando l'IA — perfetto per mostrare il tuo prompt alla community.",
|
||||
"close": "Chiudi",
|
||||
"mediaGeneration": {
|
||||
"connecting": "Connessione al server...",
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
"text": "✨ ここで魔法が起こります... AIが素晴らしいものを書きます!",
|
||||
"imageUpload": "サンプル画像をアップロード",
|
||||
"videoUpload": "サンプル動画/GIFをアップロード",
|
||||
"audio": "音声出力プレビュー"
|
||||
"audioUpload": "サンプルオーディオをアップロード"
|
||||
},
|
||||
"insertVariable": "変数を挿入",
|
||||
"variableName": "変数名",
|
||||
@@ -259,15 +259,19 @@
|
||||
"mediaUrlDescription": "このプロンプトのメディアファイルURLを入力",
|
||||
"mediaImage": "メディア画像",
|
||||
"mediaVideo": "メディア動画",
|
||||
"mediaAudio": "メディアオーディオ",
|
||||
"clickToUpload": "クリックして画像をアップロード",
|
||||
"clickToUploadVideo": "クリックして動画をアップロード",
|
||||
"clickToUploadAudio": "クリックしてオーディオファイルをアップロード",
|
||||
"uploading": "アップロード中...",
|
||||
"maxFileSize": "最大ファイルサイズ: 4MB (JPEG, PNG, GIF, WebP)",
|
||||
"maxVideoSize": "最大ファイルサイズ: 4MB (MP4)",
|
||||
"maxAudioSize": "最大ファイルサイズ: 4MB (MP3, WAV, OGG)",
|
||||
"fileTooLarge": "ファイルが大きすぎます。最大サイズは4MBです。",
|
||||
"videoTooLarge": "動画が大きすぎます。最大サイズは4MBです。",
|
||||
"invalidFileType": "無効なファイル形式です。JPEG、PNG、GIF、WebPのみ許可されています。",
|
||||
"invalidVideoType": "無効な動画形式です。MP4動画のみ許可されています。",
|
||||
"invalidAudioType": "無効なオーディオタイプです。MP3、WAV、OGGファイルのみ許可されています。",
|
||||
"uploadMedia": "メディアをアップロード",
|
||||
"generateMedia": "生成",
|
||||
"chooseGenerator": "生成ツールを選択",
|
||||
@@ -290,8 +294,10 @@
|
||||
"generateWith": "AIで生成",
|
||||
"generateImage": "画像を生成",
|
||||
"generateVideo": "動画を生成",
|
||||
"generateAudio": "オーディオを生成",
|
||||
"generateImageDescription": "AIを使ってプロンプト用のユニークな画像を生成 — コミュニティにプロンプトを紹介するのに最適。",
|
||||
"generateVideoDescription": "AIを使ってプロンプト用のユニークな動画を生成 — コミュニティにプロンプトを紹介するのに最適。",
|
||||
"generateAudioDescription": "AIを使用してプロンプト用のユニークなオーディオ/音楽を生成 — コミュニティにプロンプトを紹介するのに最適です。",
|
||||
"close": "閉じる",
|
||||
"mediaGeneration": {
|
||||
"connecting": "サーバーに接続中...",
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
"text": "✨ 마법이 일어나는 곳... AI가 멋진 것을 작성할 거예요!",
|
||||
"imageUpload": "예시 이미지 업로드",
|
||||
"videoUpload": "예시 비디오/GIF 업로드",
|
||||
"audio": "오디오 출력 미리보기"
|
||||
"audioUpload": "예시 오디오 업로드"
|
||||
},
|
||||
"structuredFormat": "형식",
|
||||
"structuredFormatDescription": "구조화된 프롬프트의 형식을 선택하세요",
|
||||
@@ -251,15 +251,19 @@
|
||||
"mediaUrlDescription": "이 프롬프트의 미디어 파일 URL을 입력하세요",
|
||||
"mediaImage": "미디어 이미지",
|
||||
"mediaVideo": "미디어 비디오",
|
||||
"mediaAudio": "미디어 오디오",
|
||||
"clickToUpload": "클릭하여 이미지 업로드",
|
||||
"clickToUploadVideo": "클릭하여 비디오 업로드",
|
||||
"clickToUploadAudio": "오디오 파일을 업로드하려면 클릭",
|
||||
"uploading": "업로드 중...",
|
||||
"maxFileSize": "최대 파일 크기: 4MB (JPEG, PNG, GIF, WebP)",
|
||||
"maxVideoSize": "최대 파일 크기: 4MB (MP4)",
|
||||
"maxAudioSize": "최대 파일 크기: 4MB (MP3, WAV, OGG)",
|
||||
"fileTooLarge": "파일이 너무 큽니다. 최대 크기는 4MB입니다.",
|
||||
"videoTooLarge": "동영상이 너무 큽니다. 최대 크기는 4MB입니다.",
|
||||
"invalidFileType": "잘못된 파일 형식입니다. JPEG, PNG, GIF, WebP만 허용됩니다.",
|
||||
"invalidVideoType": "잘못된 동영상 형식입니다. MP4 동영상만 허용됩니다.",
|
||||
"invalidAudioType": "잘못된 오디오 형식입니다. MP3, WAV, OGG 파일만 허용됩니다.",
|
||||
"uploadMedia": "미디어 업로드",
|
||||
"generateMedia": "생성",
|
||||
"chooseGenerator": "생성기 선택",
|
||||
@@ -282,8 +286,10 @@
|
||||
"generateWith": "AI로 생성",
|
||||
"generateImage": "이미지 생성",
|
||||
"generateVideo": "비디오 생성",
|
||||
"generateAudio": "오디오 생성",
|
||||
"generateImageDescription": "AI를 사용하여 프롬프트용 고유한 이미지 생성 — 커뮤니티에 프롬프트를 선보이기에 완벽합니다.",
|
||||
"generateVideoDescription": "AI를 사용하여 프롬프트용 고유한 비디오 생성 — 커뮤니티에 프롬프트를 선보이기에 완벽합니다.",
|
||||
"generateAudioDescription": "AI를 사용하여 프롬프트에 맞는 고유한 오디오/음악을 생성하세요 — 커뮤니티에 프롬프트를 선보이기에 완벽합니다.",
|
||||
"close": "닫기",
|
||||
"mediaGeneration": {
|
||||
"connecting": "서버에 연결 중...",
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
"text": "✨ A magia acontece aqui... sua IA escreverá algo brilhante!",
|
||||
"imageUpload": "Envie uma imagem de exemplo",
|
||||
"videoUpload": "Envie um vídeo/GIF de exemplo",
|
||||
"audio": "Prévia de áudio"
|
||||
"audioUpload": "Carregar exemplo de áudio"
|
||||
},
|
||||
"structuredFormat": "Formato",
|
||||
"structuredFormatDescription": "Selecione o formato para seu prompt estruturado",
|
||||
@@ -251,15 +251,19 @@
|
||||
"mediaUrlDescription": "Digite a URL do arquivo de mídia para este prompt",
|
||||
"mediaImage": "Imagem de Mídia",
|
||||
"mediaVideo": "Vídeo de Mídia",
|
||||
"mediaAudio": "Áudio de Mídia",
|
||||
"clickToUpload": "Clique para enviar uma imagem",
|
||||
"clickToUploadVideo": "Clique para enviar um vídeo",
|
||||
"clickToUploadAudio": "Clique para carregar um arquivo de áudio",
|
||||
"uploading": "Enviando...",
|
||||
"maxFileSize": "Tamanho máximo: 4MB (JPEG, PNG, GIF, WebP)",
|
||||
"maxVideoSize": "Tamanho máximo: 4MB (MP4)",
|
||||
"maxAudioSize": "Tamanho máx.: 4MB (MP3, WAV, OGG)",
|
||||
"fileTooLarge": "Arquivo muito grande. O tamanho máximo é 4MB.",
|
||||
"videoTooLarge": "Vídeo muito grande. O tamanho máximo é 4MB.",
|
||||
"invalidFileType": "Tipo de arquivo inválido. Apenas JPEG, PNG, GIF e WebP são permitidos.",
|
||||
"invalidVideoType": "Tipo de vídeo inválido. Apenas vídeos MP4 são permitidos.",
|
||||
"invalidAudioType": "Tipo de áudio inválido. Apenas arquivos MP3, WAV e OGG são permitidos.",
|
||||
"uploadMedia": "Enviar Mídia",
|
||||
"generateMedia": "Gerar",
|
||||
"chooseGenerator": "Escolher Gerador",
|
||||
@@ -282,8 +286,10 @@
|
||||
"generateWith": "Gerar com IA",
|
||||
"generateImage": "Gerar Imagem",
|
||||
"generateVideo": "Gerar Vídeo",
|
||||
"generateAudio": "Gerar Áudio",
|
||||
"generateImageDescription": "Gere uma imagem única para seu prompt usando IA — perfeito para mostrar seu prompt à comunidade.",
|
||||
"generateVideoDescription": "Gere um vídeo único para seu prompt usando IA — perfeito para mostrar seu prompt à comunidade.",
|
||||
"generateAudioDescription": "Gere áudio/música únicos para seu prompt usando IA — perfeito para apresentar seu prompt à comunidade.",
|
||||
"close": "Fechar",
|
||||
"mediaGeneration": {
|
||||
"connecting": "Conectando ao servidor...",
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
"text": "✨ Здесь произойдет магия... ваш AI напишет что-то гениальное!",
|
||||
"imageUpload": "Загрузите пример изображения",
|
||||
"videoUpload": "Загрузите пример видео/GIF",
|
||||
"audio": "Предпросмотр аудио"
|
||||
"audioUpload": "Загрузить пример аудио"
|
||||
},
|
||||
"structuredFormat": "Формат",
|
||||
"structuredFormatDescription": "Выберите формат для структурированного промпта",
|
||||
@@ -251,15 +251,19 @@
|
||||
"mediaUrlDescription": "Введите URL медиафайла для этого промпта",
|
||||
"mediaImage": "Пример изображения",
|
||||
"mediaVideo": "Пример видео",
|
||||
"mediaAudio": "Медиа Аудио",
|
||||
"clickToUpload": "Нажмите для загрузки изображения",
|
||||
"clickToUploadVideo": "Нажмите для загрузки видео",
|
||||
"clickToUploadAudio": "Нажмите для загрузки аудиофайла",
|
||||
"uploading": "Загрузка...",
|
||||
"maxFileSize": "Максимальный размер: 4MB (JPEG, PNG, GIF, WebP)",
|
||||
"maxVideoSize": "Максимальный размер: 4MB (MP4)",
|
||||
"maxAudioSize": "Макс. размер: 4МБ (MP3, WAV, OGG)",
|
||||
"fileTooLarge": "Файл слишком большой. Максимальный размер 4MB.",
|
||||
"videoTooLarge": "Видео слишком большое. Максимальный размер 4MB.",
|
||||
"invalidFileType": "Недопустимый тип файла. Разрешены только JPEG, PNG, GIF и WebP.",
|
||||
"invalidVideoType": "Недопустимый тип видео. Разрешены только видео MP4.",
|
||||
"invalidAudioType": "Недопустимый тип аудио. Разрешены только файлы MP3, WAV и OGG.",
|
||||
"uploadMedia": "Загрузить медиа",
|
||||
"generateMedia": "Сгенерировать",
|
||||
"chooseGenerator": "Выбрать генератор",
|
||||
@@ -282,8 +286,10 @@
|
||||
"generateWith": "Генерировать с ИИ",
|
||||
"generateImage": "Сгенерировать изображение",
|
||||
"generateVideo": "Сгенерировать видео",
|
||||
"generateAudio": "Создать Аудио",
|
||||
"generateImageDescription": "Сгенерируйте уникальное изображение для вашего промпта с помощью ИИ — идеально для демонстрации вашего промпта сообществу.",
|
||||
"generateVideoDescription": "Сгенерируйте уникальное видео для вашего промпта с помощью ИИ — идеально для демонстрации вашего промпта сообществу.",
|
||||
"generateAudioDescription": "Создайте уникальное аудио/музыку для вашего промпта с помощью ИИ — идеально для демонстрации вашего промпта сообществу.",
|
||||
"close": "Закрыть",
|
||||
"mediaGeneration": {
|
||||
"connecting": "Подключение к серверу...",
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
"text": "AI bu prompt'u kullanarak metin yanıtı üretecek",
|
||||
"imageUpload": "Örnek görsel çıktısı yükle",
|
||||
"videoUpload": "Örnek video/GIF çıktısı yükle",
|
||||
"audio": "Ses çıktısı önizlemesi"
|
||||
"audioUpload": "Örnek ses çıktısı yükle"
|
||||
},
|
||||
"structuredFormat": "Format",
|
||||
"structuredFormatDescription": "Yapılandırılmış promptunuz için format seçin",
|
||||
@@ -251,15 +251,19 @@
|
||||
"mediaUrlDescription": "Bu prompt için medya dosyasının URL'sini girin",
|
||||
"mediaImage": "Medya Görseli",
|
||||
"mediaVideo": "Medya Videosu",
|
||||
"mediaAudio": "Medya Sesi",
|
||||
"clickToUpload": "Görsel yüklemek için tıklayın",
|
||||
"clickToUploadVideo": "Video yüklemek için tıklayın",
|
||||
"clickToUploadAudio": "Ses dosyası yüklemek için tıklayın",
|
||||
"uploading": "Yükleniyor...",
|
||||
"maxFileSize": "Maksimum dosya boyutu: 4MB (JPEG, PNG, GIF, WebP)",
|
||||
"maxVideoSize": "Maksimum dosya boyutu: 4MB (MP4)",
|
||||
"maxAudioSize": "Maksimum dosya boyutu: 4MB (MP3, WAV, OGG)",
|
||||
"fileTooLarge": "Dosya çok büyük. Maksimum boyut 4MB'dir.",
|
||||
"videoTooLarge": "Video çok büyük. Maksimum boyut 4MB'dir.",
|
||||
"invalidFileType": "Geçersiz dosya türü. Yalnızca JPEG, PNG, GIF ve WebP'ye izin verilir.",
|
||||
"invalidVideoType": "Geçersiz video türü. Yalnızca MP4 videolara izin verilir.",
|
||||
"invalidVideoType": "Geçersiz video tipi. Sadece MP4 videoları kabul edilir.",
|
||||
"invalidAudioType": "Geçersiz ses tipi. Sadece MP3, WAV ve OGG dosyaları kabul edilir.",
|
||||
"uploadMedia": "Medya Yükle",
|
||||
"generateMedia": "Oluştur",
|
||||
"chooseGenerator": "Oluşturucu Seç",
|
||||
@@ -282,8 +286,10 @@
|
||||
"generateWith": "AI ile oluştur",
|
||||
"generateImage": "Görsel Oluştur",
|
||||
"generateVideo": "Video Oluştur",
|
||||
"generateAudio": "Ses Oluştur",
|
||||
"generateImageDescription": "Promptunuz için AI kullanarak benzersiz bir görsel oluşturun — topluluğa promptunuzu sergilemek için mükemmel.",
|
||||
"generateVideoDescription": "Promptunuz için AI kullanarak benzersiz bir video oluşturun — topluluğa promptunuzu sergilemek için mükemmel.",
|
||||
"generateAudioDescription": "Promptunuz için AI kullanarak benzersiz ses/müzik oluşturun — topluluğa promptunuzu sergilemek için mükemmel.",
|
||||
"close": "Kapat",
|
||||
"mediaGeneration": {
|
||||
"connecting": "Sunucuya bağlanılıyor...",
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
"text": "✨ 魔法在这里发生... 你的 AI 将写出精彩的内容!",
|
||||
"imageUpload": "上传示例图片",
|
||||
"videoUpload": "上传示例视频/GIF",
|
||||
"audio": "音频输出预览"
|
||||
"audioUpload": "上传示例音频"
|
||||
},
|
||||
"structuredFormat": "格式",
|
||||
"structuredFormatDescription": "选择结构化提示词的格式",
|
||||
@@ -251,15 +251,19 @@
|
||||
"mediaUrlDescription": "输入此提示词的媒体文件链接",
|
||||
"mediaImage": "媒体图片",
|
||||
"mediaVideo": "媒体视频",
|
||||
"mediaAudio": "媒体音频",
|
||||
"clickToUpload": "点击上传图片",
|
||||
"clickToUploadVideo": "点击上传视频",
|
||||
"clickToUploadAudio": "点击上传音频文件",
|
||||
"uploading": "上传中...",
|
||||
"maxFileSize": "最大文件大小:4MB(JPEG、PNG、GIF、WebP)",
|
||||
"maxVideoSize": "最大文件大小:4MB(MP4)",
|
||||
"maxAudioSize": "最大文件大小:4MB(MP3、WAV、OGG)",
|
||||
"fileTooLarge": "文件太大。最大大小为4MB。",
|
||||
"videoTooLarge": "视频太大。最大大小为4MB。",
|
||||
"invalidFileType": "无效的文件类型。仅允许JPEG、PNG、GIF和WebP。",
|
||||
"invalidVideoType": "无效的视频类型。仅允许MP4视频。",
|
||||
"invalidAudioType": "无效的音频类型。仅允许 MP3、WAV 和 OGG 文件。",
|
||||
"uploadMedia": "上传媒体",
|
||||
"generateMedia": "生成",
|
||||
"chooseGenerator": "选择生成器",
|
||||
@@ -282,8 +286,10 @@
|
||||
"generateWith": "使用AI生成",
|
||||
"generateImage": "生成图片",
|
||||
"generateVideo": "生成视频",
|
||||
"generateAudio": "生成音频",
|
||||
"generateImageDescription": "使用AI为您的提示词生成独特的图片 — 完美展示您的提示词给社区。",
|
||||
"generateVideoDescription": "使用AI为您的提示词生成独特的视频 — 完美展示您的提示词给社区。",
|
||||
"generateAudioDescription": "使用 AI 为您的提示词生成独特的音频/音乐 — 非常适合向社区展示您的提示词。",
|
||||
"close": "关闭",
|
||||
"mediaGeneration": {
|
||||
"connecting": "正在连接服务器...",
|
||||
|
||||
7
public/sponsors/fal-dark.svg
Normal file
7
public/sponsors/fal-dark.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="436" height="175" viewBox="0 0 436 175" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M436.001 23.4839V151.377C436.001 171.666 435.773 172.578 433.268 172.578H402.296C399.791 172.578 399.336 171.666 399.336 151.377V23.4839C399.336 3.1943 399.791 2.28241 402.296 2.28241H433.268C435.773 2.28241 436.001 3.1943 436.001 23.4839Z" fill="white"/>
|
||||
<path d="M349.164 98.257V93.4696C349.164 82.0709 343.926 77.5115 334.816 77.5115C325.935 77.5115 321.152 82.2989 319.786 90.7339C319.331 93.2416 319.558 95.2934 318.192 95.2934H287.448C285.626 95.2934 285.626 94.8374 285.626 93.2416C285.626 81.1591 297.013 56.082 336.638 56.082C364.649 56.082 384.69 67.2527 384.69 98.257V143.852C384.69 154.338 390.383 169.157 390.383 171.208C390.383 172.12 389.7 172.576 389.017 172.576H354.857C353.263 172.576 353.035 171.664 351.896 165.965L350.985 161.634C350.302 158.214 349.847 157.074 348.48 157.074C346.658 157.074 345.52 161.178 340.51 165.965C335.044 170.98 328.44 174.4 317.053 174.4C298.607 174.4 281.982 163.913 281.982 141.116C281.982 115.583 301.795 104.64 331.173 103.5C346.431 102.816 349.164 104.412 349.164 98.257ZM349.164 131.313V126.754C349.164 122.194 348.025 120.826 344.153 121.054L335.727 121.51C325.024 122.194 318.647 127.666 318.647 137.924C318.647 147.955 324.113 152.971 332.084 152.971C340.965 152.971 349.164 144.764 349.164 131.313Z" fill="white"/>
|
||||
<path d="M220.001 85.9458C220.001 81.6143 218.179 81.1584 209.753 81.1584H205.654C204.06 81.1584 203.832 80.0185 203.832 70.6716V69.0758C203.832 59.7289 204.06 58.817 205.654 58.817H210.436C218.862 58.817 220.001 58.1331 220.001 54.0296V39.8953C220.001 13.2224 233.437 0 259.399 0C269.647 0 276.706 1.5958 277.845 2.27973C278.301 2.73567 278.301 3.41959 278.301 11.8546V14.8182C278.301 23.4812 278.301 25.533 277.617 25.533C276.934 25.533 273.746 23.4812 268.28 23.4812C260.993 23.4812 256.438 27.1288 256.438 39.8953V54.0296C256.438 58.1331 258.032 58.817 268.28 58.817H278.528C280.578 58.817 280.578 59.7289 280.578 69.0758V70.6716C280.578 80.0185 280.35 81.1584 278.756 81.1584H268.28C258.032 81.1584 256.438 81.6143 256.438 85.9458V151.374C256.438 171.664 255.755 172.576 253.933 172.576H222.278C220.456 172.576 220.001 171.664 220.001 151.374V85.9458Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M109.571 2.69C112.515 2.69 114.874 5.08348 115.155 8.01352C117.665 34.149 138.466 54.948 164.603 57.458C167.534 57.7394 169.927 60.0985 169.927 63.042V112.255C169.927 115.198 167.534 117.557 164.603 117.839C138.466 120.349 117.665 141.148 115.155 167.283C114.874 170.213 112.515 172.607 109.571 172.607H60.3553C57.4116 172.607 55.0524 170.213 54.7709 167.283C52.2608 141.148 31.4601 120.349 5.32289 117.839C2.39266 117.557 -0.000976562 115.198 -0.000976562 112.255V63.042C-0.000976562 60.0985 2.39267 57.7394 5.3229 57.458C31.4601 54.948 52.2608 34.149 54.7709 8.01351C55.0524 5.08348 57.4116 2.69 60.3553 2.69H109.571ZM34.1182 87.5045C34.1182 115.776 57.0124 138.694 85.2539 138.694C113.495 138.694 136.39 115.776 136.39 87.5045C136.39 59.2332 113.495 36.3147 85.2539 36.3147C57.0124 36.3147 34.1182 59.2332 34.1182 87.5045Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
7
public/sponsors/fal.svg
Normal file
7
public/sponsors/fal.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="436" height="175" viewBox="0 0 436 175" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M436.001 23.4839V151.377C436.001 171.666 435.773 172.578 433.268 172.578H402.296C399.791 172.578 399.336 171.666 399.336 151.377V23.4839C399.336 3.1943 399.791 2.28241 402.296 2.28241H433.268C435.773 2.28241 436.001 3.1943 436.001 23.4839Z" fill="black"/>
|
||||
<path d="M349.164 98.257V93.4696C349.164 82.0709 343.926 77.5115 334.816 77.5115C325.935 77.5115 321.152 82.2989 319.786 90.7339C319.331 93.2416 319.558 95.2934 318.192 95.2934H287.448C285.626 95.2934 285.626 94.8374 285.626 93.2416C285.626 81.1591 297.013 56.082 336.638 56.082C364.649 56.082 384.69 67.2527 384.69 98.257V143.852C384.69 154.338 390.383 169.157 390.383 171.208C390.383 172.12 389.7 172.576 389.017 172.576H354.857C353.263 172.576 353.035 171.664 351.896 165.965L350.985 161.634C350.302 158.214 349.847 157.074 348.48 157.074C346.658 157.074 345.52 161.178 340.51 165.965C335.044 170.98 328.44 174.4 317.053 174.4C298.607 174.4 281.982 163.913 281.982 141.116C281.982 115.583 301.795 104.64 331.173 103.5C346.431 102.816 349.164 104.412 349.164 98.257ZM349.164 131.313V126.754C349.164 122.194 348.025 120.826 344.153 121.054L335.727 121.51C325.024 122.194 318.647 127.666 318.647 137.924C318.647 147.955 324.113 152.971 332.084 152.971C340.965 152.971 349.164 144.764 349.164 131.313Z" fill="black"/>
|
||||
<path d="M220.001 85.9458C220.001 81.6143 218.179 81.1584 209.753 81.1584H205.654C204.06 81.1584 203.832 80.0185 203.832 70.6716V69.0758C203.832 59.7289 204.06 58.817 205.654 58.817H210.436C218.862 58.817 220.001 58.1331 220.001 54.0296V39.8953C220.001 13.2224 233.437 0 259.399 0C269.647 0 276.706 1.5958 277.845 2.27973C278.301 2.73567 278.301 3.41959 278.301 11.8546V14.8182C278.301 23.4812 278.301 25.533 277.617 25.533C276.934 25.533 273.746 23.4812 268.28 23.4812C260.993 23.4812 256.438 27.1288 256.438 39.8953V54.0296C256.438 58.1331 258.032 58.817 268.28 58.817H278.528C280.578 58.817 280.578 59.7289 280.578 69.0758V70.6716C280.578 80.0185 280.35 81.1584 278.756 81.1584H268.28C258.032 81.1584 256.438 81.6143 256.438 85.9458V151.374C256.438 171.664 255.755 172.576 253.933 172.576H222.278C220.456 172.576 220.001 171.664 220.001 151.374V85.9458Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M109.571 2.69C112.515 2.69 114.874 5.08348 115.155 8.01352C117.665 34.149 138.466 54.948 164.603 57.458C167.534 57.7394 169.927 60.0985 169.927 63.042V112.255C169.927 115.198 167.534 117.557 164.603 117.839C138.466 120.349 117.665 141.148 115.155 167.283C114.874 170.213 112.515 172.607 109.571 172.607H60.3553C57.4116 172.607 55.0524 170.213 54.7709 167.283C52.2608 141.148 31.4601 120.349 5.32289 117.839C2.39266 117.557 -0.000976562 115.198 -0.000976562 112.255V63.042C-0.000976562 60.0985 2.39267 57.7394 5.3229 57.458C31.4601 54.948 52.2608 34.149 54.7709 8.01351C55.0524 5.08348 57.4116 2.69 60.3553 2.69H109.571ZM34.1182 87.5045C34.1182 115.776 57.0124 138.694 85.2539 138.694C113.495 138.694 136.39 115.776 136.39 87.5045C136.39 59.2332 113.495 36.3147 85.2539 36.3147C57.0124 36.3147 34.1182 59.2332 34.1182 87.5045Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
@@ -17,6 +17,7 @@ export async function GET() {
|
||||
const available = isMediaGenerationAvailable();
|
||||
const imageModels = getAvailableModels("image");
|
||||
const videoModels = getAvailableModels("video");
|
||||
const audioModels = getAvailableModels("audio");
|
||||
|
||||
// Get user's credit info
|
||||
const user = await db.user.findUnique({
|
||||
@@ -32,6 +33,7 @@ export async function GET() {
|
||||
available,
|
||||
imageModels,
|
||||
videoModels,
|
||||
audioModels,
|
||||
credits: {
|
||||
remaining: user?.generationCreditsRemaining ?? 0,
|
||||
daily: user?.dailyGenerationLimit ?? 0,
|
||||
|
||||
206
src/components/prompts/audio-player.tsx
Normal file
206
src/components/prompts/audio-player.tsx
Normal file
@@ -0,0 +1,206 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useRef, useEffect, useMemo } from "react";
|
||||
import { Play, Pause, Volume2, VolumeX } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface AudioPlayerProps {
|
||||
src: string;
|
||||
onError?: () => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const BARS = 32;
|
||||
|
||||
export function AudioPlayer({ src, onError, className }: AudioPlayerProps) {
|
||||
const audioRef = useRef<HTMLAudioElement>(null);
|
||||
const animationRef = useRef<number | null>(null);
|
||||
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [isMuted, setIsMuted] = useState(false);
|
||||
const [currentTime, setCurrentTime] = useState(0);
|
||||
const [duration, setDuration] = useState(0);
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
const [animatedHeights, setAnimatedHeights] = useState<number[]>(Array(BARS).fill(0.15));
|
||||
|
||||
const formatTime = (time: number) => {
|
||||
if (!isFinite(time)) return "0:00";
|
||||
const minutes = Math.floor(time / 60);
|
||||
const seconds = Math.floor(time % 60);
|
||||
return `${minutes}:${seconds.toString().padStart(2, "0")}`;
|
||||
};
|
||||
|
||||
// Generate static waveform pattern based on src
|
||||
const baseHeights = useMemo(() => {
|
||||
const seed = src.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
||||
return Array.from({ length: BARS }, (_, i) => {
|
||||
const pseudo = Math.abs(Math.sin(seed + i * 12.9898) * 43758.5453 % 1);
|
||||
return 0.2 + pseudo * 0.6;
|
||||
});
|
||||
}, [src]);
|
||||
|
||||
// Animate waveform when playing
|
||||
useEffect(() => {
|
||||
if (!isPlaying) {
|
||||
if (animationRef.current) {
|
||||
cancelAnimationFrame(animationRef.current);
|
||||
animationRef.current = null;
|
||||
}
|
||||
setAnimatedHeights(baseHeights.map(h => h * 0.5));
|
||||
return;
|
||||
}
|
||||
|
||||
let phase = 0;
|
||||
const animate = () => {
|
||||
phase += 0.15;
|
||||
const newHeights = baseHeights.map((base, i) => {
|
||||
const wave = Math.sin(phase + i * 0.3) * 0.3 + 0.7;
|
||||
const beat = Math.sin(phase * 2 + i * 0.1) * 0.2 + 0.8;
|
||||
return Math.min(1, base * wave * beat);
|
||||
});
|
||||
setAnimatedHeights(newHeights);
|
||||
animationRef.current = requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
animate();
|
||||
|
||||
return () => {
|
||||
if (animationRef.current) {
|
||||
cancelAnimationFrame(animationRef.current);
|
||||
}
|
||||
};
|
||||
}, [isPlaying, baseHeights]);
|
||||
|
||||
useEffect(() => {
|
||||
const audio = audioRef.current;
|
||||
if (!audio) return;
|
||||
|
||||
const handleLoadedMetadata = () => {
|
||||
setDuration(audio.duration);
|
||||
setIsLoaded(true);
|
||||
};
|
||||
|
||||
const handleTimeUpdate = () => {
|
||||
setCurrentTime(audio.currentTime);
|
||||
};
|
||||
|
||||
const handleEnded = () => {
|
||||
setIsPlaying(false);
|
||||
};
|
||||
|
||||
const handleError = () => {
|
||||
onError?.();
|
||||
};
|
||||
|
||||
const handlePlay = () => setIsPlaying(true);
|
||||
const handlePause = () => setIsPlaying(false);
|
||||
|
||||
audio.addEventListener("loadedmetadata", handleLoadedMetadata);
|
||||
audio.addEventListener("timeupdate", handleTimeUpdate);
|
||||
audio.addEventListener("ended", handleEnded);
|
||||
audio.addEventListener("error", handleError);
|
||||
audio.addEventListener("play", handlePlay);
|
||||
audio.addEventListener("pause", handlePause);
|
||||
|
||||
return () => {
|
||||
audio.removeEventListener("loadedmetadata", handleLoadedMetadata);
|
||||
audio.removeEventListener("timeupdate", handleTimeUpdate);
|
||||
audio.removeEventListener("ended", handleEnded);
|
||||
audio.removeEventListener("error", handleError);
|
||||
audio.removeEventListener("play", handlePlay);
|
||||
audio.removeEventListener("pause", handlePause);
|
||||
};
|
||||
}, [onError]);
|
||||
|
||||
const togglePlay = async () => {
|
||||
const audio = audioRef.current;
|
||||
if (!audio) return;
|
||||
|
||||
try {
|
||||
if (isPlaying) {
|
||||
audio.pause();
|
||||
} else {
|
||||
await audio.play();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Playback error:", e);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleMute = () => {
|
||||
if (!audioRef.current) return;
|
||||
audioRef.current.muted = !isMuted;
|
||||
setIsMuted(!isMuted);
|
||||
};
|
||||
|
||||
const handleSeek = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (!audioRef.current || !duration) return;
|
||||
const rect = e.currentTarget.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const percentage = x / rect.width;
|
||||
audioRef.current.currentTime = percentage * duration;
|
||||
};
|
||||
|
||||
const progress = duration ? (currentTime / duration) * 100 : 0;
|
||||
|
||||
return (
|
||||
<div className={cn("flex items-center gap-3 p-3 rounded-lg bg-muted/50", className)}>
|
||||
<audio ref={audioRef} src={src} preload="auto" />
|
||||
|
||||
{/* Play button */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={togglePlay}
|
||||
disabled={!isLoaded}
|
||||
className="h-9 w-9 shrink-0 rounded-full bg-primary text-primary-foreground flex items-center justify-center hover:bg-primary/90 transition-colors disabled:opacity-50"
|
||||
>
|
||||
{isPlaying ? (
|
||||
<Pause className="h-4 w-4" />
|
||||
) : (
|
||||
<Play className="h-4 w-4 ml-0.5" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* Waveform */}
|
||||
<div
|
||||
className="flex-1 min-w-0 h-8 flex items-center cursor-pointer"
|
||||
onClick={handleSeek}
|
||||
>
|
||||
<div className="flex-1 h-full flex items-center gap-[2px]">
|
||||
{animatedHeights.map((height, i) => {
|
||||
const barProgress = ((i + 1) / BARS) * 100;
|
||||
const isActive = barProgress <= progress;
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
className={cn(
|
||||
"flex-1 rounded-full transition-all duration-75",
|
||||
isActive ? "bg-primary" : "bg-muted-foreground/30"
|
||||
)}
|
||||
style={{ height: `${height * 100}%` }}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Countdown timer */}
|
||||
<span className="text-xs text-muted-foreground tabular-nums shrink-0">
|
||||
{formatTime(Math.max(0, duration - currentTime))}
|
||||
</span>
|
||||
|
||||
{/* Mute button */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleMute}
|
||||
className="h-7 w-7 shrink-0 rounded-full hover:bg-muted flex items-center justify-center transition-colors"
|
||||
>
|
||||
{isMuted ? (
|
||||
<VolumeX className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
) : (
|
||||
<Volume2 className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
import { useState, useEffect, useCallback, useRef } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Loader2, Wand2, Upload, ChevronDown, AlertCircle, CheckCircle2 } from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
@@ -45,14 +46,16 @@ import { getProviderWebSocketHandler } from "@/lib/plugins/media-generators";
|
||||
interface MediaGeneratorModel {
|
||||
id: string;
|
||||
name: string;
|
||||
type: "image" | "video";
|
||||
type: "image" | "video" | "audio";
|
||||
provider: string;
|
||||
providerName: string;
|
||||
providerLogo?: string;
|
||||
providerLogoDark?: string;
|
||||
}
|
||||
|
||||
interface MediaGeneratorProps {
|
||||
prompt: string;
|
||||
mediaType: "IMAGE" | "VIDEO";
|
||||
mediaType: "IMAGE" | "VIDEO" | "AUDIO";
|
||||
onMediaGenerated: (url: string) => void;
|
||||
onUploadClick: () => void;
|
||||
inputImageUrl?: string;
|
||||
@@ -105,7 +108,11 @@ export function MediaGenerator({
|
||||
const response = await fetch("/api/media-generate");
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
const relevantModels = mediaType === "IMAGE" ? data.imageModels : data.videoModels;
|
||||
const relevantModels = mediaType === "IMAGE"
|
||||
? data.imageModels
|
||||
: mediaType === "VIDEO"
|
||||
? data.videoModels
|
||||
: data.audioModels;
|
||||
setModels(relevantModels || []);
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -341,8 +348,36 @@ export function MediaGenerator({
|
||||
|
||||
{Object.entries(modelsByProvider).map(([provider, providerModels]) => (
|
||||
<div key={provider}>
|
||||
<DropdownMenuLabel className="text-xs text-muted-foreground">
|
||||
{providerModels[0]?.providerName || provider}
|
||||
<DropdownMenuLabel className="text-xs text-muted-foreground flex items-center justify-between">
|
||||
<span>{providerModels[0]?.providerName || provider}</span>
|
||||
{providerModels[0]?.providerLogo && (
|
||||
providerModels[0]?.providerLogoDark ? (
|
||||
<>
|
||||
<Image
|
||||
src={providerModels[0].providerLogo}
|
||||
alt={providerModels[0].providerName}
|
||||
width={36}
|
||||
height={12}
|
||||
className="h-3 w-auto dark:hidden"
|
||||
/>
|
||||
<Image
|
||||
src={providerModels[0].providerLogoDark}
|
||||
alt={providerModels[0].providerName}
|
||||
width={36}
|
||||
height={12}
|
||||
className="h-3 w-auto hidden dark:block"
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Image
|
||||
src={providerModels[0].providerLogo}
|
||||
alt={providerModels[0].providerName}
|
||||
width={36}
|
||||
height={12}
|
||||
className="h-3 w-auto dark:invert"
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</DropdownMenuLabel>
|
||||
{providerModels.map((model) => (
|
||||
<DropdownMenuItem
|
||||
@@ -407,8 +442,8 @@ export function MediaGenerator({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Aspect Ratio selector for image and video generation */}
|
||||
{selectedModel && (
|
||||
{/* Aspect Ratio selector for image and video generation (not for audio) */}
|
||||
{selectedModel && selectedModel.type !== "audio" && (
|
||||
<div className="text-sm">
|
||||
<span className="font-medium">{t("aspectRatio")}:</span>
|
||||
<Select value={aspectRatio} onValueChange={(v) => setAspectRatio(v as AspectRatio)}>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { ImageIcon, AlertTriangle } from "lucide-react";
|
||||
import { AudioPlayer } from "./audio-player";
|
||||
|
||||
interface MediaPreviewProps {
|
||||
mediaUrl: string;
|
||||
@@ -40,6 +41,11 @@ export function MediaPreview({ mediaUrl, title, type }: MediaPreviewProps) {
|
||||
className="w-full max-h-[500px] object-contain block"
|
||||
onError={() => setHasError(true)}
|
||||
/>
|
||||
) : type === "AUDIO" ? (
|
||||
<AudioPlayer
|
||||
src={mediaUrl}
|
||||
onError={() => setHasError(true)}
|
||||
/>
|
||||
) : (
|
||||
<a
|
||||
href={mediaUrl}
|
||||
|
||||
@@ -63,7 +63,8 @@ function MediaField({ form, t, promptType, promptContent }: MediaFieldProps) {
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const mediaUrl = form.watch("mediaUrl");
|
||||
const isVideoType = promptType === "VIDEO";
|
||||
const mediaType = isVideoType ? "VIDEO" : "IMAGE";
|
||||
const isAudioType = promptType === "AUDIO";
|
||||
const mediaType = isVideoType ? "VIDEO" : isAudioType ? "AUDIO" : "IMAGE";
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/api/config/storage")
|
||||
@@ -77,11 +78,11 @@ function MediaField({ form, t, promptType, promptContent }: MediaFieldProps) {
|
||||
fetch("/api/media-generate")
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
const models = isVideoType ? data.videoModels : data.imageModels;
|
||||
const models = isVideoType ? data.videoModels : isAudioType ? data.audioModels : data.imageModels;
|
||||
setHasGenerators(models && models.length > 0);
|
||||
})
|
||||
.catch(() => setHasGenerators(false));
|
||||
}, [isVideoType]);
|
||||
}, [isVideoType, isAudioType]);
|
||||
|
||||
const handleFileSelect = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
@@ -97,9 +98,10 @@ function MediaField({ form, t, promptType, promptContent }: MediaFieldProps) {
|
||||
// Validate file type
|
||||
const allowedImageTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"];
|
||||
const allowedVideoTypes = ["video/mp4"];
|
||||
const allowedTypes = isVideoType ? allowedVideoTypes : allowedImageTypes;
|
||||
const allowedAudioTypes = ["audio/mpeg", "audio/mp3", "audio/wav", "audio/ogg"];
|
||||
const allowedTypes = isVideoType ? allowedVideoTypes : isAudioType ? allowedAudioTypes : allowedImageTypes;
|
||||
if (!allowedTypes.includes(file.type)) {
|
||||
setUploadError(t(isVideoType ? "invalidVideoType" : "invalidFileType"));
|
||||
setUploadError(t(isVideoType ? "invalidVideoType" : isAudioType ? "invalidAudioType" : "invalidFileType"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -165,6 +167,8 @@ function MediaField({ form, t, promptType, promptContent }: MediaFieldProps) {
|
||||
<div className="relative inline-block">
|
||||
{isVideoType ? (
|
||||
<video src={mediaUrl} controls className="max-h-40 rounded-md border" />
|
||||
) : isAudioType ? (
|
||||
<audio src={mediaUrl} controls className="w-full max-w-md" />
|
||||
) : (
|
||||
<img src={mediaUrl} alt="Preview" className="max-h-40 rounded-md border" />
|
||||
)}
|
||||
@@ -188,11 +192,11 @@ function MediaField({ form, t, promptType, promptContent }: MediaFieldProps) {
|
||||
<span className="font-medium">{t("aiGenerationAvailable")}</span>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t(isVideoType ? "generateVideoDescription" : "generateImageDescription")}
|
||||
{t(isVideoType ? "generateVideoDescription" : isAudioType ? "generateAudioDescription" : "generateImageDescription")}
|
||||
</p>
|
||||
<MediaGenerator
|
||||
prompt={promptContent || ""}
|
||||
mediaType={mediaType as "IMAGE" | "VIDEO"}
|
||||
mediaType={mediaType as "IMAGE" | "VIDEO" | "AUDIO"}
|
||||
onMediaGenerated={handleMediaGenerated}
|
||||
onUploadClick={handleUploadClick}
|
||||
/>
|
||||
@@ -200,7 +204,7 @@ function MediaField({ form, t, promptType, promptContent }: MediaFieldProps) {
|
||||
) : (
|
||||
<MediaGenerator
|
||||
prompt={promptContent || ""}
|
||||
mediaType={mediaType as "IMAGE" | "VIDEO"}
|
||||
mediaType={mediaType as "IMAGE" | "VIDEO" | "AUDIO"}
|
||||
onMediaGenerated={handleMediaGenerated}
|
||||
onUploadClick={handleUploadClick}
|
||||
/>
|
||||
@@ -227,7 +231,7 @@ function MediaField({ form, t, promptType, promptContent }: MediaFieldProps) {
|
||||
name="mediaUrl"
|
||||
render={() => (
|
||||
<FormItem>
|
||||
<FormLabel>{t(isVideoType ? "mediaVideo" : "mediaImage")}</FormLabel>
|
||||
<FormLabel>{t(isVideoType ? "mediaVideo" : isAudioType ? "mediaAudio" : "mediaImage")}</FormLabel>
|
||||
<FormControl>
|
||||
<div className="space-y-3">
|
||||
{mediaUrl ? (
|
||||
@@ -238,6 +242,12 @@ function MediaField({ form, t, promptType, promptContent }: MediaFieldProps) {
|
||||
controls
|
||||
className="max-h-40 rounded-md border"
|
||||
/>
|
||||
) : isAudioType ? (
|
||||
<audio
|
||||
src={mediaUrl}
|
||||
controls
|
||||
className="w-full max-w-md"
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
src={mediaUrl}
|
||||
@@ -264,11 +274,11 @@ function MediaField({ form, t, promptType, promptContent }: MediaFieldProps) {
|
||||
<span className="font-medium">{t("aiGenerationAvailable")}</span>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t(isVideoType ? "generateVideoDescription" : "generateImageDescription")}
|
||||
{t(isVideoType ? "generateVideoDescription" : isAudioType ? "generateAudioDescription" : "generateImageDescription")}
|
||||
</p>
|
||||
<MediaGenerator
|
||||
prompt={promptContent || ""}
|
||||
mediaType={mediaType as "IMAGE" | "VIDEO"}
|
||||
mediaType={mediaType as "IMAGE" | "VIDEO" | "AUDIO"}
|
||||
onMediaGenerated={handleMediaGenerated}
|
||||
onUploadClick={handleUploadClick}
|
||||
/>
|
||||
@@ -276,7 +286,7 @@ function MediaField({ form, t, promptType, promptContent }: MediaFieldProps) {
|
||||
) : (
|
||||
<MediaGenerator
|
||||
prompt={promptContent || ""}
|
||||
mediaType={mediaType as "IMAGE" | "VIDEO"}
|
||||
mediaType={mediaType as "IMAGE" | "VIDEO" | "AUDIO"}
|
||||
onMediaGenerated={handleMediaGenerated}
|
||||
onUploadClick={handleUploadClick}
|
||||
/>
|
||||
@@ -292,10 +302,10 @@ function MediaField({ form, t, promptType, promptContent }: MediaFieldProps) {
|
||||
<Upload className="h-8 w-8 text-muted-foreground" />
|
||||
)}
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{isUploading ? t("uploading") : t(isVideoType ? "clickToUploadVideo" : "clickToUpload")}
|
||||
{isUploading ? t("uploading") : t(isVideoType ? "clickToUploadVideo" : isAudioType ? "clickToUploadAudio" : "clickToUpload")}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t(isVideoType ? "maxVideoSize" : "maxFileSize")}
|
||||
{t(isVideoType ? "maxVideoSize" : isAudioType ? "maxAudioSize" : "maxFileSize")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -304,7 +314,7 @@ function MediaField({ form, t, promptType, promptContent }: MediaFieldProps) {
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept={isVideoType ? "video/mp4" : "image/jpeg,image/png,image/gif,image/webp"}
|
||||
accept={isVideoType ? "video/mp4" : isAudioType ? "audio/mpeg,audio/mp3,audio/wav,audio/ogg" : "image/jpeg,image/png,image/gif,image/webp"}
|
||||
className="hidden"
|
||||
onChange={handleFileSelect}
|
||||
disabled={isUploading}
|
||||
@@ -1093,6 +1103,10 @@ export function PromptForm({ categories, tags, initialData, initialContributors
|
||||
<Video className="h-4 w-4 mr-2" />
|
||||
{t("generateVideo")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => form.setValue("type", "AUDIO")}>
|
||||
<Volume2 className="h-4 w-4 mr-2" />
|
||||
{t("generateAudio")}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
@@ -1156,21 +1170,12 @@ export function PromptForm({ categories, tags, initialData, initialContributors
|
||||
</div>
|
||||
)}
|
||||
{promptType === "AUDIO" && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2 text-sm font-medium">
|
||||
<Volume2 className="h-4 w-4" />
|
||||
<span>{t("outputPreview.audio")}</span>
|
||||
</div>
|
||||
{/* Fake audio player bar */}
|
||||
<div className="flex items-center gap-3 p-3 rounded-md bg-muted/50">
|
||||
<button type="button" className="h-8 w-8 rounded-full bg-muted flex items-center justify-center cursor-not-allowed" disabled>
|
||||
<Play className="h-4 w-4 text-muted-foreground/50" />
|
||||
</button>
|
||||
<div className="flex-1 h-1.5 bg-muted-foreground/20 rounded-full">
|
||||
<div className="h-full w-0 bg-muted-foreground/30 rounded-full" />
|
||||
</div>
|
||||
<span className="text-xs text-muted-foreground/50">0:00</span>
|
||||
<span>{t("outputPreview.audioUpload")}</span>
|
||||
</div>
|
||||
<MediaField form={form} t={t} promptType={promptType} promptContent={promptContent} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
* - FAL_API_KEY
|
||||
* - FAL_VIDEO_MODELS (comma-separated, e.g., "fal-ai/veo3,fal-ai/kling-video/v2/master/image-to-video")
|
||||
* - FAL_IMAGE_MODELS (comma-separated, e.g., "fal-ai/flux-pro/v1.1-ultra,fal-ai/flux/dev")
|
||||
* - FAL_AUDIO_MODELS (comma-separated, e.g., "fal-ai/stable-audio")
|
||||
*/
|
||||
|
||||
import type {
|
||||
@@ -23,7 +24,7 @@ import type {
|
||||
|
||||
const FAL_QUEUE_BASE = "https://queue.fal.run";
|
||||
|
||||
function parseModels(envVar: string | undefined, type: "image" | "video"): MediaGeneratorModel[] {
|
||||
function parseModels(envVar: string | undefined, type: "image" | "video" | "audio"): MediaGeneratorModel[] {
|
||||
if (!envVar) return [];
|
||||
return envVar
|
||||
.split(",")
|
||||
@@ -71,6 +72,11 @@ export interface FalVideoOutput {
|
||||
videos?: Array<{ url: string }>;
|
||||
}
|
||||
|
||||
export interface FalAudioOutput {
|
||||
audio_file?: { url: string };
|
||||
audio?: { url: string };
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit a generation request to Fal.ai queue
|
||||
*/
|
||||
@@ -129,7 +135,7 @@ export async function getFalRequestStatus(
|
||||
*/
|
||||
export async function getFalRequestResult(
|
||||
responseUrl: string
|
||||
): Promise<FalImageOutput | FalVideoOutput> {
|
||||
): Promise<FalImageOutput | FalVideoOutput | FalAudioOutput> {
|
||||
const apiKey = process.env.FAL_API_KEY;
|
||||
if (!apiKey) throw new Error("FAL_API_KEY is not configured");
|
||||
|
||||
@@ -184,11 +190,13 @@ const falWebSocketHandler: WebSocketHandler = {
|
||||
export const falGeneratorPlugin: MediaGeneratorPlugin = {
|
||||
id: "fal",
|
||||
name: "Fal.ai",
|
||||
logo: "/sponsors/fal.svg",
|
||||
logoDark: "/sponsors/fal-dark.svg",
|
||||
|
||||
isConfigured: () => {
|
||||
return !!(
|
||||
process.env.FAL_API_KEY &&
|
||||
(process.env.FAL_VIDEO_MODELS || process.env.FAL_IMAGE_MODELS)
|
||||
(process.env.FAL_VIDEO_MODELS || process.env.FAL_IMAGE_MODELS || process.env.FAL_AUDIO_MODELS)
|
||||
);
|
||||
},
|
||||
|
||||
@@ -202,7 +210,8 @@ export const falGeneratorPlugin: MediaGeneratorPlugin = {
|
||||
}
|
||||
const imageModels = parseModels(process.env.FAL_IMAGE_MODELS, "image");
|
||||
const videoModels = parseModels(process.env.FAL_VIDEO_MODELS, "video");
|
||||
return [...imageModels, ...videoModels];
|
||||
const audioModels = parseModels(process.env.FAL_AUDIO_MODELS, "audio");
|
||||
return [...imageModels, ...videoModels, ...audioModels];
|
||||
},
|
||||
|
||||
async startGeneration(request: GenerationRequest): Promise<GenerationTask> {
|
||||
@@ -224,6 +233,10 @@ export const falGeneratorPlugin: MediaGeneratorPlugin = {
|
||||
if (request.inputImageUrl) {
|
||||
input.image_url = request.inputImageUrl;
|
||||
}
|
||||
} else if (request.type === "audio") {
|
||||
// Audio generation parameters
|
||||
input.duration_seconds = 30;
|
||||
input.duration = 30;
|
||||
} else {
|
||||
// Image generation parameters
|
||||
input.image_size = mapAspectRatioToImageSize(request.aspectRatio);
|
||||
@@ -300,7 +313,7 @@ export const falGeneratorPlugin: MediaGeneratorPlugin = {
|
||||
/**
|
||||
* Extract output URLs from Fal.ai result
|
||||
*/
|
||||
function extractOutputUrls(result: FalImageOutput | FalVideoOutput): string[] {
|
||||
function extractOutputUrls(result: FalImageOutput | FalVideoOutput | FalAudioOutput): string[] {
|
||||
const urls: string[] = [];
|
||||
|
||||
// Image outputs
|
||||
@@ -319,5 +332,13 @@ function extractOutputUrls(result: FalImageOutput | FalVideoOutput): string[] {
|
||||
urls.push(result.video.url);
|
||||
}
|
||||
|
||||
// Audio outputs
|
||||
if ("audio_file" in result && result.audio_file) {
|
||||
urls.push(result.audio_file.url);
|
||||
}
|
||||
if ("audio" in result && result.audio) {
|
||||
urls.push(result.audio.url);
|
||||
}
|
||||
|
||||
return urls;
|
||||
}
|
||||
|
||||
@@ -51,10 +51,10 @@ export function getEnabledMediaGeneratorPlugins(): MediaGeneratorPlugin[] {
|
||||
/**
|
||||
* Get all available models from enabled generators
|
||||
*/
|
||||
export function getAvailableModels(type?: MediaType): Array<MediaGeneratorModel & { provider: string; providerName: string }> {
|
||||
export function getAvailableModels(type?: MediaType): Array<MediaGeneratorModel & { provider: string; providerName: string; providerLogo?: string; providerLogoDark?: string }> {
|
||||
initializeMediaGenerators();
|
||||
|
||||
const models: Array<MediaGeneratorModel & { provider: string; providerName: string }> = [];
|
||||
const models: Array<MediaGeneratorModel & { provider: string; providerName: string; providerLogo?: string; providerLogoDark?: string }> = [];
|
||||
|
||||
for (const plugin of getEnabledMediaGeneratorPlugins()) {
|
||||
const pluginModels = plugin.getModels();
|
||||
@@ -64,6 +64,8 @@ export function getAvailableModels(type?: MediaType): Array<MediaGeneratorModel
|
||||
...model,
|
||||
provider: plugin.id,
|
||||
providerName: plugin.name,
|
||||
providerLogo: plugin.logo,
|
||||
providerLogoDark: plugin.logoDark,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* Interface definitions for AI-powered media generation plugins.
|
||||
*/
|
||||
|
||||
export type MediaType = "image" | "video";
|
||||
export type MediaType = "image" | "video" | "audio";
|
||||
|
||||
export interface MediaGeneratorModel {
|
||||
id: string;
|
||||
@@ -92,6 +92,8 @@ export interface WebSocketHandler {
|
||||
export interface MediaGeneratorPlugin {
|
||||
id: string;
|
||||
name: string;
|
||||
logo?: string;
|
||||
logoDark?: string;
|
||||
/**
|
||||
* Check if the plugin is properly configured
|
||||
*/
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* - WIRO_API_KEY
|
||||
* - WIRO_VIDEO_MODELS (comma-separated, e.g., "google/veo3.1-fast")
|
||||
* - WIRO_IMAGE_MODELS (comma-separated, e.g., "google/nano-banana-pro,google/nano-banana")
|
||||
* - WIRO_AUDIO_MODELS (comma-separated, e.g., "elevenlabs/sound-effects")
|
||||
*/
|
||||
|
||||
import type {
|
||||
@@ -22,7 +23,7 @@ import type {
|
||||
const WIRO_API_BASE = "https://api.wiro.ai/v1";
|
||||
const WIRO_SOCKET_URL = "wss://socket.wiro.ai/v1";
|
||||
|
||||
function parseModels(envVar: string | undefined, type: "image" | "video"): MediaGeneratorModel[] {
|
||||
function parseModels(envVar: string | undefined, type: "image" | "video" | "audio"): MediaGeneratorModel[] {
|
||||
if (!envVar) return [];
|
||||
return envVar
|
||||
.split(",")
|
||||
@@ -128,11 +129,13 @@ const wiroWebSocketHandler: WebSocketHandler = {
|
||||
export const wiroGeneratorPlugin: MediaGeneratorPlugin = {
|
||||
id: "wiro",
|
||||
name: "Wiro.ai",
|
||||
logo: "/sponsors/wiro.png",
|
||||
logoDark: "/sponsors/wiro.png",
|
||||
|
||||
isConfigured: () => {
|
||||
return !!(
|
||||
process.env.WIRO_API_KEY &&
|
||||
(process.env.WIRO_VIDEO_MODELS || process.env.WIRO_IMAGE_MODELS)
|
||||
(process.env.WIRO_VIDEO_MODELS || process.env.WIRO_IMAGE_MODELS || process.env.WIRO_AUDIO_MODELS)
|
||||
);
|
||||
},
|
||||
|
||||
@@ -143,7 +146,8 @@ export const wiroGeneratorPlugin: MediaGeneratorPlugin = {
|
||||
getModels: () => {
|
||||
const imageModels = parseModels(process.env.WIRO_IMAGE_MODELS, "image");
|
||||
const videoModels = parseModels(process.env.WIRO_VIDEO_MODELS, "video");
|
||||
return [...imageModels, ...videoModels];
|
||||
const audioModels = parseModels(process.env.WIRO_AUDIO_MODELS, "audio");
|
||||
return [...imageModels, ...videoModels, ...audioModels];
|
||||
},
|
||||
|
||||
async startGeneration(request: GenerationRequest): Promise<GenerationTask> {
|
||||
@@ -169,6 +173,9 @@ export const wiroGeneratorPlugin: MediaGeneratorPlugin = {
|
||||
if (request.aspectRatio) {
|
||||
formData.append("aspectRatio", request.aspectRatio);
|
||||
}
|
||||
} else if (request.type === "audio") {
|
||||
// Audio-specific parameters
|
||||
formData.append("durationSeconds", "30");
|
||||
} else {
|
||||
// Image-specific parameters
|
||||
formData.append("resolution", request.resolution || "1K");
|
||||
|
||||
Reference in New Issue
Block a user