Commit 45311bfa authored by Anuraag Jain's avatar Anuraag Jain
Browse files

Merge branch 'main' into feat/cancel-model-download

# Conflicts:
#	src/lib/components/chat/Settings/Models.svelte
parents ae97a963 2fa94956
{
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' ou '-1' pour aucune expiration.",
"(Beta)": "(Bêta)",
"(e.g. `sh webui.sh --api`)": "(par ex. `sh webui.sh --api`)",
"(latest)": "",
"{{modelName}} is thinking...": "{{modelName}} réfléchit...",
"{{webUIName}} Backend Required": "Backend {{webUIName}} requis",
"a user": "un utilisateur",
"About": "À propos",
"Account": "Compte",
"Action": "Action",
"Add a model": "Ajouter un modèle",
"Add a model tag name": "Ajouter un nom de tag pour le modèle",
"Add a short description about what this modelfile does": "Ajouter une courte description de ce que fait ce fichier de modèle",
"Add a short title for this prompt": "Ajouter un court titre pour ce prompt",
"Add a tag": "Ajouter un tag",
"Add Docs": "Ajouter des documents",
"Add Files": "Ajouter des fichiers",
"Add message": "Ajouter un message",
"add tags": "ajouter des tags",
"Adjusting these settings will apply changes universally to all users.": "L'ajustement de ces paramètres appliquera les changements à tous les utilisateurs.",
"admin": "Administrateur",
"Admin Panel": "Panneau d'administration",
"Admin Settings": "Paramètres d'administration",
"Advanced Parameters": "Paramètres avancés",
"all": "tous",
"All Users": "Tous les utilisateurs",
"Allow": "Autoriser",
"Allow Chat Deletion": "Autoriser la suppression du chat",
"alphanumeric characters and hyphens": "caractères alphanumériques et tirets",
"Already have an account?": "Vous avez déjà un compte ?",
"an assistant": "un assistant",
"and": "et",
"API Base URL": "URL de base de l'API",
"API Key": "Clé API",
"API RPM": "RPM API",
"are allowed - Activate this command by typing": "sont autorisés - Activez cette commande en tapant",
"Are you sure?": "Êtes-vous sûr ?",
"Audio": "Audio",
"Auto-playback response": "Réponse en lecture automatique",
"Auto-send input after 3 sec.": "Envoyer automatiquement l'entrée après 3 sec.",
"AUTOMATIC1111 Base URL": "URL de base AUTOMATIC1111",
"AUTOMATIC1111 Base URL is required.": "L'URL de base AUTOMATIC1111 est requise.",
"available!": "disponible !",
"Back": "Retour",
"Builder Mode": "Mode Constructeur",
"Cancel": "Annuler",
"Categories": "Catégories",
"Change Password": "Changer le mot de passe",
"Chat": "Chat",
"Chat History": "Historique du chat",
"Chat History is off for this browser.": "L'historique du chat est désactivé pour ce navigateur.",
"Chats": "Chats",
"Check Again": "Vérifier à nouveau",
"Check for updates": "Vérifier les mises à jour",
"Checking for updates...": "Vérification des mises à jour...",
"Choose a model before saving...": "Choisissez un modèle avant d'enregistrer...",
"Chunk Overlap": "Chevauchement de bloc",
"Chunk Params": "Paramètres de bloc",
"Chunk Size": "Taille de bloc",
"Click here for help.": "Cliquez ici pour de l'aide.",
"Click here to check other modelfiles.": "Cliquez ici pour vérifier d'autres fichiers de modèle.",
"Click here to select": "Cliquez ici pour sélectionner",
"Click here to select documents.": "Cliquez ici pour sélectionner des documents.",
"click here.": "cliquez ici.",
"Click on the user role button to change a user's role.": "Cliquez sur le bouton de rôle d'utilisateur pour changer le rôle d'un utilisateur.",
"Close": "Fermer",
"Collection": "Collection",
"Command": "Commande",
"Confirm Password": "Confirmer le mot de passe",
"Connections": "Connexions",
"Content": "Contenu",
"Context Length": "Longueur du contexte",
"Conversation Mode": "Mode de conversation",
"Copy last code block": "Copier le dernier bloc de code",
"Copy last response": "Copier la dernière réponse",
"Copying to clipboard was successful!": "La copie dans le presse-papiers a réussi !",
"Create a concise, 3-5 word phrase as a header for the following query, strictly adhering to the 3-5 word limit and avoiding the use of the word 'title':": "Créez une phrase concise de 3-5 mots comme en-tête pour la requête suivante, en respectant strictement la limite de 3-5 mots et en évitant l'utilisation du mot 'titre' :",
"Create a modelfile": "Créer un fichier de modèle",
"Create Account": "Créer un compte",
"Created at": "Créé le",
"Created by": "Créé par",
"Current Model": "Modèle actuel",
"Current Password": "Mot de passe actuel",
"Custom": "Personnalisé",
"Customize Ollama models for a specific purpose": "Personnaliser les modèles Ollama pour un objectif spécifique",
"Dark": "Sombre",
"Database": "Base de données",
"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
"Default": "Par défaut",
"Default (Automatic1111)": "Par défaut (Automatic1111)",
"Default (Web API)": "Par défaut (API Web)",
"Default model updated": "Modèle par défaut mis à jour",
"Default Prompt Suggestions": "Suggestions de prompt par défaut",
"Default User Role": "Rôle d'utilisateur par défaut",
"delete": "supprimer",
"Delete a model": "Supprimer un modèle",
"Delete chat": "Supprimer le chat",
"Delete Chats": "Supprimer les chats",
"Deleted {{deleteModelTag}}": "{{deleteModelTag}} supprimé",
"Deleted {tagName}": "{tagName} supprimé",
"Description": "Description",
"Desktop Notifications": "Notifications de bureau",
"Disabled": "Désactivé",
"Discover a modelfile": "Découvrir un fichier de modèle",
"Discover a prompt": "Découvrir un prompt",
"Discover, download, and explore custom prompts": "Découvrir, télécharger et explorer des prompts personnalisés",
"Discover, download, and explore model presets": "Découvrir, télécharger et explorer des préconfigurations de modèles",
"Display the username instead of You in the Chat": "Afficher le nom d'utilisateur au lieu de 'Vous' dans le Chat",
"Document": "Document",
"Document Settings": "Paramètres du document",
"Documents": "Documents",
"does not make any external connections, and your data stays securely on your locally hosted server.": "ne fait aucune connexion externe, et vos données restent en sécurité sur votre serveur hébergé localement.",
"Don't Allow": "Ne pas autoriser",
"Don't have an account?": "Vous n'avez pas de compte ?",
"Download as a File": "Télécharger en tant que fichier",
"Download Database": "Télécharger la base de données",
"Drop any files here to add to the conversation": "Déposez des fichiers ici pour les ajouter à la conversation",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "par ex. '30s', '10m'. Les unités de temps valides sont 's', 'm', 'h'.",
"Edit Doc": "Éditer le document",
"Edit User": "Éditer l'utilisateur",
"Email": "Email",
"Enable Chat History": "Activer l'historique du chat",
"Enable New Sign Ups": "Activer les nouvelles inscriptions",
"Enabled": "Activé",
"Enter {{role}} message here": "Entrez le message {{role}} ici",
"Enter API Key": "Entrez la clé API",
"Enter Chunk Overlap": "Entrez le chevauchement de bloc",
"Enter Chunk Size": "Entrez la taille du bloc",
"Enter Image Size (e.g. 512x512)": "Entrez la taille de l'image (p. ex. 512x512)",
"Enter LiteLLM API Base URL (litellm_params.api_base)": "Entrez l'URL de base de l'API LiteLLM (litellm_params.api_base)",
"Enter LiteLLM API Key (litellm_params.api_key)": "Entrez la clé API LiteLLM (litellm_params.api_key)",
"Enter LiteLLM API RPM (litellm_params.rpm)": "Entrez le RPM de l'API LiteLLM (litellm_params.rpm)",
"Enter LiteLLM Model (litellm_params.model)": "Entrez le modèle LiteLLM (litellm_params.model)",
"Enter Max Tokens (litellm_params.max_tokens)": "Entrez le nombre max de tokens (litellm_params.max_tokens)",
"Enter model tag (e.g. {{modelTag}})": "Entrez le tag du modèle (p. ex. {{modelTag}})",
"Enter Number of Steps (e.g. 50)": "Entrez le nombre d'étapes (p. ex. 50)",
"Enter stop sequence": "Entrez la séquence de fin",
"Enter Top K": "Entrez Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Entrez l'URL (p. ex. http://127.0.0.1:7860/)",
"Enter Your Email": "Entrez votre email",
"Enter Your Full Name": "Entrez votre nom complet",
"Enter Your Password": "Entrez votre mot de passe",
"Experimental": "Expérimental",
"Export All Chats (All Users)": "Exporter tous les chats (tous les utilisateurs)",
"Export Chats": "Exporter les chats",
"Export Documents Mapping": "Exporter la correspondance des documents",
"Export Modelfiles": "Exporter les fichiers de modèle",
"Export Prompts": "Exporter les prompts",
"Failed to read clipboard contents": "Échec de la lecture du contenu du presse-papiers",
"File Mode": "Mode fichier",
"File not found.": "Fichier non trouvé.",
"Focus chat input": "Concentrer sur l'entrée du chat",
"Format your variables using square brackets like this:": "Formatez vos variables en utilisant des crochets comme ceci :",
"From (Base Model)": "De (Modèle de base)",
"Full Screen Mode": "Mode plein écran",
"General": "Général",
"General Settings": "Paramètres généraux",
"Hello, {{name}}": "Bonjour, {{name}}",
"Hide": "Cacher",
"Hide Additional Params": "Hide additional params",
"How can I help you today?": "Comment puis-je vous aider aujourd'hui ?",
"Image Generation (Experimental)": "Génération d'image (Expérimental)",
"Image Generation Engine": "Moteur de génération d'image",
"Image Settings": "Paramètres d'image",
"Images": "Images",
"Import Chats": "Importer les chats",
"Import Documents Mapping": "Importer la correspondance des documents",
"Import Modelfiles": "Importer les fichiers de modèle",
"Import Prompts": "Importer les prompts",
"Include `--api` flag when running stable-diffusion-webui": "Inclure le drapeau `--api` lors de l'exécution de stable-diffusion-webui",
"Interface": "Interface",
"join our Discord for help.": "rejoignez notre Discord pour obtenir de l'aide.",
"JSON": "JSON",
"JWT Expiration": "Expiration JWT",
"JWT Token": "Jeton JWT",
"Keep Alive": "Garder en vie",
"Keyboard shortcuts": "Raccourcis clavier",
"Language": "Langue",
"Light": "Clair",
"Listening...": "Écoute...",
"LLMs can make mistakes. Verify important information.": "Les LLMs peuvent faire des erreurs. Vérifiez les informations importantes.",
"Made by OpenWebUI Community": "Réalisé par la communauté OpenWebUI",
"Make sure to enclose them with": "Assurez-vous de les entourer avec",
"Manage LiteLLM Models": "Gérer les modèles LiteLLM",
"Manage Models": "Gérer les modèles",
"Manage Ollama Models": "Gérer les modèles Ollama",
"Max Tokens": "Tokens maximaux",
"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Un maximum de 3 modèles peut être téléchargé simultanément. Veuillez réessayer plus tard.",
"Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"MMMM DD, YYYY": "MMMM DD, YYYY",
"Model '{{modelName}}' has been successfully downloaded.": "Le modèle '{{modelName}}' a été téléchargé avec succès.",
"Model '{{modelTag}}' is already in queue for downloading.": "Le modèle '{{modelTag}}' est déjà dans la file d'attente pour le téléchargement.",
"Model {{modelId}} not found": "Modèle {{modelId}} non trouvé",
"Model {{modelName}} already exists.": "Le modèle {{modelName}} existe déjà.",
"Model Name": "Nom du modèle",
"Model not selected": "Modèle non sélectionné",
"Model Tag Name": "Nom de tag du modèle",
"Model Whitelisting": "Liste blanche de modèle",
"Model(s) Whitelisted": "Modèle(s) sur liste blanche",
"Modelfile": "Fichier de modèle",
"Modelfile Advanced Settings": "Paramètres avancés du fichier de modèle",
"Modelfile Content": "Contenu du fichier de modèle",
"Modelfiles": "Fichiers de modèle",
"Models": "Modèles",
"My Documents": "Mes documents",
"My Modelfiles": "Mes fichiers de modèle",
"My Prompts": "Mes prompts",
"Name": "Nom",
"Name Tag": "Tag de nom",
"Name your modelfile": "Nommez votre fichier de modèle",
"New Chat": "Nouveau chat",
"New Password": "Nouveau mot de passe",
"Not sure what to add?": "Vous ne savez pas quoi ajouter ?",
"Not sure what to write? Switch to": "Vous ne savez pas quoi écrire ? Basculer vers",
"Off": "Désactivé",
"Okay, Let's Go!": "D'accord, allons-y !",
"Ollama Base URL": "URL de Base Ollama",
"Ollama Version": "Version Ollama",
"On": "Activé",
"Only": "Seulement",
"Only alphanumeric characters and hyphens are allowed in the command string.": "Seuls les caractères alphanumériques et les tirets sont autorisés dans la chaîne de commande.",
"Oops! Hold tight! Your files are still in the processing oven. We're cooking them up to perfection. Please be patient and we'll let you know once they're ready.": "Oups ! Tenez bon ! Vos fichiers sont encore dans le four. Nous les cuisinons à la perfection. Soyez patient et nous vous informerons dès qu'ils seront prêts.",
"Oops! Looks like the URL is invalid. Please double-check and try again.": "Oops! Looks like the URL is invalid. Please double-check and try again.",
"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.",
"Open": "Ouvrir",
"Open AI": "Open AI",
"Open AI (Dall-E)": "Open AI (Dall-E)",
"Open new chat": "Ouvrir un nouveau chat",
"OpenAI API": "API OpenAI",
"OpenAI API Key": "Clé API OpenAI",
"OpenAI API Key is required.": "La clé API OpenAI est requise.",
"or": "ou",
"Parameters": "Paramètres",
"Password": "Mot de passe",
"PDF Extract Images (OCR)": "Extraction d'images PDF (OCR)",
"pending": "en attente",
"Permission denied when accessing microphone: {{error}}": "Permission refusée lors de l'accès au microphone : {{error}}",
"Playground": "Aire de jeu",
"Profile": "Profil",
"Prompt Content": "Contenu du prompt",
"Prompt suggestions": "Suggestions de prompt",
"Prompts": "Prompts",
"Pull a model from Ollama.com": "Tirer un modèle de Ollama.com",
"Pull Progress": "Progression du tirage",
"Query Params": "Paramètres de requête",
"RAG Template": "Modèle RAG",
"Raw Format": "Format brut",
"Record voice": "Enregistrer la voix",
"Redirecting you to OpenWebUI Community": "Vous redirige vers la communauté OpenWebUI",
"Release Notes": "Notes de version",
"Repeat Last N": "Répéter les derniers N",
"Repeat Penalty": "Pénalité de répétition",
"Request Mode": "Mode de demande",
"Reset Vector Storage": "Réinitialiser le stockage de vecteur",
"Response AutoCopy to Clipboard": "Copie automatique de la réponse dans le presse-papiers",
"Role": "Rôle",
"Rosé Pine": "Pin Rosé",
"Rosé Pine Dawn": "Aube Pin Rosé",
"Save": "Enregistrer",
"Save & Create": "Enregistrer & Créer",
"Save & Submit": "Enregistrer & Soumettre",
"Save & Update": "Enregistrer & Mettre à jour",
"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "La sauvegarde des chat directement dans le stockage de votre navigateur n'est plus prise en charge. Veuillez prendre un moment pour télécharger et supprimer vos journaux de chat en cliquant sur le bouton ci-dessous. Ne vous inquiétez pas, vous pouvez facilement importer vos sauvegardes de chat via",
"Scan": "Scanner",
"Scan complete!": "Scan terminé !",
"Scan for documents from {{path}}": "Scanner des documents depuis {{path}}",
"Search": "Recherche",
"Search Documents": "Rechercher des documents",
"Search Prompts": "Rechercher des prompts",
"See readme.md for instructions": "Voir readme.md pour les instructions",
"See what's new": "Voir les nouveautés",
"Seed": "Graine",
"Select a mode": "Sélectionnez un mode",
"Select a model": "Sélectionner un modèle",
"Select an Ollama instance": "Sélectionner une instance Ollama",
"Send a Message": "Envoyer un message",
"Send message": "Envoyer un message",
"Server connection verified": "Connexion au serveur vérifiée",
"Set as default": "Définir par défaut",
"Set Default Model": "Définir le modèle par défaut",
"Set Image Size": "Définir la taille de l'image",
"Set Steps": "Définir les étapes",
"Set Title Auto-Generation Model": "Définir le modèle de génération automatique de titre",
"Set Voice": "Définir la voix",
"Settings": "Paramètres",
"Settings saved successfully!": "Paramètres enregistrés avec succès !",
"Share to OpenWebUI Community": "Partager avec la communauté OpenWebUI",
"short-summary": "résumé court",
"Show": "Montrer",
"Show Additional Params": "Afficher les paramètres supplémentaires",
"Show shortcuts": "Afficher les raccourcis",
"sidebar": "barre latérale",
"Sign in": "Se connecter",
"Sign Out": "Se déconnecter",
"Sign up": "S'inscrire",
"Speech recognition error: {{error}}": "Erreur de reconnaissance vocale : {{error}}",
"Speech-to-Text Engine": "Moteur de reconnaissance vocale",
"SpeechRecognition API is not supported in this browser.": "L'API SpeechRecognition n'est pas prise en charge dans ce navigateur.",
"Stop Sequence": "Séquence d'arrêt",
"STT Settings": "Paramètres STT",
"Submit": "Soumettre",
"Success": "Succès",
"Successfully updated.": "Mis à jour avec succès.",
"Sync All": "Synchroniser tout",
"System": "Système",
"System Prompt": "Invite de système",
"Tags": "Tags",
"Temperature": "Température",
"Template": "Modèle",
"Text Completion": "Complétion de texte",
"Text-to-Speech Engine": "Moteur de synthèse vocale",
"Tfs Z": "Tfs Z",
"Theme": "Thème",
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Cela garantit que vos précieuses conversations sont en sécurité dans votre base de données. Merci !",
"This setting does not sync across browsers or devices.": "Ce paramètre ne se synchronise pas entre les navigateurs ou les appareils.",
"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.",
"Title": "Titre",
"Title Auto-Generation": "Génération automatique de titre",
"Title Generation Prompt": "Prompt de génération de titre",
"to": "à",
"To access the available model names for downloading,": "Pour accéder aux noms de modèles disponibles pour le téléchargement,",
"To access the GGUF models available for downloading,": "Pour accéder aux modèles GGUF disponibles pour le téléchargement,",
"to chat input.": "à l'entrée du chat.",
"Toggle settings": "Basculer les paramètres",
"Toggle sidebar": "Basculer la barre latérale",
"Top K": "Top K",
"Top P": "Top P",
"Trouble accessing Ollama?": "Problèmes d'accès à Ollama ?",
"TTS Settings": "Paramètres TTS",
"Type Hugging Face Resolve (Download) URL": "Entrez l'URL de résolution (téléchargement) Hugging Face",
"Uh-oh! There was an issue connecting to {{provider}}.": "Uh-oh ! Il y a eu un problème de connexion à {{provider}}.",
"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "Type de fichier inconnu '{{file_type}}', mais accepté et traité comme du texte brut",
"Update password": "Mettre à jour le mot de passe",
"Upload a GGUF model": "Téléverser un modèle GGUF",
"Upload files": "Téléverser des fichiers",
"Upload Progress": "Progression du Téléversement",
"URL Mode": "Mode URL",
"Use '#' in the prompt input to load and select your documents.": "Utilisez '#' dans l'entrée du prompt pour charger et sélectionner vos documents.",
"Use Gravatar": "Utiliser Gravatar",
"user": "utilisateur",
"User Permissions": "Permissions d'utilisateur",
"Users": "Utilisateurs",
"Utilize": "Utiliser",
"Valid time units:": "Unités de temps valides :",
"variable": "variable",
"variable to have them replaced with clipboard content.": "variable pour les remplacer par le contenu du presse-papiers.",
"Version": "Version",
"Web": "Web",
"WebUI Add-ons": "Add-ons WebUI",
"WebUI Settings": "Paramètres WebUI",
"WebUI will make requests to": "WebUI effectuera des demandes à",
"What’s New in": "Quoi de neuf dans",
"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Lorsque l'historique est désactivé, les nouveaux chats sur ce navigateur n'apparaîtront pas dans votre historique sur aucun de vos appareils.",
"Whisper (Local)": "Whisper (Local)",
"Write a prompt suggestion (e.g. Who are you?)": "Écrivez un prompt (e.x. Qui est-tu ?)",
"Write a summary in 50 words that summarizes [topic or keyword].": "Ecrivez un résumé en 50 mots [sujet ou mot-clé]",
"You": "You",
"You're a helpful assistant.": "Vous êtes un assistant utile",
"You're now logged in.": "Vous êtes maintenant connecté."
}
[
{
"code": "en-US",
"title": "English (US)"
},
{
"code": "ca-ES",
"title": "Catalan"
},
{
"code": "de-DE",
"title": "Deutsch"
},
{
"code": "es-ES",
"title": "Spanish"
},
{
"code": "fa-IR",
"title": "فارسی (Farsi)"
},
{
"code": "fr-CA",
"title": "French (Canada)"
},
{
"code": "fr-FR",
"title": "French (France)"
},
{
"code": "ru-RU",
"title": "Russian (Russia)"
},
{
"code": "uk-UA",
"title": "Ukrainian"
},
{
"code": "vi-VN",
"title": "Tiếng Việt"
},
{
"code": "zh-CN",
"title": "Chinese (Simplified)"
},
{
"code": "zh-TW",
"title": "Chinese (Traditional)"
}
]
\ No newline at end of file
{
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' или '-1' для не истечение.",
"(Beta)": "(бета)",
"(e.g. `sh webui.sh --api`)": "(например: `sh webui.sh --api`)",
"(latest)": "(новый)",
"{{modelName}} is thinking...": "{{modelName}} это думает...",
"{{webUIName}} Backend Required": "{{webUIName}} бэкенд требуемый",
"a user": "юзер",
"About": "Относительно",
"Account": "Аккаунт",
"Action": "Действие",
"Add a model": "Добавьте модель",
"Add a model tag name": "Добавьте тэг модели имя",
"Add a short description about what this modelfile does": "Добавьте краткое описание, что делает этот моделифайл",
"Add a short title for this prompt": "Добавьте краткое название для этого взаимодействия",
"Add a tag": "Добавьте тэг",
"Add Docs": "Добавьте документы",
"Add Files": "Добавьте файлы",
"Add message": "Добавьте message",
"add tags": "Добавьте тэгы",
"Adjusting these settings will apply changes universally to all users.": "Регулирующий этих настроек приведет к изменениям для все юзеры.",
"admin": "админ",
"Admin Panel": "Панель админ",
"Admin Settings": "Настройки админ",
"Advanced Parameters": "Расширенные Параметры",
"all": "всё",
"All Users": "Всё юзеры",
"Allow": "Дозволять",
"Allow Chat Deletion": "Дозволять удаление чат",
"alphanumeric characters and hyphens": "буквенно цифровые символы и дефисы",
"Already have an account?": "у вас есть аккаунт уже?",
"an assistant": "ассистент",
"and": "и",
"API Base URL": "Базовый адрес API",
"API Key": "Ключ API",
"API RPM": "API RPM",
"are allowed - Activate this command by typing": "разрешено - активируйте эту команду набором",
"Are you sure?": "Вы уверены?",
"Audio": "Аудио",
"Auto-playback response": "Автоматическое воспроизведение ответа",
"Auto-send input after 3 sec.": "Автоматическая отправка ввода через 3 секунды.",
"AUTOMATIC1111 Base URL": "Базовый адрес URL AUTOMATIC1111",
"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 Необходима базовый адрес URL.",
"available!": "доступный!",
"Back": "Назад",
"Builder Mode": "Режим конструктор",
"Cancel": "Аннулировать",
"Categories": "Категории",
"Change Password": "Изменить пароль",
"Chat": "Чат",
"Chat History": "История чат",
"Chat History is off for this browser.": "История чат отключен для этого браузера.",
"Chats": "Чаты",
"Check Again": "Перепроверять",
"Check for updates": "Проверить обновления",
"Checking for updates...": "Проверка обновлений...",
"Choose a model before saving...": "Выберите модель перед сохранением...",
"Chunk Overlap": "Перекрытие фрагментов",
"Chunk Params": "Параметры фрагментов",
"Chunk Size": "Размер фрагмента",
"Click here for help.": "Нажмите здесь для помощь.",
"Click here to check other modelfiles.": "Нажмите тут чтобы проверить другие файлы моделей.",
"Click here to select": "Нажмите тут чтобы выберите",
"Click here to select documents.": "Нажмите здесь чтобы выберите документы.",
"click here.": "нажмите здесь.",
"Click on the user role button to change a user's role.": "Нажмите кнопку роли пользователя чтобы изменить роль пользователя.",
"Close": "Закрывать",
"Collection": "Коллекция",
"Command": "Команда",
"Confirm Password": "Подтвердите пароль",
"Connections": "Соединение",
"Content": "Содержание",
"Context Length": "Длина контексту",
"Conversation Mode": "Режим разговора",
"Copy last code block": "Копировать последний блок кода",
"Copy last response": "Копировать последний ответ",
"Copying to clipboard was successful!": "Копирование в буфер обмена прошло успешно!",
"Create a concise, 3-5 word phrase as a header for the following query, strictly adhering to the 3-5 word limit and avoiding the use of the word 'title'": "Создайте краткую, 3-5 словную фразу в качестве заголовка для следующего запроса, строго соблюдая ограничение в 3-5 слов и избегая использования слова 'заголовок'",
"Create a modelfile": "Создать модельный файл",
"Create Account": "Создать аккаунт",
"Created at": "Создано в",
"Created by": "Создано",
"Current Model": "Текущая модель",
"Current Password": "Текущий пароль",
"Custom": "Пользовательский",
"Customize Ollama models for a specific purpose": "Настроить модели Ollama для конкретной цели",
"Dark": "Тёмный",
"Database": "База данных",
"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
"Default": "По умолчанию",
"Default (Automatic1111)": "По умолчанию (Автоматический1111)",
"Default (Web API)": "По умолчанию (Web API)",
"Default model updated": "Модель по умолчанию обновлена",
"Default Prompt Suggestions": "Предложения промтов по умолчанию",
"Default User Role": "Роль пользователя по умолчанию",
"delete": "удалить",
"Delete a model": "Удалить модель",
"Delete chat": "Удалить чат",
"Delete Chats": "Удалить чаты",
"Deleted {{deleteModelTag}}": "Удалено {{deleteModelTag}}",
"Deleted {tagName}": "Удалено {tagName}",
"Description": "Описание",
"Desktop Notifications": "Уведомления на рабочем столе",
"Disabled": "Отключено",
"Discover a modelfile": "Найти файл модели",
"Discover a prompt": "Найти промт",
"Discover, download, and explore custom prompts": "Находите, загружайте и исследуйте настраиваемые промты",
"Discover, download, and explore model presets": "Находите, загружайте и исследуйте предустановки модели",
"Display the username instead of You in the Chat": "Отображать имя пользователя вместо 'Вы' в чате",
"Document": "Документ",
"Document Settings": "Настройки документа",
"Documents": "Документы",
"does not make any external connections, and your data stays securely on your locally hosted server.": "не устанавливает никаких внешних соединений, и ваши данные остаются безопасно на вашем локальном сервере.",
"Don't Allow": "Не разрешать",
"Don't have an account?": "у вас не есть аккаунт?",
"Download as a File": "Загрузить как файл",
"Download Database": "Загрузить базу данных",
"Drop any files here to add to the conversation": "Перетащите сюда файлы, чтобы добавить их в разговор",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "например, '30с','10м'. Допустимые единицы времени: 'с', 'м', 'ч'.",
"Edit Doc": "Редактировать документ",
"Edit User": "Редактировать пользователя",
"Email": "Электронная почта",
"Enable Chat History": "Включить историю чата",
"Enable New Sign Ups": "Разрешить новые регистрации",
"Enabled": "Включено",
"Enter {{role}} message here": "Введите сообщение {{role}} здесь",
"Enter API Key": "Введите ключ API",
"Enter Chunk Overlap": "Введите перекрытие фрагмента",
"Enter Chunk Size": "Введите размер фрагмента",
"Enter Image Size (e.g. 512x512)": "Введите размер изображения (например, 512x512)",
"Enter LiteLLM API Base URL (litellm_params.api_base)": "Введите базовый URL API LiteLLM (litellm_params.api_base)",
"Enter LiteLLM API Key (litellm_params.api_key)": "Введите ключ API LiteLLM (litellm_params.api_key)",
"Enter LiteLLM API RPM (litellm_params.rpm)": "Введите RPM API LiteLLM (litellm_params.rpm)",
"Enter LiteLLM Model (litellm_params.model)": "Введите модель LiteLLM (litellm_params.model)",
"Enter Max Tokens (litellm_params.max_tokens)": "Введите максимальное количество токенов (litellm_params.max_tokens)",
"Enter model tag (e.g. {{modelTag}})": "Введите тег модели (например, {{modelTag}})",
"Enter Number of Steps (e.g. 50)": "Введите количество шагов (например, 50)",
"Enter stop sequence": "Введите последовательность остановки",
"Enter Top K": "Введите Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Введите URL-адрес (например, http://127.0.0.1:7860/)",
"Enter Your Email": "Введите вашу электронную почту",
"Enter Your Full Name": "Введите ваше полное имя",
"Enter Your Password": "Введите ваш пароль",
"Experimental": "Экспериментальное",
"Export All Chats (All Users)": "Экспортировать все чаты (все пользователи)",
"Export Chats": "Экспортировать чаты",
"Export Documents Mapping": "Экспортировать отображение документов",
"Export Modelfiles": "Экспортировать файлы модели",
"Export Prompts": "Экспортировать промты",
"Failed to read clipboard contents": "Не удалось прочитать содержимое буфера обмена",
"File Mode": "Режим файла",
"File not found.": "Файл не найден.",
"Focus chat input": "Фокус ввода чата",
"Format your variables using square brackets like this:": "Форматируйте ваши переменные, используя квадратные скобки, как здесь:",
"From (Base Model)": "Из (базой модель)",
"Full Screen Mode": "Полноэкранный режим",
"General": "Общее",
"General Settings": "Общие настройки",
"Hello, {{name}}": "Привет, {{name}}",
"Hide": "Скрыть",
"Hide Additional Params": "Скрыть дополнительные параметры",
"How can I help you today?": "Чем я могу помочь вам сегодня?",
"Image Generation (Experimental)": "Генерация изображений (Экспериментально)",
"Image Generation Engine": "Механизм генерации изображений",
"Image Settings": "Настройки изображения",
"Images": "Изображения",
"Import Chats": "Импорт чатов",
"Import Documents Mapping": "Импорт сопоставления документов",
"Import Modelfiles": "Импорт файлов модели",
"Import Prompts": "Импорт подсказок",
"Include `--api` flag when running stable-diffusion-webui": "Добавьте флаг `--api` при запуске stable-diffusion-webui",
"Interface": "Интерфейс",
"join our Discord for help.": "присоединяйтесь к нашему Discord для помощи.",
"JSON": "JSON",
"JWT Expiration": "Истечение срока JWT",
"JWT Token": "Токен JWT",
"Keep Alive": "Поддерживать активность",
"Keyboard shortcuts": "Горячие клавиши",
"Language": "Язык",
"Light": "Светлый",
"Listening...": "Слушаю...",
"LLMs can make mistakes. Verify important information.": "LLMs могут допускать ошибки. Проверяйте важную информацию.",
"Made by OpenWebUI Community": "Сделано сообществом OpenWebUI",
"Make sure to enclose them with": "Убедитесь, что они заключены в",
"Manage LiteLLM Models": "Управление моделями LiteLLM",
"Manage Models": "Управление моделями",
"Manage Ollama Models": "Управление моделями Ollama",
"Max Tokens": "Максимальное количество токенов",
"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Максимальное количество моделей для загрузки одновременно - 3. Пожалуйста, попробуйте позже.",
"Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"MMMM DD, YYYY": "DD MMMM YYYY г.",
"Model '{{modelName}}' has been successfully downloaded.": "Модель '{{modelName}}' успешно загружена.",
"Model '{{modelTag}}' is already in queue for downloading.": "Модель '{{modelTag}}' уже находится в очереди на загрузку.",
"Model {{modelId}} not found": "Модель {{modelId}} не найдена",
"Model {{modelName}} already exists.": "Модель {{modelName}} уже существует.",
"Model Name": "Имя модели",
"Model not selected": "Модель не выбрана",
"Model Tag Name": "Имя тега модели",
"Model Whitelisting": "Включение модели в белый список",
"Model(s) Whitelisted": "Модель(и) включены в белый список",
"Modelfile": "Файл модели",
"Modelfile Advanced Settings": "Дополнительные настройки файла модели",
"Modelfile Content": "Содержимое файла модели",
"Modelfiles": "Файлы моделей",
"Models": "Модели",
"My Documents": "Мои документы",
"My Modelfiles": "Мои файлы моделей",
"My Prompts": "Мои подсказки",
"Name": "Имя",
"Name Tag": "Имя тега",
"Name your modelfile": "Назовите свой файл модели",
"New Chat": "Новый чат",
"New Password": "Новый пароль",
"Not sure what to add?": "Не уверены, что добавить?",
"Not sure what to write? Switch to": "Не уверены, что написать? Переключитесь на",
"Off": "Выключено.",
"Okay, Let's Go!": "Давайте начнём!",
"Ollama Base URL": "Базовый адрес URL Ollama",
"Ollama Version": "Версия Ollama",
"On": "Включено.",
"Only": "Только",
"Only alphanumeric characters and hyphens are allowed in the command string.": "В строке команды разрешено использовать только буквенно-цифровые символы и дефисы.",
"Oops! Hold tight! Your files are still in the processing oven. We're cooking them up to perfection. Please be patient and we'll let you know once they're ready.": "Упс! Зажмите пояса! Ваши файлы все еще в процессе обработки. Мы готовим их до идеального состояния. Пожалуйста, будьте терпеливы, и мы сообщим вам, когда они будут готовы.",
"Oops! Looks like the URL is invalid. Please double-check and try again.": "Упс! Похоже, что URL-адрес недействителен. Пожалуйста, перепроверьте и попробуйте еще раз.",
"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Упс! Вы используете неподдерживаемый метод (только фронтенд). Пожалуйста, обслуживайте веб-интерфейс из бэкенда.",
"Open": "Открыть",
"Open AI": "Open AI",
"Open AI (Dall-E)": "Open AI (Dall-E)",
"Open new chat": "Открыть новый чат",
"OpenAI API": "API OpenAI",
"OpenAI API Key": "Ключ API OpenAI",
"OpenAI API Key is required.": "Требуется ключ API OpenAI.",
"or": "или",
"Parameters": "Параметры",
"Password": "Пароль",
"PDF Extract Images (OCR)": "Извлечение изображений из PDF (OCR)",
"pending": "ожидание",
"Permission denied when accessing microphone: {{error}}": "Отказано в доступе к микрофону: {{error}}",
"Playground": "Площадка",
"Profile": "Профиль",
"Prompt Content": "Содержание промпта",
"Prompt suggestions": "Предложения промптов",
"Prompts": "Промпты",
"Pull a model from Ollama.com": "Загрузить модель с Ollama.com",
"Pull Progress": "Прогресс загрузки",
"Query Params": "Параметры запроса",
"RAG Template": "Шаблон RAG",
"Raw Format": "Сырой формат",
"Record voice": "Записать голос",
"Redirecting you to OpenWebUI Community": "Перенаправляем вас в сообщество OpenWebUI",
"Release Notes": "Примечания к выпуску",
"Repeat Last N": "Повторить последние N",
"Repeat Penalty": "Штраф за повтор",
"Request Mode": "Режим запроса",
"Reset Vector Storage": "Сбросить векторное хранилище",
"Response AutoCopy to Clipboard": "Автоматическое копирование ответа в буфер обмена",
"Role": "Роль",
"Rosé Pine": "Розовое сосновое дерево",
"Rosé Pine Dawn": "Розовое сосновое дерево рассвет",
"Save": "Сохранить",
"Save & Create": "Сохранить и создать",
"Save & Submit": "Сохранить и отправить",
"Save & Update": "Сохранить и обновить",
"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Прямое сохранение журналов чата в хранилище вашего браузера больше не поддерживается. Пожалуйста, потратьте минуту, чтобы скачать и удалить ваши журналы чата, нажав на кнопку ниже. Не волнуйтесь, вы легко сможете повторно импортировать свои журналы чата в бэкенд через",
"Scan": "Сканировать",
"Scan complete!": "Сканирование завершено!",
"Scan for documents from {{path}}": "Сканирование документов из {{path}}",
"Search": "Поиск",
"Search Documents": "Поиск документов",
"Search Prompts": "Поиск промтов",
"See readme.md for instructions": "Смотрите readme.md для инструкций",
"See what's new": "Посмотреть, что нового",
"Seed": "Сид",
"Select a mode": "Выберите режим",
"Select a model": "Выберите модель",
"Select an Ollama instance": "Выберите экземпляр Ollama",
"Send a Message": "Отправить сообщение",
"Send message": "Отправить сообщение",
"Server connection verified": "Соединение с сервером проверено",
"Set as default": "Установить по умолчанию",
"Set Default Model": "Установить модель по умолчанию",
"Set Image Size": "Установить размер изображения",
"Set Steps": "Установить шаги",
"Set Title Auto-Generation Model": "Установить модель автогенерации заголовков",
"Set Voice": "Установить голос",
"Settings": "Настройки",
"Settings saved successfully!": "Настройки успешно сохранены!",
"Share to OpenWebUI Community": "Поделиться с сообществом OpenWebUI",
"short-summary": "краткое описание",
"Show": "Показать",
"Show Additional Params": "Показать дополнительные параметры",
"Show shortcuts": "Показать клавиатурные сокращения",
"sidebar": "боковая панель",
"Sign in": "Войти",
"Sign Out": "Выход",
"Sign up": "зарегистрировать",
"Speech recognition error: {{error}}": "Ошибка распознавания речи: {{error}}",
"Speech-to-Text Engine": "Система распознавания речи",
"SpeechRecognition API is not supported in this browser.": "API распознавания речи не поддерживается в этом браузере.",
"Stop Sequence": "Последовательность остановки",
"STT Settings": "Настройки распознавания речи",
"Submit": "Отправить",
"Success": "Успех",
"Successfully updated.": "Успешно обновлено.",
"Sync All": "Синхронизировать все",
"System": "Система",
"System Prompt": "Системный промпт",
"Tags": "Теги",
"Temperature": "Температура",
"Template": "Шаблон",
"Text Completion": "Завершение текста",
"Text-to-Speech Engine": "Система синтеза речи",
"Tfs Z": "Tfs Z",
"Theme": "Тема",
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Это обеспечивает сохранение ваших ценных разговоров в безопасной базе данных на вашем сервере. Спасибо!",
"This setting does not sync across browsers or devices.": "Эта настройка не синхронизируется между браузерами или устройствами.",
"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Совет: Обновляйте несколько переменных подряд, нажимая клавишу Tab в поле ввода чата после каждой замены.",
"Title": "Заголовок",
"Title Auto-Generation": "Автогенерация заголовка",
"Title Generation Prompt": "Промпт для генерации заголовка",
"to": "в",
"To access the available model names for downloading,": "Чтобы получить доступ к доступным для загрузки именам моделей,",
"To access the GGUF models available for downloading,": "Чтобы получить доступ к моделям GGUF, доступным для загрузки,",
"to chat input.": "в чате.",
"Toggle settings": "Переключить настройки",
"Toggle sidebar": "Переключить боковую панель",
"Top K": "Top K",
"Top P": "Top P",
"Trouble accessing Ollama?": "Проблемы с доступом к Ollama?",
"TTS Settings": "Настройки TTS",
"Type Hugging Face Resolve (Download) URL": "Введите URL-адрес Hugging Face Resolve (загрузки)",
"Uh-oh! There was an issue connecting to {{provider}}.": "Упс! Возникла проблема подключения к {{provider}}.",
"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "Неизвестный тип файла '{{file_type}}', но принимается и обрабатывается как обычный текст",
"Update password": "Обновить пароль",
"Upload a GGUF model": "Загрузить модель GGUF",
"Upload files": "Загрузить файлы",
"Upload Progress": "Прогресс загрузки",
"URL Mode": "Режим URL",
"Use '#' in the prompt input to load and select your documents.": "Используйте '#' в поле ввода промпта для загрузки и выбора ваших документов.",
"Use Gravatar": "Использовать Gravatar",
"user": "пользователь",
"User Permissions": "Права пользователя",
"Users": "Пользователи",
"Utilize": "Использовать",
"Valid time units:": "Допустимые единицы времени:",
"variable": "переменная",
"variable to have them replaced with clipboard content.": "переменная, чтобы их заменить содержимым буфера обмена.",
"Version": "Версия",
"Web": "Веб",
"WebUI Add-ons": "Дополнения для WebUI",
"WebUI Settings": "Настройки WebUI",
"WebUI will make requests to": "WebUI будет отправлять запросы на",
"What’s New in": "Что нового в",
"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Когда история отключена, новые чаты в этом браузере не будут отображаться в вашей истории на любом из ваших устройств.",
"Whisper (Local)": "Шепот (локальный)",
"Write a prompt suggestion (e.g. Who are you?)": "Напишите предложение промпта (например, Кто вы?)",
"Write a summary in 50 words that summarizes [topic or keyword].": "Напишите резюме в 50 словах, которое кратко описывает [тему или ключевое слово].",
"You": "Вы",
"You're a helpful assistant.": "Вы полезный ассистент.",
"You're now logged in.": "Вы вошли в систему."
}
\ No newline at end of file
{
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' or '-1' для відсутності терміну дії.",
"(Beta)": "(Beta)",
"(e.g. `sh webui.sh --api`)": "(e.g. `sh webui.sh --api`)",
"(latest)": "(остання)",
"{{modelName}} is thinking...": "{{modelName}} думає...",
"{{webUIName}} Backend Required": "Необхідний бекенд {{webUIName}}",
"a user": "користувача",
"About": "Про програму",
"Account": "Обліковий запис",
"Action": "Дія",
"Add a model": "Додати модель",
"Add a model tag name": "Додати ім'я тегу моделі",
"Add a short description about what this modelfile does": "Додати короткий опис того, що робить цей файл моделі",
"Add a short title for this prompt": "Додати коротку назву для цього промту",
"Add a tag": "Додайте тег",
"Add Docs": "Додати документи",
"Add Files": "Додати файли",
"Add message": "Додати повідомлення",
"add tags": "додати теги",
"Adjusting these settings will apply changes universally to all users.": "Зміни в цих налаштуваннях будуть застосовані для всіх користувачів.",
"admin": "адмін",
"Admin Panel": "Панель адміністратора",
"Admin Settings": "Налаштування адміністратора",
"Advanced Parameters": "Розширені параметри",
"all": "всі",
"All Users": "Всі користувачі",
"Allow": "Дозволити",
"Allow Chat Deletion": "Дозволити видалення чату",
"alphanumeric characters and hyphens": "алфавітно-цифрові символи та дефіси",
"Already have an account?": "Вже є обліковий запис?",
"an assistant": "асистента",
"and": "та",
"API Base URL": "Базова адреса URL API",
"API Key": "Ключ API",
"API RPM": "API RPM",
"are allowed - Activate this command by typing": "дозволено - активізуйте цю команду набором",
"Are you sure?": "Ви впевнені?",
"Audio": "Аудіо",
"Auto-playback response": "Автоматичне відтворення відповіді",
"Auto-send input after 3 sec.": "Автоматична відправка вводу через 3 сек.",
"AUTOMATIC1111 Base URL": "Базова адреса URL AUTOMATIC1111",
"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 Необхідна URL-адреса.",
"available!": "доступно!",
"Back": "Назад",
"Builder Mode": "Режим конструктора",
"Cancel": "Скасувати",
"Categories": "Категорії",
"Change Password": "Змінити пароль",
"Chat": "Чат",
"Chat History": "Історія чату",
"Chat History is off for this browser.": "Історія чату вимкнена для цього браузера.",
"Chats": "Чати",
"Check Again": "Перевірити ще раз",
"Check for updates": "Перевірити оновлення",
"Checking for updates...": "Перевірка оновлень...",
"Choose a model before saving...": "Оберіть модель перед збереженням...",
"Chunk Overlap": "Перекриття фрагментів",
"Chunk Params": "Параметри фрагментів",
"Chunk Size": "Розмір фрагменту",
"Click here for help.": "Клацніть тут, щоб отримати допомогу.",
"Click here to check other modelfiles.": "Клацніть тут, щоб перевірити інші файли моделей.",
"Click here to select": "Натисніть тут, щоб вибрати",
"Click here to select documents.": "Натисніть тут, щоб вибрати документи.",
"click here.": "клацніть тут.",
"Click on the user role button to change a user's role.": "Натисніть кнопку ролі користувача, щоб змінити роль користувача.",
"Close": "Закрити",
"Collection": "Колекція",
"Command": "Команда",
"Confirm Password": "Підтвердіть пароль",
"Connections": "З'єднання",
"Content": "Зміст",
"Context Length": "Довжина контексту",
"Conversation Mode": "Режим розмови",
"Copy last code block": "Копіювати останній блок коду",
"Copy last response": "Копіювати останню відповідь",
"Copying to clipboard was successful!": "Копіювання в буфер обміну виконано успішно!",
"Create a concise, 3-5 word phrase as a header for the following query, strictly adhering to the 3-5 word limit and avoiding the use of the word 'title':": "Create a concise, 3-5 word phrase as a header for the following query, strictly adhering to the 3-5 word limit and avoiding the use of the word 'title':",
"Create a modelfile": "Створити файл моделі",
"Create Account": "Створити обліковий запис",
"Created at": "Створено",
"Created by": "Створено",
"Current Model": "Поточна модель",
"Current Password": "Поточний пароль",
"Custom": "Налаштувати",
"Customize Ollama models for a specific purpose": "Налаштувати моделі Ollama для конкретної мети",
"Dark": "Темна",
"Database": "База даних",
"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
"Default": "За замовчуванням",
"Default (Automatic1111)": "За замовчуванням (Automatic1111)",
"Default (Web API)": "За замовчуванням (Web API)",
"Default model updated": "Модель за замовчуванням оновлено",
"Default Prompt Suggestions": "Пропозиції промтів замовчуванням",
"Default User Role": "Роль користувача за замовчуванням",
"delete": "видалити",
"Delete a model": "Видалити модель",
"Delete chat": "Видалити чат",
"Delete Chats": "Видалити чати",
"Deleted {{deleteModelTag}}": "Видалено {{deleteModelTag}}",
"Deleted {tagName}": "Видалено {tagName}",
"Description": "Опис",
"Desktop Notifications": "Сповіщення",
"Disabled": "Вимкнено",
"Discover a modelfile": "Знайти файл моделі",
"Discover a prompt": "Знайти промт",
"Discover, download, and explore custom prompts": "Знайдіть, завантажте та досліджуйте налаштовані промти",
"Discover, download, and explore model presets": "Знайдіть, завантажте та досліджуйте налаштовані налаштування моделі",
"Display the username instead of You in the Chat": "Показувати ім'я користувача замість 'Ви' в чаті",
"Document": "Документ",
"Document Settings": "Налаштування документа",
"Documents": "Документи",
"does not make any external connections, and your data stays securely on your locally hosted server.": "не встановлює жодних зовнішніх з'єднань, і ваші дані залишаються в безпеці на вашому локальному сервері.",
"Don't Allow": "Не дозволяти",
"Don't have an account?": "Немає облікового запису?",
"Download as a File": "Завантажити як файл",
"Download Database": "Завантажити базу даних",
"Drop any files here to add to the conversation": "Перетягніть сюди файли, щоб додати до розмови",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "напр. '30s','10m'. Дійсні одиниці часу: 'с', 'хв', 'г'.",
"Edit Doc": "Редагувати документ",
"Edit User": "Редагувати користувача",
"Email": "Електронна пошта",
"Enable Chat History": "Увімкнути історію чату",
"Enable New Sign Ups": "Дозволити нові реєстрації",
"Enabled": "Увімкнено",
"Enter {{role}} message here": "Введіть повідомлення {{role}} тут",
"Enter API Key": "Введіть API-ключ",
"Enter Chunk Overlap": "Введіть перекриття фрагменту",
"Enter Chunk Size": "Введіть розмір фрагменту",
"Enter Image Size (e.g. 512x512)": "Введіть розмір зображення (напр. 512x512)",
"Enter LiteLLM API Base URL (litellm_params.api_base)": "Введіть URL-адресу API LiteLLM (litellm_params.api_base)",
"Enter LiteLLM API Key (litellm_params.api_key)": "Введіть ключ API LiteLLM (litellm_params.api_key)",
"Enter LiteLLM API RPM (litellm_params.rpm)": "Введіть RPM API LiteLLM (litellm_params.rpm)",
"Enter LiteLLM Model (litellm_params.model)": "Введіть модель LiteLLM (litellm_params.model)",
"Enter Max Tokens (litellm_params.max_tokens)": "Введіть максимальну кількість токенів (litellm_params.max_tokens)",
"Enter model tag (e.g. {{modelTag}})": "Введіть тег моделі (напр. {{modelTag}})",
"Enter Number of Steps (e.g. 50)": "Введіть кількість кроків (напр. 50)",
"Enter stop sequence": "Введіть символ зупинки",
"Enter Top K": "Введіть Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Введіть URL-адресу (напр. http://127.0.0.1:7860/)",
"Enter Your Email": "Введіть вашу електронну пошту",
"Enter Your Full Name": "Введіть ваше ім'я",
"Enter Your Password": "Введіть ваш пароль",
"Experimental": "Експериментальне",
"Export All Chats (All Users)": "Експортувати всі чати (всі користувачі)",
"Export Chats": "Експортувати чати",
"Export Documents Mapping": "Експортувати відображення документів",
"Export Modelfiles": "Експортувати файл моделі",
"Export Prompts": "Експортувати промти",
"Failed to read clipboard contents": "Не вдалося прочитати вміст буфера обміну",
"File Mode": "Файловий режим",
"File not found.": "Файл не знайдено.",
"Focus chat input": "Фокус вводу чату",
"Format your variables using square brackets like this:": "Форматуйте свої змінні квадратними дужками так:",
"From (Base Model)": "Від (базова модель)",
"Full Screen Mode": "Режим повного екрану",
"General": "Загальні",
"General Settings": "Загальні налаштування",
"Hello, {{name}}": "Привіт, {{name}}",
"Hide": "Приховати",
"Hide Additional Params": "Приховати додаткові параметри",
"How can I help you today?": "Чим я можу допомогти вам сьогодні?",
"Image Generation (Experimental)": "Генерування зображень (експериментально)",
"Image Generation Engine": "Механізм генерації зображень",
"Image Settings": "Налаштування зображення",
"Images": "Зображення",
"Import Chats": "Імпортувати чати",
"Import Documents Mapping": "Імпортувати відображення документів",
"Import Modelfiles": "Імпортувати файл моделі",
"Import Prompts": "Імпортувати промти",
"Include `--api` flag when running stable-diffusion-webui": "Включіть прапор `--api` при запуску stable-diffusion-webui",
"Interface": "Інтерфейс",
"join our Discord for help.": "приєднуйтеся до нашого Discord для допомоги.",
"JSON": "JSON",
"JWT Expiration": "Термін дії JWT",
"JWT Token": "Токен JWT",
"Keep Alive": "Зберегти активність",
"Keyboard shortcuts": "Клавіатурні скорочення",
"Language": "Мова",
"Light": "Світла",
"Listening...": "Слухаю...",
"LLMs can make mistakes. Verify important information.": "LLMs можуть помилятися. Перевірте важливу інформацію.",
"Made by OpenWebUI Community": "Зроблено спільнотою OpenWebUI",
"Make sure to enclose them with": "Переконайтеся, що вони закриті",
"Manage LiteLLM Models": "Керування моделями LiteLLM",
"Manage Models": "Керування моделями",
"Manage Ollama Models": "Керування моделями Ollama",
"Max Tokens": "Максимальна кількість токенів",
"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Максимум 3 моделі можна завантажити одночасно. Будь ласка, спробуйте пізніше.",
"Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"MMMM DD, YYYY": "MMMM DD, YYYY",
"Model '{{modelName}}' has been successfully downloaded.": "Модель '{{modelName}}' успішно завантажено.",
"Model '{{modelTag}}' is already in queue for downloading.": "Модель '{{modelTag}}' вже знаходиться в черзі на завантаження.",
"Model {{modelId}} not found": "Модель {{modelId}} не знайдено",
"Model {{modelName}} already exists.": "Модель {{modelName}} вже існує.",
"Model Name": "Назва моделі",
"Model not selected": "Модель не вибрана",
"Model Tag Name": "Ім'я тегу моделі",
"Model Whitelisting": "Модель білого списку",
"Model(s) Whitelisted": "Модель(і) білого списку",
"Modelfile": "Файли моделі",
"Modelfile Advanced Settings": "Додаткові налаштування файлу моделі",
"Modelfile Content": "Вміст файлу моделі",
"Modelfiles": "Файли моделей",
"Models": "Моделі",
"My Documents": "Мої документи",
"My Modelfiles": "Мої файли моделей",
"My Prompts": "Мої промти",
"Name": "Ім'я",
"Name Tag": "Назва тегу",
"Name your modelfile": "Назвіть свій файл моделі",
"New Chat": "Новий чат",
"New Password": "Новий пароль",
"Not sure what to add?": "Не впевнений, що додати?",
"Not sure what to write? Switch to": "Не впевнений, що писати? Переключитися на",
"Off": "Вимк",
"Okay, Let's Go!": "Гаразд, давайте почнемо!",
"Ollama Base URL": "Основна URL-адреса Ollama",
"Ollama Version": "Версія Ollama",
"On": "Увімк",
"Only": "Тільки",
"Only alphanumeric characters and hyphens are allowed in the command string.": "У рядку команди дозволено використовувати лише алфавітно-цифрові символи та дефіси.",
"Oops! Hold tight! Your files are still in the processing oven. We're cooking them up to perfection. Please be patient and we'll let you know once they're ready.": "Ой! Зачекайте, будь ласка! Ваші файли ще готуються. Ми робимо все, щоб вони були ідеальними. Будь ласка, будьте терплячі, ми повідомимо вам, коли вони будуть готові.",
"Oops! Looks like the URL is invalid. Please double-check and try again.": "Упс! Схоже, що URL-адреса невірна. Будь ласка, перевірте ще раз та спробуйте ще раз.",
"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Упс! Ви використовуєте непідтримуваний метод (тільки для фронтенду). Будь ласка, обслуговуйте WebUI з бекенду.",
"Open": "Відкрити",
"Open AI": "Open AI",
"Open AI (Dall-E)": "Open AI (Dall-E)",
"Open new chat": "Відкрити новий чат",
"OpenAI API": "API OpenAI",
"OpenAI API Key": "Ключ API OpenAI",
"OpenAI API Key is required.": "Потрібен ключ OpenAI API.",
"or": "або",
"Parameters": "Параметри",
"Password": "Пароль",
"PDF Extract Images (OCR)": "Розпізнавання зображень з PDF (OCR)",
"pending": "на розгляді",
"Permission denied when accessing microphone: {{error}}": "Доступ до мікрофона заборонено: {{error}}",
"Playground": "Майданчик",
"Profile": "Профіль",
"Prompt Content": "Зміст промту",
"Prompt suggestions": "Швидкі промти",
"Prompts": "Промти",
"Pull a model from Ollama.com": "Завантажити модель з Ollama.com",
"Pull Progress": "Прогрес завантаження",
"Query Params": "Параметри запиту",
"RAG Template": "Шаблон RAG",
"Raw Format": "Необроблений формат",
"Record voice": "Записати голос",
"Redirecting you to OpenWebUI Community": "Перенаправляємо вас до спільноти OpenWebUI",
"Release Notes": "Нотатки до випуску",
"Repeat Last N": "Повторити останні N",
"Repeat Penalty": "Штраф за повторення",
"Request Mode": "Режим запиту",
"Reset Vector Storage": "Скинути векторне сховище",
"Response AutoCopy to Clipboard": "Автокопіювання відповіді в буфер обміну",
"Role": "Роль",
"Rosé Pine": "Rosé Pine",
"Rosé Pine Dawn": "Rosé Pine Dawn",
"Save": "Зберегти",
"Save & Create": "Зберегти та створити",
"Save & Submit": "Зберегти та надіслати",
"Save & Update": "Зберегти та оновити",
"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Збереження журналів чату безпосередньо в сховище вашого браузера більше не підтримується. Будь ласка, завантажте та видаліть журнали чату, натиснувши кнопку нижче. Не хвилюйтеся, ви можете легко повторно імпортувати журнали чату до бекенду через",
"Scan": "Сканування",
"Scan complete!": "Сканування завершено!",
"Scan for documents from {{path}}": "Сканування документів з {{path}}",
"Search": "Пошук",
"Search Documents": "Пошук документів",
"Search Prompts": "Пошук промтів",
"See readme.md for instructions": "Див. readme.md для інструкцій",
"See what's new": "Подивіться, що нового",
"Seed": "Сід",
"Select a mode": "Оберіть режим",
"Select a model": "Виберіть модель",
"Select an Ollama instance": "Виберіть екземпляр Ollama",
"Send a Message": "Надіслати повідомлення",
"Send message": "Надіслати повідомлення",
"Server connection verified": "З'єднання з сервером підтверджено",
"Set as default": "Встановити за замовчуванням",
"Set Default Model": "Встановити модель за замовчуванням",
"Set Image Size": "Встановити розмір зображення",
"Set Steps": "Встановити кроки",
"Set Title Auto-Generation Model": "Встановити модель автогенерації заголовків",
"Set Voice": "Встановити голос",
"Settings": "Налаштування",
"Settings saved successfully!": "Налаштування успішно збережено!",
"Share to OpenWebUI Community": "Поділитися зі спільнотою OpenWebUI",
"short-summary": "короткий зміст",
"Show": "Показати",
"Show Additional Params": "Показати додаткові параметри",
"Show shortcuts": "Показати клавіатурні скорочення",
"sidebar": "бокова панель",
"Sign in": "Увійти",
"Sign Out": "Вийти",
"Sign up": "Зареєструватися",
"Speech recognition error: {{error}}": "Помилка розпізнавання мови: {{error}}",
"Speech-to-Text Engine": "Система розпізнавання мови",
"SpeechRecognition API is not supported in this browser.": "SpeechRecognition API не підтримується в цьому браузері.",
"Stop Sequence": "Символ зупинки",
"STT Settings": "Налаштування STT",
"Submit": "Надіслати",
"Success": "Успіх",
"Successfully updated.": "Успішно оновлено.",
"Sync All": "Синхронізувати все",
"System": "Система",
"System Prompt": "Системний промт",
"Tags": "Теги",
"Temperature": "Температура",
"Template": "Шаблон",
"Text Completion": "Завершення тексту",
"Text-to-Speech Engine": "Система синтезу мови",
"Tfs Z": "Tfs Z",
"Theme": "Тема",
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Це забезпечує збереження ваших цінних розмов у безпечному бекенд-сховищі. Дякуємо!",
"This setting does not sync across browsers or devices.": "Це налаштування не синхронізується між браузерами або пристроями.",
"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Порада: Оновіть кілька слотів змінних послідовно, натискаючи клавішу табуляції у вікні чату після кожної заміни.",
"Title": "Заголовок",
"Title Auto-Generation": "Автогенерація заголовків",
"Title Generation Prompt": "Промт для генерування заголовків",
"to": "в",
"To access the available model names for downloading,": "Щоб отримати доступ до назв доступних для завантаження моделей,",
"To access the GGUF models available for downloading,": "Щоб отримати доступ до моделей GGUF, які можна завантажити,,",
"to chat input.": "в чаті.",
"Toggle settings": "Переключити налаштування",
"Toggle sidebar": "Переключити бокову панель",
"Top K": "Top K",
"Top P": "Top P",
"Trouble accessing Ollama?": "Проблеми з доступом до Ollama?",
"TTS Settings": "Налаштування TTS",
"Type Hugging Face Resolve (Download) URL": "Введіть URL ресурсу Hugging Face Resolve (завантаження)",
"Uh-oh! There was an issue connecting to {{provider}}.": "Ой! Виникла проблема при підключенні до {{provider}}.",
"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "Невідомий тип файлу '{{file_type}}', але приймається та обробляється як звичайний текст",
"Update password": "Оновити пароль",
"Upload a GGUF model": "Завантажити GGUF модель",
"Upload files": "Завантажити файли",
"Upload Progress": "Прогрес завантаження",
"URL Mode": "Режим URL",
"Use '#' in the prompt input to load and select your documents.": "Для введення промтів до веб-сторінок (URL) або вибору документів, будь ласка, використовуйте символ '#'.",
"Use Gravatar": "Змінити аватар",
"user": "користувач",
"User Permissions": "Дозволи користувача",
"Users": "Користувачі",
"Utilize": "Використовувати",
"Valid time units:": "Дійсні одиниці часу:",
"variable": "змінна",
"variable to have them replaced with clipboard content.": "змінна, щоб замінити їх вмістом буфера обміну.",
"Version": "Версія",
"Web": "Веб",
"WebUI Add-ons": "Додатки WebUI",
"WebUI Settings": "Налаштування WebUI",
"WebUI will make requests to": "WebUI буде робити запити до",
"What’s New in": "Що нового в",
"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Коли історія вимкнена, нові чати в цьому браузері не будуть відображатися в історії на жодному з ваших пристроїв.",
"Whisper (Local)": "Whisper (локально)",
"Write a prompt suggestion (e.g. Who are you?)": "Напишіть промт (напр. Хто ти?)",
"Write a summary in 50 words that summarizes [topic or keyword].": "Напишіть стислий зміст у 50 слів, який узагальнює [тема або ключове слово].",
"You": "Ви",
"You're a helpful assistant.": "Ви корисний асистент.",
"You're now logged in.": "Ви увійшли в систему."
}
{
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' hoặc '-1' không hết hạn.",
"(Beta)": "(Beta)",
"(e.g. `sh webui.sh --api`)": "(vd: `sh webui.sh --api`)",
"(latest)": "(mới nhất)",
"{{modelName}} is thinking...": "{{modelName}} đang suy nghĩ...",
"{{webUIName}} Backend Required": "{{webUIName}} Yêu cầu Backend",
"a user": "một người dùng",
"About": "Giới thiệu",
"Account": "Tài khoản",
"Action": "Hành động",
"Add a model": "Thêm mô hình",
"Add a model tag name": "Thêm tên thẻ mô hình (tag)",
"Add a short description about what this modelfile does": "Thêm mô tả ngắn về việc tệp mô tả mô hình (modelfile) này làm gì",
"Add a short title for this prompt": "Thêm tiêu đề ngắn cho prompt này",
"Add a tag": "Thêm thẻ (tag)",
"Add Docs": "Thêm tài liệu",
"Add Files": "Thêm tệp",
"Add message": "Thêm tin nhắn",
"add tags": "thêm thẻ",
"Adjusting these settings will apply changes universally to all users.": "Điều chỉnh các cài đặt này sẽ áp dụng thay đổi toàn cầu cho tất cả người dùng.",
"admin": "quản trị viên",
"Admin Panel": "Trang Quản trị",
"Admin Settings": "Cài đặt hệ thống",
"Advanced Parameters": "Các tham số Nâng cao",
"all": "tất cả",
"All Users": "Tất cả Người dùng",
"Allow": "Cho phép",
"Allow Chat Deletion": "Cho phép Xóa cuộc trò chuyện",
"alphanumeric characters and hyphens": "ký tự số và gạch nối",
"Already have an account?": "Đã có tài khoản?",
"an assistant": "một trợ lý",
"and": "và",
"API Base URL": "URL Cơ bản API",
"API Key": "Khóa API",
"API RPM": "RPM API",
"are allowed - Activate this command by typing": "được phép - Kích hoạt lệnh này bằng cách gõ",
"Are you sure?": "Bạn có chắc chắn không?",
"Audio": "Âm thanh",
"Auto-playback response": "Tự động phát lại phản hồi",
"Auto-send input after 3 sec.": "Tự động gửi đầu vào sau 3 giây.",
"AUTOMATIC1111 Base URL": "URL Cơ bản AUTOMATIC1111",
"AUTOMATIC1111 Base URL is required.": "Yêu cầu URL Cơ bản AUTOMATIC1111.",
"available!": "có sẵn!",
"Back": "Quay lại",
"Builder Mode": "Chế độ Builder",
"Cancel": "Hủy bỏ",
"Categories": "Danh mục",
"Change Password": "Đổi Mật khẩu",
"Chat": "Trò chuyện",
"Chat History": "Lịch sử Trò chuyện",
"Chat History is off for this browser.": "Lịch sử Trò chuyện đã tắt cho trình duyệt này.",
"Chats": "Cuộc trò chuyện",
"Check Again": "Kiểm tra Lại",
"Check for updates": "Kiểm tra cập nhật",
"Checking for updates...": "Đang kiểm tra cập nhật...",
"Choose a model before saving...": "Chọn một mô hình trước khi lưu...",
"Chunk Overlap": "Chunk chồng lấn (overlap)",
"Chunk Params": "Cài đặt tham số cho Chunk",
"Chunk Size": "Kích thước Chunk",
"Click here for help.": "Bấm vào đây để được trợ giúp.",
"Click here to check other modelfiles.": "Bấm vào đây để kiểm tra các tệp mô tả mô hình (modelfiles) khác.",
"Click here to select": "Bấm vào đây để chọn",
"Click here to select documents.": "Bấm vào đây để chọn tài liệu.",
"click here.": "bấm vào đây.",
"Click on the user role button to change a user's role.": "Bấm vào nút vai trò người dùng để thay đổi vai trò của người dùng.",
"Close": "Đóng",
"Collection": "Bộ sưu tập",
"Command": "Lệnh",
"Confirm Password": "Xác nhận Mật khẩu",
"Connections": "Kết nối",
"Content": "Nội dung",
"Context Length": "Độ dài Ngữ cảnh",
"Conversation Mode": "Chế độ Hội thoại",
"Copy last code block": "Sao chép khối mã cuối cùng",
"Copy last response": "Sao chép phản hồi cuối cùng",
"Copying to clipboard was successful!": "Sao chép vào clipboard thành công!",
"Create a concise, 3-5 word phrase as a header for the following query, strictly adhering to the 3-5 word limit and avoiding the use of the word 'title':": "Tạo một cụm từ súc tích, 3-5 từ làm tiêu đề cho truy vấn sau, tuân thủ nghiêm ngặt giới hạn 3-5 từ và tránh sử dụng từ 'tiêu đề':",
"Create a modelfile": "Tạo tệp mô tả mô hình",
"Create Account": "Tạo Tài khoản",
"Created at": "Được tạo vào lúc",
"Created by": "Được tạo bởi",
"Current Model": "Mô hình hiện tại",
"Current Password": "Mật khẩu hiện tại",
"Custom": "Tùy chỉnh",
"Customize Ollama models for a specific purpose": "Tùy chỉnh các mô hình dựa trên Ollama cho một mục đích cụ thể",
"Dark": "Tối",
"Database": "Cơ sở dữ liệu",
"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
"Default": "Mặc định",
"Default (Automatic1111)": "Mặc định (Automatic1111)",
"Default (Web API)": "Mặc định (Web API)",
"Default model updated": "Mô hình mặc định đã được cập nhật",
"Default Prompt Suggestions": "Đề nghị Nhắc mặc định",
"Default User Role": "Vai trò Người dùng Mặc định",
"delete": "xóa",
"Delete a model": "Xóa mô hình",
"Delete chat": "Xóa cuộc trò chuyện",
"Delete Chats": "Xóa Cuộc trò chuyện",
"Deleted {{deleteModelTag}}": "Đã xóa {{deleteModelTag}}",
"Deleted {tagName}": "Đã xóa {tagName}",
"Description": "Mô tả",
"Desktop Notifications": "Thông báo Máy tính",
"Disabled": "Đã vô hiệu hóa",
"Discover a modelfile": "Khám phá một tệp mô hình",
"Discover a prompt": "Khám phá một prompt",
"Discover, download, and explore custom prompts": "Khám phá, tải xuống và khám phá các prompt tùy chỉnh",
"Discover, download, and explore model presets": "Khám phá, tải xuống và khám phá các thiết lập mô hình sẵn",
"Display the username instead of You in the Chat": "Hiển thị tên người dùng thay vì 'Bạn' trong Cuộc trò chuyện",
"Document": "Tài liệu",
"Document Settings": "Cài đặt Tài liệu",
"Documents": "Tài liệu",
"does not make any external connections, and your data stays securely on your locally hosted server.": "không thực hiện bất kỳ kết nối ngoài nào, và dữ liệu của bạn vẫn được lưu trữ an toàn trên máy chủ lưu trữ cục bộ của bạn.",
"Don't Allow": "Không Cho phép",
"Don't have an account?": "Không có tài khoản?",
"Download as a File": "Tải xuống dưới dạng tệp",
"Download Database": "Tải xuống Cơ sở dữ liệu",
"Drop any files here to add to the conversation": "Thả bất kỳ tệp nào ở đây để thêm vào cuộc trò chuyện",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "vd: '30s','10m'. Đơn vị thời gian hợp lệ là 's', 'm', 'h'.",
"Edit Doc": "Sửa Tài liệu",
"Edit User": "Sửa Người dùng",
"Email": "Email",
"Enable Chat History": "Bật Lịch sử Trò chuyện",
"Enable New Sign Ups": "Cho phép Đăng ký Mới",
"Enabled": "Đã bật",
"Enter {{role}} message here": "Nhập tin nhắn {{role}} ở đây",
"Enter API Key": "Nhập Khóa API",
"Enter Chunk Overlap": "Nhập Chunk chồng lấn (overlap)",
"Enter Chunk Size": "Nhập Kích thước Chunk",
"Enter Image Size (e.g. 512x512)": "Nhập Kích thước ảnh (vd: 512x512)",
"Enter LiteLLM API Base URL (litellm_params.api_base)": "Nhập URL Cơ bản API LiteLLM (litellm_params.api_base)",
"Enter LiteLLM API Key (litellm_params.api_key)": "Nhập Khóa API LiteLLM (litellm_params.api_key)",
"Enter LiteLLM API RPM (litellm_params.rpm)": "Nhập RPM API LiteLLM (litellm_params.rpm)",
"Enter LiteLLM Model (litellm_params.model)": "Nhập Mô hình LiteLLM (litellm_params.model)",
"Enter Max Tokens (litellm_params.max_tokens)": "Nhập Số Token Tối đa (litellm_params.max_tokens)",
"Enter model tag (e.g. {{modelTag}})": "Nhập thẻ mô hình (vd: {{modelTag}})",
"Enter Number of Steps (e.g. 50)": "Nhập Số Bước (vd: 50)",
"Enter stop sequence": "Nhập trình tự dừng",
"Enter Top K": "Nhập Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Nhập URL (vd: http://127.0.0.1:7860/)",
"Enter Your Email": "Nhập Email của bạn",
"Enter Your Full Name": "Nhập Họ và Tên của bạn",
"Enter Your Password": "Nhập Mật khẩu của bạn",
"Experimental": "Thực nghiệm",
"Export All Chats (All Users)": "Xuất Tất cả Cuộc trò chuyện (Tất cả Người dùng)",
"Export Chats": "Xuất Cuộc trò chuyện",
"Export Documents Mapping": "Xuất Ánh xạ Tài liệu",
"Export Modelfiles": "Xuất Tệp Mô hình",
"Export Prompts": "Xuất prompts",
"Failed to read clipboard contents": "Không thể đọc nội dung clipboard",
"File Mode": "Chế độ Tệp văn bản",
"File not found.": "Không tìm thấy tệp.",
"Focus chat input": "Tập trung vào đầu vào cuộc trò chuyện",
"Format your variables using square brackets like this:": "Định dạng các biến của bạn bằng cách sử dụng dấu ngoặc vuông như thế này:",
"From (Base Model)": "Từ (Mô hình Cơ sở)",
"Full Screen Mode": "Chế độ Toàn màn hình",
"General": "Tổng quát",
"General Settings": "Cài đặt tổng quát",
"Hello, {{name}}": "Xin chào, {{name}}",
"Hide": "Ẩn",
"Hide Additional Params": "Ẩn Các tham số bổ sung",
"How can I help you today?": "Tôi có thể giúp gì cho bạn hôm nay?",
"Image Generation (Experimental)": "tạo ảnh (Thực nghiệm)",
"Image Generation Engine": "Công cụ tạo ảnh",
"Image Settings": "Cài đặt ảnh",
"Images": "Hình ảnh",
"Import Chats": "Nạp cuộc trò chuyện (chats)",
"Import Documents Mapping": "Nạp sơ đồ hóa Tài liệu (document mapping)",
"Import Modelfiles": "Nạp tệp mô tả mô hình (modelefiles)",
"Import Prompts": "Nạp Prompts",
"Include `--api` flag when running stable-diffusion-webui": "Bao gồm flag `--api` khi chạy stable-diffusion-webui",
"Interface": "Giao diện",
"join our Discord for help.": "tham gia Discord của chúng tôi để được trợ giúp.",
"JSON": "JSON",
"JWT Expiration": "JWT Hết hạn",
"JWT Token": "Token JWT",
"Keep Alive": "Giữ kết nối",
"Keyboard shortcuts": "Phím tắt",
"Language": "Ngôn ngữ",
"Light": "Sáng",
"Listening...": "Đang nghe...",
"LLMs can make mistakes. Verify important information.": "Hệ thống có thể tạo ra nội dung không chính xác hoặc sai. Hãy kiểm chứng kỹ lưỡng thông tin trước khi tiếp nhận và sử dụng.",
"Made by OpenWebUI Community": "Được tạo bởi Cộng đồng OpenWebUI",
"Make sure to enclose them with": "Hãy chắc chắn bao quanh chúng bằng",
"Manage LiteLLM Models": "Quản lý mô hình với LiteLLM",
"Manage Models": "Quản lý mô hình",
"Manage Ollama Models": "Quản lý mô hình với Ollama",
"Max Tokens": "Số Token Tối đa",
"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Tối đa 3 mô hình có thể được tải xuống cùng lúc. Vui lòng thử lại sau.",
"Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"MMMM DD, YYYY": "MMMM DD, YYYY",
"Model '{{modelName}}' has been successfully downloaded.": "Mô hình '{{modelName}}' đã được tải xuống thành công.",
"Model '{{modelTag}}' is already in queue for downloading.": "Mô hình '{{modelTag}}' đã có trong hàng đợi để tải xuống.",
"Model {{modelId}} not found": "Không tìm thấy Mô hình {{modelId}}",
"Model {{modelName}} already exists.": "Mô hình {{modelName}} đã tồn tại.",
"Model Name": "Tên Mô hình",
"Model not selected": "Chưa chọn Mô hình",
"Model Tag Name": "Tên thẻ Mô hình",
"Model Whitelisting": "Whitelist mô hình",
"Model(s) Whitelisted": "các mô hình được cho vào danh sách Whitelist",
"Modelfile": "Tệp Mô hình",
"Modelfile Advanced Settings": "Cài đặt Nâng cao Tệp Mô hình",
"Modelfile Content": "Nội dung Tệp Mô hình",
"Modelfiles": "Tệp Mô hình",
"Models": "Mô hình",
"My Documents": "Tài liệu của tôi",
"My Modelfiles": "Tệp Mô hình của tôi",
"My Prompts": "Các prompt của tôi",
"Name": "Tên",
"Name Tag": "Tên Thẻ",
"Name your modelfile": "Đặt tên cho tệp mô hình của bạn",
"New Chat": "Cuộc trò chuyện Mới",
"New Password": "Mật khẩu Mới",
"Not sure what to add?": "Không chắc phải thêm gì?",
"Not sure what to write? Switch to": "Không chắc phải viết gì? Chuyển sang",
"Off": "Tắt",
"Okay, Let's Go!": "Được rồi, Bắt đầu thôi!",
"Ollama Base URL": "URL Cơ bản Ollama",
"Ollama Version": "Phiên bản Ollama",
"On": "Bật",
"Only": "Chỉ",
"Only alphanumeric characters and hyphens are allowed in the command string.": "Chỉ ký tự số và gạch nối được phép trong chuỗi lệnh.",
"Oops! Hold tight! Your files are still in the processing oven. We're cooking them up to perfection. Please be patient and we'll let you know once they're ready.": "Rất tiếc! Hãy giữ nguyên! Các tệp của bạn vẫn đang trong được phân tích và xử lý. Chúng tôi đang cố gắng hoàn thành chúng. Vui lòng kiên nhẫn và chúng tôi sẽ cho bạn biết khi chúng sẵn sàng.",
"Oops! Looks like the URL is invalid. Please double-check and try again.": "Rất tiếc! URL dường như không hợp lệ. Vui lòng kiểm tra lại và thử lại.",
"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Rất tiếc! Bạn đang sử dụng một phương thức không được hỗ trợ (chỉ dành cho frontend). Vui lòng cung cấp phương thức cho WebUI từ phía backend.",
"Open": "Mở",
"Open AI": "Open AI",
"Open AI (Dall-E)": "Open AI (Dall-E)",
"Open new chat": "Mở cuộc trò chuyện mới",
"OpenAI API": "API OpenAI",
"OpenAI API Key": "Khóa API OpenAI",
"OpenAI API Key is required.": "Yêu cầu Khóa API OpenAI.",
"or": "hoặc",
"Parameters": "Tham số",
"Password": "Mật khẩu",
"PDF Extract Images (OCR)": "Trích xuất ảnh từ PDF (OCR)",
"pending": "đang chờ",
"Permission denied when accessing microphone: {{error}}": "Quyền truy cập micrô bị từ chối: {{error}}",
"Playground": "Thử nghiệm (Playground)",
"Profile": "Hồ sơ",
"Prompt Content": "Nội dung prompt",
"Prompt suggestions": "Đề nghị prompt",
"Prompts": "prompt",
"Pull a model from Ollama.com": "Tải một mô hình từ Ollama.com",
"Pull Progress": "Tiến trình Tải xuống",
"Query Params": "Tham số Truy vấn",
"RAG Template": "Mẫu prompt cho RAG",
"Raw Format": "Raw Format",
"Record voice": "Ghi âm",
"Redirecting you to OpenWebUI Community": "Đang chuyển hướng bạn đến Cộng đồng OpenWebUI",
"Release Notes": "Ghi chú Phát hành",
"Repeat Last N": "Repeat Last N",
"Repeat Penalty": "Repeat Penalty",
"Request Mode": "Request Mode",
"Reset Vector Storage": "Cài đặt lại Vector Storage",
"Response AutoCopy to Clipboard": "Tự động Sao chép Phản hồi vào clipboard",
"Role": "Vai trò",
"Rosé Pine": "Rosé Pine",
"Rosé Pine Dawn": "Rosé Pine Dawn",
"Save": "Lưu",
"Save & Create": "Lưu & Tạo",
"Save & Submit": "Lưu & Gửi",
"Save & Update": "Lưu & Cập nhật",
"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Không còn hỗ trợ lưu trữ nhật ký trò chuyện trực tiếp vào bộ nhớ trình duyệt của bạn. Vui lòng dành thời gian để tải xuống và xóa nhật ký trò chuyện của bạn bằng cách nhấp vào nút bên dưới. Đừng lo lắng, bạn có thể dễ dàng nhập lại nhật ký trò chuyện của mình vào backend thông qua",
"Scan": "Quét",
"Scan complete!": "Quét hoàn tất!",
"Scan for documents from {{path}}": "Quét tài liệu từ {{path}}",
"Search": "Tìm kiếm",
"Search Documents": "Tìm Tài liệu",
"Search Prompts": "Tìm prompt",
"See readme.md for instructions": "Xem readme.md để biết hướng dẫn",
"See what's new": "Xem những gì mới",
"Seed": "Hạt giống",
"Select a mode": "Chọn một chế độ",
"Select a model": "Chọn một mô hình",
"Select an Ollama instance": "Chọn một thực thể Ollama",
"Send a Message": "Gửi yêu cầu",
"Send message": "Gửi yêu cầu",
"Server connection verified": "Kết nối máy chủ đã được xác minh",
"Set as default": "Đặt làm mặc định",
"Set Default Model": "Đặt Mô hình Mặc định",
"Set Image Size": "Đặt Kích thước ảnh",
"Set Steps": "Đặt Số Bước",
"Set Title Auto-Generation Model": "Đặt Mô hình Tự động Tạo Tiêu đề",
"Set Voice": "Đặt Giọng nói",
"Settings": "Cài đặt",
"Settings saved successfully!": "Cài đặt đã được lưu thành công!",
"Share to OpenWebUI Community": "Chia sẻ đến Cộng đồng OpenWebUI",
"short-summary": "tóm tắt ngắn",
"Show": "Hiển thị",
"Show Additional Params": "Hiển thị Tham số Bổ sung",
"Show shortcuts": "Hiển thị phím tắt",
"sidebar": "thanh bên",
"Sign in": "Đăng nhập",
"Sign Out": "Đăng xuất",
"Sign up": "Đăng ký",
"Speech recognition error: {{error}}": "Lỗi nhận dạng giọng nói: {{error}}",
"Speech-to-Text Engine": "Công cụ Nhận dạng Giọng nói",
"SpeechRecognition API is not supported in this browser.": "Trình duyệt này không hỗ trợ API Nhận dạng Giọng nói.",
"Stop Sequence": "Trình tự Dừng",
"STT Settings": "Cài đặt Nhận dạng Giọng nói",
"Submit": "Gửi",
"Success": "Thành công",
"Successfully updated.": "Đã cập nhật thành công.",
"Sync All": "Đồng bộ hóa Tất cả",
"System": "Hệ thống",
"System Prompt": "prompt Hệ thống",
"Tags": "Thẻ",
"Temperature": "Nhiệt độ",
"Template": "Mẫu",
"Text Completion": "Hoàn tất Văn bản",
"Text-to-Speech Engine": "Công cụ Chuyển Văn bản thành Giọng nói",
"Tfs Z": "Tfs Z",
"Theme": "Chủ đề",
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Điều này đảm bảo rằng các cuộc trò chuyện có giá trị của bạn được lưu an toàn vào cơ sở dữ liệu backend của bạn. Cảm ơn bạn!",
"This setting does not sync across browsers or devices.": "Cài đặt này không đồng bộ hóa trên các trình duyệt hoặc thiết bị.",
"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Mẹo: Cập nhật nhiều khe biến liên tiếp bằng cách nhấn phím tab trong đầu vào trò chuyện sau mỗi việc thay thế.",
"Title": "Tiêu đề",
"Title Auto-Generation": "Tự động Tạo Tiêu đề",
"Title Generation Prompt": "prompt Tạo Tiêu đề",
"to": "đến",
"To access the available model names for downloading,": "Để truy cập các tên mô hình có sẵn để tải xuống,",
"To access the GGUF models available for downloading,": "Để truy cập các mô hình GGUF có sẵn để tải xuống,",
"to chat input.": "đến đầu vào trò chuyện.",
"Toggle settings": "Bật/tắt cài đặt",
"Toggle sidebar": "Bật/tắt thanh bên",
"Top K": "Top K",
"Top P": "Top P",
"Trouble accessing Ollama?": "Gặp vấn đề khi truy cập Ollama?",
"TTS Settings": "Cài đặt Chuyển văn bản thành Giọng nói",
"Type Hugging Face Resolve (Download) URL": "Nhập URL Hugging Face Resolve (Tải xuống)",
"Uh-oh! There was an issue connecting to {{provider}}.": "Ồ! Đã xảy ra sự cố khi kết nối với {{provider}}.",
"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "Loại Tệp Không xác định '{{file_type}}', nhưng đang chấp nhận và xử lý như văn bản thô",
"Update password": "Cập nhật mật khẩu",
"Upload a GGUF model": "Tải lên một mô hình GGUF",
"Upload files": "Tải lên tệp",
"Upload Progress": "Tiến trình Tải lên",
"URL Mode": "Chế độ URL",
"Use '#' in the prompt input to load and select your documents.": "Sử dụng '#' trong đầu vào prompt để tải và chọn tài liệu của bạn.",
"Use Gravatar": "Sử dụng Gravatar",
"user": "người dùng",
"User Permissions": "Quyền của Người dùng",
"Users": "Người dùng",
"Utilize": "Sử dụng",
"Valid time units:": "Đơn vị thời gian hợp lệ:",
"variable": "biến",
"variable to have them replaced with clipboard content.": "biến để có chúng được thay thế bằng nội dung clipboard.",
"Version": "Phiên bản",
"Web": "Web",
"WebUI Add-ons": "Tiện ích WebUI",
"WebUI Settings": "Cài đặt WebUI",
"WebUI will make requests to": "WebUI sẽ thực hiện các yêu cầu đến",
"What's New in": "Có gì mới trong",
"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Khi chế độ lịch sử trò chuyện đã tắt, các cuộc trò chuyện mới trên trình duyệt này sẽ không xuất hiện trên bất kỳ thiết bị nào của bạn.",
"Whisper (Local)": "Whisper (Local)",
"Write a prompt suggestion (e.g. Who are you?)": "Hãy viết một prompt (vd: Bạn là ai?)",
"Write a summary in 50 words that summarizes [topic or keyword].": "Viết một tóm tắt trong vòng 50 từ cho [chủ đề hoặc từ khóa].",
"You": "Bạn",
"You're a helpful assistant.": "Bạn là một trợ lý hữu ích.",
"You're now logged in.": "Bạn đã đăng nhập."
}
\ No newline at end of file
{
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' 或 '-1' 表示无过期时间。",
"(Beta)": "(测试版)",
"(e.g. `sh webui.sh --api`)": "(例如 `sh webui.sh --api`)",
"(latest)": "",
"{{modelName}} is thinking...": "{{modelName}} 正在思考...",
"{{webUIName}} Backend Required": "需要 {{webUIName}} 后端",
"a user": "",
"About": "关于",
"Account": "账户",
"Action": "操作",
"Add a model": "添加模型",
"Add a model tag name": "添加模型标签名称",
"Add a short description about what this modelfile does": "添加关于此模型文件功能的简短描述",
"Add a short title for this prompt": "为这个提示添加一个简短的标题",
"Add a tag": "",
"Add Docs": "添加文档",
"Add Files": "添加文件",
"Add message": "添加消息",
"add tags": "添加标签",
"Adjusting these settings will apply changes universally to all users.": "调整这些设置将对所有用户普遍应用更改。",
"admin": "管理员",
"Admin Panel": "管理员面板",
"Admin Settings": "管理员设置",
"Advanced Parameters": "高级参数",
"all": "所有",
"All Users": "所有用户",
"Allow": "允许",
"Allow Chat Deletion": "允许删除聊天",
"alphanumeric characters and hyphens": "字母数字字符和连字符",
"Already have an account?": "已经有账户了吗?",
"an assistant": "",
"and": "和",
"API Base URL": "API 基础 URL",
"API Key": "API 密钥",
"API RPM": "API RPM",
"are allowed - Activate this command by typing": "被允许 - 通过输入激活此命令",
"Are you sure?": "你确定吗?",
"Audio": "音频",
"Auto-playback response": "自动播放回应",
"Auto-send input after 3 sec.": "3秒后自动发送输入",
"AUTOMATIC1111 Base URL": "AUTOMATIC1111 基础 URL",
"AUTOMATIC1111 Base URL is required.": "需要 AUTOMATIC1111 基础 URL。",
"available!": "可用!",
"Back": "返回",
"Builder Mode": "构建者模式",
"Cancel": "取消",
"Categories": "分类",
"Change Password": "更改密码",
"Chat": "聊天",
"Chat History": "聊天历史",
"Chat History is off for this browser.": "此浏览器已关闭聊天历史。",
"Chats": "聊天",
"Check Again": "再次检查",
"Check for updates": "检查更新",
"Checking for updates...": "正在检查更新...",
"Choose a model before saving...": "保存前选择一个模型...",
"Chunk Overlap": "块重叠",
"Chunk Params": "块参数",
"Chunk Size": "块大小",
"Click here for help.": "点击这里获取帮助。",
"Click here to check other modelfiles.": "点击这里检查其他模型文件。",
"Click here to select": "点击这里选择",
"Click here to select documents.": "点击这里选择文档。",
"click here.": "点击这里。",
"Click on the user role button to change a user's role.": "点击用户角色按钮以更改用户的角色。",
"Close": "关闭",
"Collection": "收藏",
"Command": "命令",
"Confirm Password": "确认密码",
"Connections": "连接",
"Content": "内容",
"Context Length": "上下文长度",
"Conversation Mode": "对话模式",
"Copy last code block": "复制最后一个代码块",
"Copy last response": "复制最后一次回复",
"Copying to clipboard was successful!": "复制到剪贴板成功!",
"Create a concise, 3-5 word phrase as a header for the following query, strictly adhering to the 3-5 word limit and avoiding the use of the word 'title':": "为以下查询创建一个简洁的、3-5个词的短语作为标题,严格遵守3-5个词的限制并避免使用“标题”一词:",
"Create a modelfile": "创建模型文件",
"Create Account": "创建账户",
"Created at": "创建于",
"Created by": "创建者",
"Current Model": "当前模型",
"Current Password": "当前密码",
"Custom": "自定义",
"Customize Ollama models for a specific purpose": "为特定目的定制Ollama模型",
"Dark": "暗色",
"Database": "数据库",
"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
"Default": "默认",
"Default (Automatic1111)": "默认(Automatic1111)",
"Default (Web API)": "默认(Web API)",
"Default model updated": "默认模型已更新",
"Default Prompt Suggestions": "默认提示词建议",
"Default User Role": "默认用户角色",
"delete": "删除",
"Delete a model": "删除一个模型",
"Delete chat": "删除聊天",
"Delete Chats": "删除聊天记录",
"Deleted {{deleteModelTag}}": "已删除{{deleteModelTag}}",
"Deleted {tagName}": "已删除{tagName}",
"Description": "描述",
"Desktop Notifications": "桌面通知",
"Disabled": "禁用",
"Discover a modelfile": "探索模型文件",
"Discover a prompt": "探索提示词",
"Discover, download, and explore custom prompts": "发现、下载并探索自定义提示词",
"Discover, download, and explore model presets": "发现、下载并探索模型预设",
"Display the username instead of You in the Chat": "在聊天中显示用户名而不是“您”",
"Document": "文档",
"Document Settings": "文档设置",
"Documents": "文档",
"does not make any external connections, and your data stays securely on your locally hosted server.": "不进行任何外部连接,您的数据安全地存储在您的本地服务器上。",
"Don't Allow": "不允许",
"Don't have an account?": "没有账户?",
"Download as a File": "下载为文件",
"Download Database": "下载数据库",
"Drop any files here to add to the conversation": "将任何文件拖到这里以添加到对话中",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "例如 '30s','10m'。有效的时间单位是's', 'm', 'h'。",
"Edit Doc": "编辑文档",
"Edit User": "编辑用户",
"Email": "电子邮件",
"Enable Chat History": "启用聊天历史",
"Enable New Sign Ups": "启用新注册",
"Enabled": "启用",
"Enter {{role}} message here": "",
"Enter API Key": "",
"Enter Chunk Overlap": "",
"Enter Chunk Size": "",
"Enter Image Size (e.g. 512x512)": "",
"Enter LiteLLM API Base URL (litellm_params.api_base)": "",
"Enter LiteLLM API Key (litellm_params.api_key)": "",
"Enter LiteLLM API RPM (litellm_params.rpm)": "",
"Enter LiteLLM Model (litellm_params.model)": "",
"Enter Max Tokens (litellm_params.max_tokens)": "",
"Enter model tag (e.g. {{modelTag}})": "",
"Enter Number of Steps (e.g. 50)": "",
"Enter stop sequence": "输入停止序列",
"Enter Top K": "",
"Enter URL (e.g. http://127.0.0.1:7860/)": "",
"Enter Your Email": "输入您的电子邮件",
"Enter Your Full Name": "输入您的全名",
"Enter Your Password": "输入您的密码",
"Experimental": "实验性",
"Export All Chats (All Users)": "导出所有聊天(所有用户)",
"Export Chats": "导出聊天",
"Export Documents Mapping": "导出文档映射",
"Export Modelfiles": "导出模型文件",
"Export Prompts": "导出提示词",
"Failed to read clipboard contents": "无法读取剪贴板内容",
"File Mode": "文件模式",
"File not found.": "文件未找到。",
"Focus chat input": "聚焦聊天输入",
"Format your variables using square brackets like this:": "使用这样的方括号格式化你的变量:",
"From (Base Model)": "来自(基础模型)",
"Full Screen Mode": "全屏模式",
"General": "通用",
"General Settings": "通用设置",
"Hello, {{name}}": "你好,{{name}}",
"Hide": "隐藏",
"Hide Additional Params": "隐藏额外参数",
"How can I help you today?": "今天我如何能帮到你?",
"Image Generation (Experimental)": "图像生成(实验性)",
"Image Generation Engine": "图像生成引擎",
"Image Settings": "图像设置",
"Images": "图像",
"Import Chats": "导入聊天",
"Import Documents Mapping": "导入文档映射",
"Import Modelfiles": "导入模型文件",
"Import Prompts": "导入提示",
"Include `--api` flag when running stable-diffusion-webui": "运行stable-diffusion-webui时包含`--api`标志",
"Interface": "界面",
"join our Discord for help.": "加入我们的Discord寻求帮助。",
"JSON": "JSON",
"JWT Expiration": "JWT过期",
"JWT Token": "JWT令牌",
"Keep Alive": "保持活动",
"Keyboard shortcuts": "键盘快捷键",
"Language": "语言",
"Light": "浅色",
"Listening...": "监听中...",
"LLMs can make mistakes. Verify important information.": "大型语言模型可能会犯错。验证重要信息。",
"Made by OpenWebUI Community": "由OpenWebUI社区制作",
"Make sure to enclose them with": "确保将它们包含在内",
"Manage LiteLLM Models": "管理LiteLLM模型",
"Manage Models": "",
"Manage Ollama Models": "管理Ollama模型",
"Max Tokens": "最大令牌数",
"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "最多可以同时下载3个模型。请稍后再试。",
"Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"MMMM DD, YYYY": "MMMM DD, YYYY",
"Model '{{modelName}}' has been successfully downloaded.": "模型'{{modelName}}'已成功下载。",
"Model '{{modelTag}}' is already in queue for downloading.": "模型'{{modelTag}}'已在下载队列中。",
"Model {{modelId}} not found": "未找到模型{{modelId}}",
"Model {{modelName}} already exists.": "模型{{modelName}}已存在。",
"Model Name": "模型名称",
"Model not selected": "未选择模型",
"Model Tag Name": "模型标签名称",
"Model Whitelisting": "",
"Model(s) Whitelisted": "",
"Modelfile": "模型文件",
"Modelfile Advanced Settings": "模型文件高级设置",
"Modelfile Content": "模型文件内容",
"Modelfiles": "模型文件",
"Models": "模型",
"My Documents": "我的文档",
"My Modelfiles": "我的模型文件",
"My Prompts": "我的提示",
"Name": "名称",
"Name Tag": "名称标签",
"Name your modelfile": "命名你的模型文件",
"New Chat": "新聊天",
"New Password": "新密码",
"Not sure what to add?": "不确定要添加什么?",
"Not sure what to write? Switch to": "不确定写什么?切换到",
"Off": "关闭",
"Okay, Let's Go!": "好的,我们开始吧!",
"Ollama Base URL": "Ollama 基础 URL",
"Ollama Version": "Ollama 版本",
"On": "开",
"Only": "仅",
"Only alphanumeric characters and hyphens are allowed in the command string.": "命令字符串中只允许使用字母数字字符和连字符。",
"Oops! Hold tight! Your files are still in the processing oven. We're cooking them up to perfection. Please be patient and we'll let you know once they're ready.": "哎呀!请稍等!您的文件仍在处理中。我们正在将它们做得尽善尽美,请耐心等待,一旦准备好我们会通知您。",
"Oops! Looks like the URL is invalid. Please double-check and try again.": "哎呀!看起来 URL 无效。请仔细检查后再试一次。",
"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "哎呀!您正在使用不支持的方法(仅限前端)。请从后端提供 WebUI。",
"Open": "打开",
"Open AI": "Open AI",
"Open AI (Dall-E)": "Open AI (Dall-E)",
"Open new chat": "打开新聊天",
"OpenAI API": "OpenAI API",
"OpenAI API Key": "OpenAI API 密钥",
"OpenAI API Key is required.": "需要 OpenAI API 密钥。",
"or": "或",
"Parameters": "参数",
"Password": "密码",
"PDF Extract Images (OCR)": "",
"pending": "待定",
"Permission denied when accessing microphone: {{error}}": "访问麦克风时权限被拒绝:{{error}}",
"Playground": "游乐场",
"Profile": "个人资料",
"Prompt Content": "提示词内容",
"Prompt suggestions": "提示词建议",
"Prompts": "提示词",
"Pull a model from Ollama.com": "从 Ollama.com 拉取一个模型",
"Pull Progress": "拉取进度",
"Query Params": "查询参数",
"RAG Template": "RAG 模板",
"Raw Format": "原始格式",
"Record voice": "录音",
"Redirecting you to OpenWebUI Community": "正在将您重定向到 OpenWebUI 社区",
"Release Notes": "发布说明",
"Repeat Last N": "重复最后 N 次",
"Repeat Penalty": "重复惩罚",
"Request Mode": "请求模式",
"Reset Vector Storage": "重置向量存储",
"Response AutoCopy to Clipboard": "响应自动复制到剪贴板",
"Role": "角色",
"Rosé Pine": "Rosé Pine",
"Rosé Pine Dawn": "Rosé Pine Dawn",
"Save": "保存",
"Save & Create": "保存并创建",
"Save & Submit": "保存并提交",
"Save & Update": "保存并更新",
"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "直接将聊天记录保存到浏览器存储中不再受支持。请点击下面的按钮下载并删除您的聊天记录。别担心,您可以通过轻松地将聊天记录重新导入到后端",
"Scan": "扫描",
"Scan complete!": "扫描完成!",
"Scan for documents from {{path}}": "从 {{path}} 扫描文档",
"Search": "搜索",
"Search Documents": "搜索文档",
"Search Prompts": "搜索提示词",
"See readme.md for instructions": "查看 readme.md 以获取说明",
"See what's new": "查看最新内容",
"Seed": "种子",
"Select a mode": "选择一个模式",
"Select a model": "选择一个模型",
"Select an Ollama instance": "",
"Send a Message": "发送消息",
"Send message": "发送消息",
"Server connection verified": "服务器连接已验证",
"Set as default": "设为默认",
"Set Default Model": "设置默认模型",
"Set Image Size": "设置图片大小",
"Set Steps": "设置步骤",
"Set Title Auto-Generation Model": "设置标题自动生成模型",
"Set Voice": "设置声音",
"Settings": "设置",
"Settings saved successfully!": "",
"Share to OpenWebUI Community": "分享到OpenWebUI社区",
"short-summary": "简短总结",
"Show": "显示",
"Show Additional Params": "显示额外参数",
"Show shortcuts": "显示快捷方式",
"sidebar": "侧边栏",
"Sign in": "登录",
"Sign Out": "登出",
"Sign up": "注册",
"Speech recognition error: {{error}}": "语音识别错误:{{error}}",
"Speech-to-Text Engine": "语音转文字引擎",
"SpeechRecognition API is not supported in this browser.": "此浏览器不支持SpeechRecognition API。",
"Stop Sequence": "停止序列",
"STT Settings": "语音转文字设置",
"Submit": "提交",
"Success": "成功",
"Successfully updated.": "成功更新。",
"Sync All": "同步所有",
"System": "系统",
"System Prompt": "系统提示",
"Tags": "标签",
"Temperature": "温度",
"Template": "模板",
"Text Completion": "文本完成",
"Text-to-Speech Engine": "文本转语音引擎",
"Tfs Z": "Tfs Z",
"Theme": "主题",
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "这确保了您宝贵的对话被安全保存到后端数据库中。谢谢!",
"This setting does not sync across browsers or devices.": "此设置不会在浏览器或设备之间同步。",
"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "提示:在每次替换后,在聊天输入中按Tab键可以连续更新多个变量槽。",
"Title": "标题",
"Title Auto-Generation": "标题自动生成",
"Title Generation Prompt": "标题生成提示",
"to": "到",
"To access the available model names for downloading,": "要访问可下载的模型名称,",
"To access the GGUF models available for downloading,": "要访问可下载的GGUF模型,",
"to chat input.": "到聊天输入。",
"Toggle settings": "切换设置",
"Toggle sidebar": "切换侧边栏",
"Top K": "Top K",
"Top P": "Top P",
"Trouble accessing Ollama?": "访问Ollama时遇到问题?",
"TTS Settings": "文本转语音设置",
"Type Hugging Face Resolve (Download) URL": "输入Hugging Face解析(下载)URL",
"Uh-oh! There was an issue connecting to {{provider}}.": "哦哦!连接到{{provider}}时出现问题。",
"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "未知文件类型'{{file_type}}',但接受并视为纯文本",
"Update password": "",
"Upload a GGUF model": "上传一个GGUF模型",
"Upload files": "上传文件",
"Upload Progress": "上传进度",
"URL Mode": "URL模式",
"Use '#' in the prompt input to load and select your documents.": "在提示输入中使用'#'来加载和选择你的文档。",
"Use Gravatar": "使用Gravatar",
"user": "用户",
"User Permissions": "用户权限",
"Users": "用户",
"Utilize": "利用",
"Valid time units:": "有效时间单位:",
"variable": "变量",
"variable to have them replaced with clipboard content.": "变量将被剪贴板内容替换。",
"Version": "",
"Web": "网页",
"WebUI Add-ons": "WebUI 插件",
"WebUI Settings": "WebUI 设置",
"WebUI will make requests to": "",
"What’s New in": "最新变化",
"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "当历史记录被关闭时,这个浏览器上的新聊天不会出现在你任何设备的历史记录中。",
"Whisper (Local)": "私语(本地)",
"Write a prompt suggestion (e.g. Who are you?)": "写一个提示建议(例如:你是谁?)",
"Write a summary in 50 words that summarizes [topic or keyword].": "用50个字写一个总结[主题或关键词]。",
"You": "你",
"You're a helpful assistant.": "你是一个有帮助的助手。",
"You're now logged in.": "你现在已经登录了。"
}
{
"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' 或 '-1' 表示無期限。",
"(Beta)": "(測試版)",
"(e.g. `sh webui.sh --api`)": "(例如 `sh webui.sh --api`)",
"(latest)": "",
"{{modelName}} is thinking...": "{{modelName}} 正在思考...",
"{{webUIName}} Backend Required": "需要 {{webUIName}} 後台",
"a user": "",
"About": "關於",
"Account": "帳號",
"Action": "動作",
"Add a model": "新增模型",
"Add a model tag name": "新增模型標籤名稱",
"Add a short description about what this modelfile does": "新增關於此模型文件功能的簡短描述",
"Add a short title for this prompt": "為此提示新增一個簡短的標題",
"Add a tag": "新增標籤",
"Add Docs": "新增文件",
"Add Files": "新增檔案",
"Add message": "新增訊息",
"add tags": "新增標籤",
"Adjusting these settings will apply changes universally to all users.": "調整這些設定將對所有使用者進行更改。",
"admin": "管理員",
"Admin Panel": "管理員控制台",
"Admin Settings": "管理設定",
"Advanced Parameters": "進階參數",
"all": "所有",
"All Users": "所有使用者",
"Allow": "允許",
"Allow Chat Deletion": "允許刪除聊天",
"alphanumeric characters and hyphens": "字母數字字符和連字符",
"Already have an account?": "已經有帳號了嗎?",
"an assistant": "",
"and": "和",
"API Base URL": "API 基本 URL",
"API Key": "API 金鑰",
"API RPM": "API RPM",
"are allowed - Activate this command by typing": "是允許的 - 透過輸入來啟動此命令",
"Are you sure?": "你確定嗎?",
"Audio": "音訊",
"Auto-playback response": "自動播放回答",
"Auto-send input after 3 sec.": "3秒後自動傳送輸入內容",
"AUTOMATIC1111 Base URL": "AUTOMATIC1111 基本 URL",
"AUTOMATIC1111 Base URL is required.": "需要 AUTOMATIC1111 基本 URL",
"available!": "可以使用!",
"Back": "返回",
"Builder Mode": "建構模式",
"Cancel": "取消",
"Categories": "分類",
"Change Password": "修改密碼",
"Chat": "聊天",
"Chat History": "聊天歷史",
"Chat History is off for this browser.": "此瀏覽器已關閉聊天歷史。",
"Chats": "聊天",
"Check Again": "重新檢查",
"Check for updates": "檢查更新",
"Checking for updates...": "正在檢查更新...",
"Choose a model before saving...": "儲存前選擇一個模型...",
"Chunk Overlap": "區塊重疊",
"Chunk Params": "區塊參數",
"Chunk Size": "區塊大小",
"Click here for help.": "點擊這裡尋找幫助。",
"Click here to check other modelfiles.": "點擊這裡檢查其他模型文件。",
"Click here to select": "點擊這裡選擇",
"Click here to select documents.": "點擊這裡選擇文件。",
"click here.": "點擊這裡。",
"Click on the user role button to change a user's role.": "點擊使用者角色按鈕以更改使用者的角色。",
"Close": "關閉",
"Collection": "收藏",
"Command": "命令",
"Confirm Password": "確認密碼",
"Connections": "連線",
"Content": "內容",
"Context Length": "上下文長度",
"Conversation Mode": "對話模式",
"Copy last code block": "複製最後一個程式碼區塊",
"Copy last response": "複製最後一個回答",
"Copying to clipboard was successful!": "成功複製到剪貼簿!",
"Create a concise, 3-5 word phrase as a header for the following query, strictly adhering to the 3-5 word limit and avoiding the use of the word 'title':": "為以下的查詢建立一個簡潔、3-5 個詞的短語作為標題,嚴格遵守 3-5 個詞的限制,避免使用「標題」這個詞:",
"Create a modelfile": "建立一個模型文件",
"Create Account": "建立帳號",
"Created at": "建立於",
"Created by": "建立者",
"Current Model": "目前模型",
"Current Password": "目前密碼",
"Custom": "自訂",
"Customize Ollama models for a specific purpose": "為特定目的自訂 Ollama 模型",
"Dark": "暗色",
"Database": "資料庫",
"DD/MM/YYYY HH:mm": "DD/MM/YYYY HH:mm",
"Default": "預設",
"Default (Automatic1111)": "預設(Automatic1111)",
"Default (Web API)": "預設(Web API)",
"Default model updated": "預設模型已更新",
"Default Prompt Suggestions": "預設提示建議",
"Default User Role": "預設使用者角色",
"delete": "刪除",
"Delete a model": "刪除一個模型",
"Delete chat": "刪除聊天",
"Delete Chats": "刪除聊天",
"Deleted {{deleteModelTag}}": "已刪除 {{deleteModelTag}}",
"Deleted {tagName}": "已刪除 {tagName}",
"Description": "描述",
"Desktop Notifications": "桌面通知",
"Disabled": "已停用",
"Discover a modelfile": "發現一個模型文件",
"Discover a prompt": "發現一個提示",
"Discover, download, and explore custom prompts": "發現、下載並探索自訂提示",
"Discover, download, and explore model presets": "發現、下載並探索模型預設",
"Display the username instead of You in the Chat": "在聊天中顯示使用者名稱而不是「你」",
"Document": "文件",
"Document Settings": "文件設定",
"Documents": "文件",
"does not make any external connections, and your data stays securely on your locally hosted server.": "不會與外部溝通,你的數據安全地留在你的本機伺服器上。",
"Don't Allow": "不允許",
"Don't have an account?": "沒有帳號?",
"Download as a File": "下載為文件",
"Download Database": "下載資料庫",
"Drop any files here to add to the conversation": "拖拽文件到此處以新增至對話",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "例如 '30s', '10m'。有效的時間單位為 's', 'm', 'h'。",
"Edit Doc": "編輯文件",
"Edit User": "編輯使用者",
"Email": "電子郵件",
"Enable Chat History": "啟用聊天歷史",
"Enable New Sign Ups": "允許新使用者註冊",
"Enabled": "已啟用",
"Enter {{role}} message here": "在這裡輸入 {{role}} 訊息",
"Enter API Key": "輸入 API 金鑰",
"Enter Chunk Overlap": "輸入區塊重疊",
"Enter Chunk Size": "輸入區塊大小",
"Enter Image Size (e.g. 512x512)": "輸入圖片大小(例如 512x512)",
"Enter LiteLLM API Base URL (litellm_params.api_base)": "輸入 LiteLLM API 基本 URL(litellm_params.api_base)",
"Enter LiteLLM API Key (litellm_params.api_key)": "輸入 LiteLLM API 金鑰(litellm_params.api_key)",
"Enter LiteLLM API RPM (litellm_params.rpm)": "輸入 LiteLLM API RPM(litellm_params.rpm)",
"Enter LiteLLM Model (litellm_params.model)": "輸入 LiteLLM 模型(litellm_params.model)",
"Enter Max Tokens (litellm_params.max_tokens)": "輸入最大令牌數(litellm_params.max_tokens)",
"Enter model tag (e.g. {{modelTag}})": "輸入模型標籤(例如 {{modelTag}})",
"Enter Number of Steps (e.g. 50)": "輸入步數(例如 50)",
"Enter stop sequence": "輸入停止序列",
"Enter Top K": "輸入 Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "輸入 URL(例如 http://127.0.0.1:7860/)",
"Enter Your Email": "輸入你的電子郵件",
"Enter Your Full Name": "輸入你的全名",
"Enter Your Password": "輸入你的密碼",
"Experimental": "實驗性",
"Export All Chats (All Users)": "匯出所有聊天(所有使用者)",
"Export Chats": "匯出聊天",
"Export Documents Mapping": "匯出文件映射",
"Export Modelfiles": "匯出模型文件",
"Export Prompts": "匯出提示",
"Failed to read clipboard contents": "無法讀取剪貼簿內容",
"File Mode": "文件模式",
"File not found.": "找不到文件。",
"Focus chat input": "聚焦聊天輸入框",
"Format your variables using square brackets like this:": "使用這樣的方括號來格式化你的變數:",
"From (Base Model)": "來自(基礎模型)",
"Full Screen Mode": "全螢幕模式",
"General": "常用",
"General Settings": "常用設定",
"Hello, {{name}}": "你好, {{name}}",
"Hide": "隱藏",
"Hide Additional Params": "隱藏附加參數",
"How can I help you today?": "今天能為你做什麼?",
"Image Generation (Experimental)": "圖片產生(實驗性)",
"Image Generation Engine": "圖片產生引擎",
"Image Settings": "圖片設定",
"Images": "圖片",
"Import Chats": "匯入聊天",
"Import Documents Mapping": "匯入文件映射",
"Import Modelfiles": "匯入模型文件",
"Import Prompts": "匯入提示",
"Include `--api` flag when running stable-diffusion-webui": "執行 stable-diffusion-webui 時包含 `--api` 標誌",
"Interface": "介面",
"join our Discord for help.": "加入我們的 Discord 尋找幫助。",
"JSON": "JSON",
"JWT Expiration": "JWT 過期",
"JWT Token": "JWT 令牌",
"Keep Alive": "保持活躍",
"Keyboard shortcuts": "鍵盤快速鍵",
"Language": "語言",
"Light": "亮色",
"Listening...": "正在聽取...",
"LLMs can make mistakes. Verify important information.": "LLM 可能會產生錯誤。請驗證重要資訊。",
"Made by OpenWebUI Community": "由 OpenWebUI 社區製作",
"Make sure to enclose them with": "確保用...圍起來",
"Manage LiteLLM Models": "管理 LiteLLM 模型",
"Manage Models": "",
"Manage Ollama Models": "管理 Ollama 模型",
"Max Tokens": "最大令牌數",
"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "最多可以同時下載 3 個模型。請稍後再試。",
"Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta",
"Mirostat Tau": "Mirostat Tau",
"MMMM DD, YYYY": "MMMM DD, YYYY",
"Model '{{modelName}}' has been successfully downloaded.": "'{{modelName}}' 模型已成功下載。",
"Model '{{modelTag}}' is already in queue for downloading.": "'{{modelTag}}' 模型已經在下載佇列中。",
"Model {{modelId}} not found": "找不到 {{modelId}} 模型",
"Model {{modelName}} already exists.": "模型 {{modelName}} 已存在。",
"Model Name": "模型名稱",
"Model not selected": "未選擇模型",
"Model Tag Name": "模型標籤名稱",
"Model Whitelisting": "白名單模型",
"Model(s) Whitelisted": "模型已加入白名單",
"Modelfile": "模型文件",
"Modelfile Advanced Settings": "模型文件進階設定",
"Modelfile Content": "模型文件內容",
"Modelfiles": "模型文件",
"Models": "模型",
"My Documents": "我的文件",
"My Modelfiles": "我的模型文件",
"My Prompts": "我的提示",
"Name": "名稱",
"Name Tag": "名稱標籤",
"Name your modelfile": "命名你的模型文件",
"New Chat": "新增聊天",
"New Password": "新密碼",
"Not sure what to add?": "不確定要新增什麼嗎?",
"Not sure what to write? Switch to": "不確定要寫什麼?切換到",
"Off": "關閉",
"Okay, Let's Go!": "好的,啟動吧!",
"Ollama Base URL": "Ollama 基本 URL",
"Ollama Version": "Ollama 版本",
"On": "開啟",
"Only": "僅有",
"Only alphanumeric characters and hyphens are allowed in the command string.": "命令字符串中只允許使用字母數字字符和連字符。",
"Oops! Hold tight! Your files are still in the processing oven. We're cooking them up to perfection. Please be patient and we'll let you know once they're ready.": "哎呀!請稍等!你的文件還在處理中。我們正最佳化文件,請耐心等待,一旦準備好,我們會通知你。",
"Oops! Looks like the URL is invalid. Please double-check and try again.": "哎呀!看起來 URL 無效。請仔細檢查後再試一次。",
"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "哎呀!你正在使用不支援的方法(僅有前台)。請從後台提供 WebUI。",
"Open": "開啟",
"Open AI": "Open AI",
"Open AI (Dall-E)": "Open AI (Dall-E)",
"Open new chat": "開啟新聊天",
"OpenAI API": "OpenAI API",
"OpenAI API Key": "OpenAI API 金鑰",
"OpenAI API Key is required.": "需要 OpenAI API 金鑰。",
"or": "或",
"Parameters": "參數",
"Password": "密碼",
"PDF Extract Images (OCR)": "PDF 圖像輸出(OCR 光學文字辨識)",
"pending": "等待中",
"Permission denied when accessing microphone: {{error}}": "存取麥克風時被拒絕權限: {{error}}",
"Playground": "AI 對話遊樂場",
"Profile": "個人資料",
"Prompt Content": "提示內容",
"Prompt suggestions": "提示建議",
"Prompts": "提示",
"Pull a model from Ollama.com": "從 Ollama.com 下載模型",
"Pull Progress": "下載進度",
"Query Params": "查詢參數",
"RAG Template": "RAG 範例",
"Raw Format": "原始格式",
"Record voice": "錄音",
"Redirecting you to OpenWebUI Community": "將你重新導向到 OpenWebUI 社群",
"Release Notes": "發布說明",
"Repeat Last N": "重複最後 N 次",
"Repeat Penalty": "重複懲罰",
"Request Mode": "請求模式",
"Reset Vector Storage": "重置向量儲存空間",
"Response AutoCopy to Clipboard": "自動複製回答到剪貼簿",
"Role": "角色",
"Rosé Pine": "玫瑰松",
"Rosé Pine Dawn": "黎明玫瑰松",
"Save": "儲存",
"Save & Create": "儲存並建立",
"Save & Submit": "儲存並送出",
"Save & Update": "儲存並更新",
"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "現已不支援將聊天紀錄儲存到瀏覽器儲存空間中。請點擊下面的按鈕下載並刪除你的聊天記錄。別擔心,你可以通過以下方式輕鬆地重新匯入你的聊天記錄到後台",
"Scan": "掃描",
"Scan complete!": "掃描完成!",
"Scan for documents from {{path}}": "從 {{path}} 掃描文件",
"Search": "搜尋",
"Search Documents": "搜尋文件",
"Search Prompts": "搜尋提示",
"See readme.md for instructions": "查看 readme.md 獲取指南",
"See what's new": "查看最新內容",
"Seed": "種子",
"Select a mode": "選擇模式",
"Select a model": "選擇一個模型",
"Select an Ollama instance": "選擇 Ollama 實例",
"Send a Message": "傳送訊息",
"Send message": "傳送訊息",
"Server connection verified": "已驗證伺服器連線",
"Set as default": "設為預設",
"Set Default Model": "設定預設模型",
"Set Image Size": "設定圖片大小",
"Set Steps": "設定步數",
"Set Title Auto-Generation Model": "設定標題自動產生模型",
"Set Voice": "設定語音",
"Settings": "設定",
"Settings saved successfully!": "成功儲存設定",
"Share to OpenWebUI Community": "分享到 OpenWebUI 社群",
"short-summary": "簡短摘要",
"Show": "顯示",
"Show Additional Params": "顯示額外參數",
"Show shortcuts": "顯示快速鍵",
"sidebar": "側邊欄",
"Sign in": "登入",
"Sign Out": "登出",
"Sign up": "註冊",
"Speech recognition error: {{error}}": "語音識別錯誤: {{error}}",
"Speech-to-Text Engine": "語音轉文字引擎",
"SpeechRecognition API is not supported in this browser.": "此瀏覽器不支持 SpeechRecognition API。",
"Stop Sequence": "停止序列",
"STT Settings": "語音轉文字設定",
"Submit": "提交",
"Success": "成功",
"Successfully updated.": "更新成功。",
"Sync All": "全部同步",
"System": "系統",
"System Prompt": "系統提示",
"Tags": "標籤",
"Temperature": "溫度",
"Template": "模板",
"Text Completion": "文字完成",
"Text-to-Speech Engine": "文字轉語音引擎",
"Tfs Z": "Tfs Z",
"Theme": "主題",
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "這確保你寶貴的對話安全地儲存到你的後台資料庫。謝謝!",
"This setting does not sync across browsers or devices.": "此設定不會在瀏覽器或裝置間同步。",
"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "提示:透過在每次替換後在聊天輸入中按 Tab 鍵連續更新多個變量。",
"Title": "標題",
"Title Auto-Generation": "標題自動產生",
"Title Generation Prompt": "標題產生提示",
"to": "到",
"To access the available model names for downloading,": "要存取可供下載的模型名稱,",
"To access the GGUF models available for downloading,": "要存取可供下載的 GGUF 模型,",
"to chat input.": "到聊天輸入。",
"Toggle settings": "切換設定",
"Toggle sidebar": "切換側邊欄",
"Top K": "Top K",
"Top P": "Top P",
"Trouble accessing Ollama?": "存取 Ollama 時遇到問題?",
"TTS Settings": "文字轉語音設定",
"Type Hugging Face Resolve (Download) URL": "輸入 Hugging Face 解析(下載)URL",
"Uh-oh! There was an issue connecting to {{provider}}.": "哎呀!連線到 {{provider}} 時出現問題。",
"Unknown File Type '{{file_type}}', but accepting and treating as plain text": "未知的文件類型 '{{file_type}}',但接受並視為純文字",
"Update password": "更新密碼",
"Upload a GGUF model": "上傳一個 GGUF 模型",
"Upload files": "上傳文件",
"Upload Progress": "上傳進度",
"URL Mode": "URL 模式",
"Use '#' in the prompt input to load and select your documents.": "使用 '#' 在提示輸入中以載入並選擇你的文件。",
"Use Gravatar": "使用 Gravatar",
"user": "使用者",
"User Permissions": "使用者權限",
"Users": "使用者",
"Utilize": "使用",
"Valid time units:": "有效時間單位:",
"variable": "變量",
"variable to have them replaced with clipboard content.": "變量將替換為剪貼簿內容。",
"Version": "版本",
"Web": "網頁",
"WebUI Add-ons": "WebUI 擴充套件",
"WebUI Settings": "WebUI 設定",
"WebUI will make requests to": "WebUI 將會存取",
"What’s New in": "全新內容",
"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "當歷史被關閉時,這個瀏覽器上的新聊天將不會出現在任何裝置的歷史記錄中。",
"Whisper (Local)": "Whisper(本機)",
"Write a prompt suggestion (e.g. Who are you?)": "寫一個提示建議(例如:你是誰?)",
"Write a summary in 50 words that summarizes [topic or keyword].": "寫一個50字的摘要來概括[主題或關鍵詞]。",
"You": "你",
"You're a helpful assistant.": "你是一個有幫助的助手。",
"You're now logged in.": "你已登入。"
}
import { APP_NAME } from '$lib/constants';
import { writable } from 'svelte/store';
// Backend
export const WEBUI_NAME = writable(APP_NAME);
export const config = writable(undefined);
export const user = writable(undefined);
// Frontend
export const theme = writable('dark');
export const theme = writable('system');
export const chatId = writable('');
......@@ -32,3 +34,4 @@ export const documents = writable([
export const settings = writable({});
export const showSettings = writable(false);
export const showChangelog = writable(false);
......@@ -101,11 +101,10 @@ export const copyToClipboard = (text) => {
);
};
export const checkVersion = (required, current) => {
// Returns true when current version is below required
export const compareVersion = (latest, current) => {
return current === '0.0.0'
? false
: current.localeCompare(required, undefined, {
: current.localeCompare(latest, undefined, {
numeric: true,
sensitivity: 'case',
caseFirst: 'upper'
......
export const RAGTemplate = (context: string, query: string) => {
let template = `Use the following context as your learned knowledge, inside <context></context> XML tags.
<context>
[context]
</context>
When answer to user:
- If you don't know, just say that you don't know.
- If you don't know when you are not sure, ask for clarification.
Avoid mentioning that you obtained the information from the context.
And answer according to the language of the user's question.
Given the context information, answer the query.
Query: [query]`;
import { getRAGTemplate } from '$lib/apis/rag';
export const RAGTemplate = async (token: string, context: string, query: string) => {
let template = await getRAGTemplate(token).catch(() => {
return `Use the following context as your learned knowledge, inside <context></context> XML tags.
<context>
[context]
</context>
When answer to user:
- If you don't know, just say that you don't know.
- If you don't know when you are not sure, ask for clarification.
Avoid mentioning that you obtained the information from the context.
And answer according to the language of the user's question.
Given the context information, answer the query.
Query: [query]`;
});
template = template.replace(/\[context\]/g, context);
template = template.replace(/\[query\]/g, query);
......
<script lang="ts">
import toast from 'svelte-french-toast';
import { toast } from 'svelte-sonner';
import { onMount, tick, getContext } from 'svelte';
import { openDB, deleteDB } from 'idb';
import { onMount, tick } from 'svelte';
import { goto } from '$app/navigation';
import fileSaver from 'file-saver';
const { saveAs } = fileSaver;
import { goto } from '$app/navigation';
import { getOllamaModels, getOllamaVersion } from '$lib/apis/ollama';
import { getModelfiles } from '$lib/apis/modelfiles';
import { getPrompts } from '$lib/apis/prompts';
import { getOpenAIModels } from '$lib/apis/openai';
import { getLiteLLMModels } from '$lib/apis/litellm';
import { getDocs } from '$lib/apis/documents';
import { getAllChatTags } from '$lib/apis/chats';
import {
user,
......@@ -21,43 +23,48 @@
modelfiles,
prompts,
documents,
tags
tags,
showChangelog,
config
} from '$lib/stores';
import { REQUIRED_OLLAMA_VERSION, WEBUI_API_BASE_URL } from '$lib/constants';
import { compareVersion } from '$lib/utils';
import SettingsModal from '$lib/components/chat/SettingsModal.svelte';
import Sidebar from '$lib/components/layout/Sidebar.svelte';
import { checkVersion } from '$lib/utils';
import ShortcutsModal from '$lib/components/chat/ShortcutsModal.svelte';
import { getDocs } from '$lib/apis/documents';
import { getAllChatTags } from '$lib/apis/chats';
import ChangelogModal from '$lib/components/ChangelogModal.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
const i18n = getContext('i18n');
let ollamaVersion = '';
let loaded = false;
let showShortcutsButtonElement: HTMLButtonElement;
let DB = null;
let localDBChats = [];
let showShortcuts = false;
const getModels = async () => {
let models = [];
models.push(
...(await getOllamaModels(localStorage.token).catch((error) => {
toast.error(error);
return [];
}))
);
// $settings.OPENAI_API_BASE_URL ?? 'https://api.openai.com/v1',
// $settings.OPENAI_API_KEY
const openAIModels = await getOpenAIModels(localStorage.token).catch((error) => {
console.log(error);
return null;
});
models.push(...(openAIModels ? [{ name: 'hr' }, ...openAIModels] : []));
let models = await Promise.all([
await getOllamaModels(localStorage.token).catch((error) => {
console.log(error);
return null;
}),
await getOpenAIModels(localStorage.token).catch((error) => {
console.log(error);
return null;
}),
await getLiteLLMModels(localStorage.token).catch((error) => {
console.log(error);
return null;
})
]);
models = models
.filter((models) => models)
.reduce((a, e, i, arr) => a.concat(e, ...(i < arr.length - 1 ? [{ name: 'hr' }] : [])), []);
return models;
};
......@@ -72,7 +79,7 @@
ollamaVersion = version;
console.log(ollamaVersion);
if (checkVersion(REQUIRED_OLLAMA_VERSION, ollamaVersion)) {
if (compareVersion(REQUIRED_OLLAMA_VERSION, ollamaVersion)) {
toast.error(`Ollama Version: ${ollamaVersion !== '' ? ollamaVersion : 'Not Detected'}`);
}
};
......@@ -92,17 +99,18 @@
if (localDBChats.length === 0) {
await deleteDB('Chats');
}
console.log('localdb', localDBChats);
}
console.log(DB);
} catch (error) {
// IndexedDB Not Found
console.log('IDB Not Found');
}
console.log();
await models.set(await getModels());
await tick();
await settings.set(JSON.parse(localStorage.getItem('settings') ?? '{}'));
await modelfiles.set(await getModelfiles(localStorage.token));
......@@ -115,8 +123,6 @@
await models.set(await getModels());
});
await setOllamaVersion();
document.addEventListener('keydown', function (event) {
const isCtrlPressed = event.ctrlKey || event.metaKey; // metaKey is for Cmd key on Mac
// Check if the Shift key is pressed
......@@ -178,10 +184,14 @@
if (isCtrlPressed && event.key === '/') {
event.preventDefault();
console.log('showShortcuts');
document.getElementById('show-shortcuts-button')?.click();
showShortcutsButtonElement.click();
}
});
if ($user.role === 'admin') {
showChangelog.set(localStorage.version !== $config.version);
}
await tick();
}
......@@ -191,15 +201,18 @@
{#if loaded}
<div class=" hidden lg:flex fixed bottom-0 right-0 px-3 py-3 z-10">
<button
id="show-shortcuts-button"
class="text-gray-600 dark:text-gray-300 bg-gray-300/20 w-6 h-6 flex items-center justify-center text-xs rounded-full"
on:click={() => {
showShortcuts = !showShortcuts;
}}
>
?
</button>
<Tooltip content="Help" placement="left">
<button
id="show-shortcuts-button"
bind:this={showShortcutsButtonElement}
class="text-gray-600 dark:text-gray-300 bg-gray-300/20 w-6 h-6 flex items-center justify-center text-xs rounded-full"
on:click={() => {
showShortcuts = !showShortcuts;
}}
>
?
</button>
</Tooltip>
</div>
<ShortcutsModal bind:show={showShortcuts} />
......@@ -229,7 +242,7 @@
location.href = '/';
}}
>
Check Again
{$i18n.t('Check Again')}
</button>
<button
......@@ -237,61 +250,7 @@
on:click={async () => {
localStorage.removeItem('token');
location.href = '/auth';
}}>Sign Out</button
>
</div>
</div>
</div>
</div>
</div>
{:else if checkVersion(REQUIRED_OLLAMA_VERSION, ollamaVersion ?? '0')}
<div class="fixed w-full h-full flex z-50">
<div
class="absolute w-full h-full backdrop-blur-md bg-white/20 dark:bg-gray-900/50 flex justify-center"
>
<div class="m-auto pb-44 flex flex-col justify-center">
<div class="max-w-md">
<div class="text-center dark:text-white text-2xl font-medium z-50">
Connection Issue or Update Needed
</div>
<div class=" mt-4 text-center text-sm dark:text-gray-200 w-full">
Oops! It seems like your Ollama needs a little attention. <br
class=" hidden sm:flex"
/>We've detected either a connection hiccup or observed that you're using an older
version. Ensure you're on the latest Ollama version
<br class=" hidden sm:flex" />(version
<span class=" dark:text-white font-medium">{REQUIRED_OLLAMA_VERSION} or higher</span
>) or check your connection.
<div class="mt-1 text-sm">
Trouble accessing Ollama?
<a
class=" text-black dark:text-white font-semibold underline"
href="https://github.com/ollama-webui/ollama-webui#troubleshooting"
target="_blank"
>
Click here for help.
</a>
</div>
</div>
<div class=" mt-6 mx-auto relative group w-fit">
<button
class="relative z-20 flex px-5 py-2 rounded-full bg-white border border-gray-100 dark:border-none hover:bg-gray-100 transition font-medium text-sm"
on:click={async () => {
location.href = '/';
// await setOllamaVersion();
}}
>
Check Again
</button>
<button
class="text-xs text-center w-full mt-2 text-gray-400 underline"
on:click={async () => {
await setOllamaVersion(REQUIRED_OLLAMA_VERSION);
}}>Close</button
}}>{$i18n.t('Sign Out')}</button
>
</div>
</div>
......@@ -310,12 +269,14 @@
</div>
<div class=" mt-4 text-center text-sm dark:text-gray-200 w-full">
Saving chat logs directly to your browser's storage is no longer supported. Please
take a moment to download and delete your chat logs by clicking the button below.
Don't worry, you can easily re-import your chat logs to the backend through <span
class="font-semibold dark:text-white">Settings > Chats > Import Chats</span
>. This ensures that your valuable conversations are securely saved to your backend
database. Thank you!
{$i18n.t(
"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through"
)}
<span class="font-semibold dark:text-white"
>{$i18n.t('Settings')} > {$i18n.t('Chats')} > {$i18n.t('Import Chats')}</span
>. {$i18n.t(
'This ensures that your valuable conversations are securely saved to your backend database. Thank you!'
)}
</div>
<div class=" mt-6 mx-auto relative group w-fit">
......@@ -341,7 +302,7 @@
class="text-xs text-center w-full mt-2 text-gray-400 underline"
on:click={async () => {
localDBChats = [];
}}>Close</button
}}>{$i18n.t('Close')}</button
>
</div>
</div>
......@@ -355,6 +316,7 @@
>
<Sidebar />
<SettingsModal bind:show={$showSettings} />
<ChangelogModal bind:show={$showChangelog} />
<slot />
</div>
</div>
......
<script lang="ts">
import { v4 as uuidv4 } from 'uuid';
import toast from 'svelte-french-toast';
import { toast } from 'svelte-sonner';
import { onMount, tick } from 'svelte';
import { onMount, tick, getContext } from 'svelte';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
......@@ -14,6 +14,7 @@
chats,
chatId,
config,
WEBUI_NAME,
tags as _tags
} from '$lib/stores';
import { copyToClipboard, splitStream } from '$lib/utils';
......@@ -36,11 +37,15 @@
import ModelSelector from '$lib/components/chat/ModelSelector.svelte';
import Navbar from '$lib/components/layout/Navbar.svelte';
import { RAGTemplate } from '$lib/utils/rag';
import { LITELLM_API_BASE_URL, OPENAI_API_BASE_URL } from '$lib/constants';
import { WEBUI_BASE_URL } from '$lib/constants';
const i18n = getContext('i18n');
let stopResponseFlag = false;
let autoScroll = true;
let processing = '';
let messagesContainerElement: HTMLDivElement;
let currentRequestId = null;
let selectedModels = [''];
......@@ -102,13 +107,8 @@
await cancelChatCompletion(localStorage.token, currentRequestId);
currentRequestId = null;
}
window.history.replaceState(history.state, '', `/`);
console.log('initNewChat');
await chatId.set('');
console.log($chatId);
autoScroll = true;
......@@ -119,8 +119,6 @@
currentId: null
};
console.log($config);
if ($page.url.searchParams.get('models')) {
selectedModels = $page.url.searchParams.get('models')?.split(',');
} else if ($settings?.models) {
......@@ -131,15 +129,23 @@
selectedModels = [''];
}
selectedModels = selectedModels.map((modelId) =>
$models.map((m) => m.id).includes(modelId) ? modelId : ''
);
let _settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
settings.set({
..._settings
});
const chatInput = document.getElementById('chat-textarea');
setTimeout(() => chatInput?.focus(), 0);
};
const scrollToBottom = () => {
const element = document.getElementById('messages-container');
element.scrollTop = element.scrollHeight;
if (messagesContainerElement) {
messagesContainerElement.scrollTop = messagesContainerElement.scrollHeight;
}
};
//////////////////////////
......@@ -149,8 +155,12 @@
const submitPrompt = async (userPrompt, _user = null) => {
console.log('submitPrompt', $chatId);
selectedModels = selectedModels.map((modelId) =>
$models.map((m) => m.id).includes(modelId) ? modelId : ''
);
if (selectedModels.includes('')) {
toast.error('Model not selected');
toast.error($i18n.t('Model not selected'));
} else if (messages.length != 0 && messages.at(-1).done != true) {
// Response not done
console.log('wait');
......@@ -160,7 +170,9 @@
) {
// Upload not done
toast.error(
`Oops! Hold tight! Your files are still in the processing oven. We're cooking them up to perfection. Please be patient and we'll let you know once they're ready.`
$i18n.t(
`Oops! Hold tight! Your files are still in the processing oven. We're cooking them up to perfection. Please be patient and we'll let you know once they're ready.`
)
);
} else {
// Reset chat message textarea height
......@@ -196,7 +208,7 @@
if ($settings.saveChatHistory ?? true) {
chat = await createNewChat(localStorage.token, {
id: $chatId,
title: 'New Chat',
title: $i18n.t('New Chat'),
models: selectedModels,
system: $settings.system ?? undefined,
options: {
......@@ -227,86 +239,42 @@
const sendPrompt = async (prompt, parentId) => {
const _chatId = JSON.parse(JSON.stringify($chatId));
const docs = messages
.filter((message) => message?.files ?? null)
.map((message) =>
message.files.filter((item) => item.type === 'doc' || item.type === 'collection')
)
.flat(1);
console.log(docs);
if (docs.length > 0) {
processing = 'Reading';
const query = history.messages[parentId].content;
let relevantContexts = await Promise.all(
docs.map(async (doc) => {
if (doc.type === 'collection') {
return await queryCollection(localStorage.token, doc.collection_names, query, 4).catch(
(error) => {
console.log(error);
return null;
}
);
} else {
return await queryDoc(localStorage.token, doc.collection_name, query, 4).catch(
(error) => {
console.log(error);
return null;
}
);
}
})
);
relevantContexts = relevantContexts.filter((context) => context);
const contextString = relevantContexts.reduce((a, context, i, arr) => {
return `${a}${context.documents.join(' ')}\n`;
}, '');
console.log(contextString);
history.messages[parentId].raContent = RAGTemplate(contextString, query);
history.messages[parentId].contexts = relevantContexts;
await tick();
processing = '';
}
await Promise.all(
selectedModels.map(async (model) => {
console.log(model);
const modelTag = $models.filter((m) => m.name === model).at(0);
// Create response message
let responseMessageId = uuidv4();
let responseMessage = {
parentId: parentId,
id: responseMessageId,
childrenIds: [],
role: 'assistant',
content: '',
model: model,
timestamp: Math.floor(Date.now() / 1000) // Unix epoch
};
// Add message to history and Set currentId to messageId
history.messages[responseMessageId] = responseMessage;
history.currentId = responseMessageId;
// Append messageId to childrenIds of parent message
if (parentId !== null) {
history.messages[parentId].childrenIds = [
...history.messages[parentId].childrenIds,
responseMessageId
];
}
selectedModels.map(async (modelId) => {
const model = $models.filter((m) => m.id === modelId).at(0);
if (model) {
// Create response message
let responseMessageId = uuidv4();
let responseMessage = {
parentId: parentId,
id: responseMessageId,
childrenIds: [],
role: 'assistant',
content: '',
model: model.id,
timestamp: Math.floor(Date.now() / 1000) // Unix epoch
};
// Add message to history and Set currentId to messageId
history.messages[responseMessageId] = responseMessage;
history.currentId = responseMessageId;
// Append messageId to childrenIds of parent message
if (parentId !== null) {
history.messages[parentId].childrenIds = [
...history.messages[parentId].childrenIds,
responseMessageId
];
}
if (modelTag?.external) {
await sendPromptOpenAI(model, prompt, responseMessageId, _chatId);
} else if (modelTag) {
await sendPromptOllama(model, prompt, responseMessageId, _chatId);
if (model?.external) {
await sendPromptOpenAI(model, prompt, responseMessageId, _chatId);
} else if (model) {
await sendPromptOllama(model, prompt, responseMessageId, _chatId);
}
} else {
toast.error(`Model ${model} not found`);
toast.error($i18n.t(`Model {{modelId}} not found`, { modelId }));
}
})
);
......@@ -315,6 +283,7 @@
};
const sendPromptOllama = async (model, userPrompt, responseMessageId, _chatId) => {
model = model.id;
const responseMessage = history.messages[responseMessageId];
// Wait until history/message have been updated
......@@ -333,15 +302,25 @@
...messages
]
.filter((message) => message)
.map((message, idx, arr) => ({
role: message.role,
content: arr.length - 2 !== idx ? message.content : message?.raContent ?? message.content,
...(message.files && {
images: message.files
.filter((file) => file.type === 'image')
.map((file) => file.url.slice(file.url.indexOf(',') + 1))
})
}));
.map((message, idx, arr) => {
// Prepare the base message object
const baseMessage = {
role: message.role,
content: arr.length - 2 !== idx ? message.content : message?.raContent ?? message.content
};
// Extract and format image URLs if any exist
const imageUrls = message.files
?.filter((file) => file.type === 'image')
.map((file) => file.url.slice(file.url.indexOf(',') + 1));
// Add images array only if it contains elements
if (imageUrls && imageUrls.length > 0 && message.role === 'user') {
baseMessage.images = imageUrls;
}
return baseMessage;
});
let lastImageIndex = -1;
......@@ -359,6 +338,13 @@
}
});
const docs = messages
.filter((message) => message?.files ?? null)
.map((message) =>
message.files.filter((item) => item.type === 'doc' || item.type === 'collection')
)
.flat(1);
const [res, controller] = await generateChatCompletion(localStorage.token, {
model: model,
messages: messagesBody,
......@@ -366,7 +352,8 @@
...($settings.options ?? {})
},
format: $settings.requestFormat ?? undefined,
keep_alive: $settings.keepAlive ?? undefined
keep_alive: $settings.keepAlive ?? undefined,
docs: docs.length > 0 ? docs : undefined
});
if (res && res.ok) {
......@@ -448,7 +435,7 @@
: `${model}`,
{
body: responseMessage.content,
icon: selectedModelfile?.imageUrl ?? '/favicon.png'
icon: selectedModelfile?.imageUrl ?? `${WEBUI_BASE_URL}/static/favicon.png`
}
);
}
......@@ -499,12 +486,18 @@
responseMessage.content = error.error;
}
} else {
toast.error(`Uh-oh! There was an issue connecting to Ollama.`);
responseMessage.content = `Uh-oh! There was an issue connecting to Ollama.`;
toast.error(
$i18n.t(`Uh-oh! There was an issue connecting to {{provider}}.`, { provider: 'Ollama' })
);
responseMessage.content = $i18n.t(`Uh-oh! There was an issue connecting to {{provider}}.`, {
provider: 'Ollama'
});
}
responseMessage.error = true;
responseMessage.content = `Uh-oh! There was an issue connecting to Ollama.`;
responseMessage.content = $i18n.t(`Uh-oh! There was an issue connecting to {{provider}}.`, {
provider: 'Ollama'
});
responseMessage.done = true;
messages = messages;
}
......@@ -526,54 +519,69 @@
const responseMessage = history.messages[responseMessageId];
scrollToBottom();
const res = await generateOpenAIChatCompletion(localStorage.token, {
model: model,
stream: true,
messages: [
$settings.system
? {
role: 'system',
content: $settings.system
}
: undefined,
...messages
]
.filter((message) => message)
.map((message, idx, arr) => ({
role: message.role,
...(message.files?.filter((file) => file.type === 'image').length > 0 ?? false
const docs = messages
.filter((message) => message?.files ?? null)
.map((message) =>
message.files.filter((item) => item.type === 'doc' || item.type === 'collection')
)
.flat(1);
console.log(docs);
const res = await generateOpenAIChatCompletion(
localStorage.token,
{
model: model.id,
stream: true,
messages: [
$settings.system
? {
content: [
{
type: 'text',
text:
arr.length - 1 !== idx
? message.content
: message?.raContent ?? message.content
},
...message.files
.filter((file) => file.type === 'image')
.map((file) => ({
type: 'image_url',
image_url: {
url: file.url
}
}))
]
role: 'system',
content: $settings.system
}
: {
content:
arr.length - 1 !== idx ? message.content : message?.raContent ?? message.content
})
})),
seed: $settings?.options?.seed ?? undefined,
stop: $settings?.options?.stop ?? undefined,
temperature: $settings?.options?.temperature ?? undefined,
top_p: $settings?.options?.top_p ?? undefined,
num_ctx: $settings?.options?.num_ctx ?? undefined,
frequency_penalty: $settings?.options?.repeat_penalty ?? undefined,
max_tokens: $settings?.options?.num_predict ?? undefined
});
: undefined,
...messages
]
.filter((message) => message)
.map((message, idx, arr) => ({
role: message.role,
...((message.files?.filter((file) => file.type === 'image').length > 0 ?? false) &&
message.role === 'user'
? {
content: [
{
type: 'text',
text:
arr.length - 1 !== idx
? message.content
: message?.raContent ?? message.content
},
...message.files
.filter((file) => file.type === 'image')
.map((file) => ({
type: 'image_url',
image_url: {
url: file.url
}
}))
]
}
: {
content:
arr.length - 1 !== idx ? message.content : message?.raContent ?? message.content
})
})),
seed: $settings?.options?.seed ?? undefined,
stop: $settings?.options?.stop ?? undefined,
temperature: $settings?.options?.temperature ?? undefined,
top_p: $settings?.options?.top_p ?? undefined,
num_ctx: $settings?.options?.num_ctx ?? undefined,
frequency_penalty: $settings?.options?.repeat_penalty ?? undefined,
max_tokens: $settings?.options?.num_predict ?? undefined,
docs: docs.length > 0 ? docs : undefined
},
model.source === 'litellm' ? `${LITELLM_API_BASE_URL}/v1` : `${OPENAI_API_BASE_URL}`
);
if (res && res.ok) {
const reader = res.body
......@@ -618,7 +626,7 @@
if ($settings.notificationEnabled && !document.hasFocus()) {
const notification = new Notification(`OpenAI ${model}`, {
body: responseMessage.content,
icon: '/favicon.png'
icon: `${WEBUI_BASE_URL}/static/favicon.png`
});
}
......@@ -662,12 +670,18 @@
}
}
} else {
toast.error(`Uh-oh! There was an issue connecting to ${model}.`);
responseMessage.content = `Uh-oh! There was an issue connecting to ${model}.`;
toast.error(
$i18n.t(`Uh-oh! There was an issue connecting to {{provider}}.`, { provider: model })
);
responseMessage.content = $i18n.t(`Uh-oh! There was an issue connecting to {{provider}}.`, {
provider: model
});
}
responseMessage.error = true;
responseMessage.content = `Uh-oh! There was an issue connecting to ${model}.`;
responseMessage.content = $i18n.t(`Uh-oh! There was an issue connecting to {{provider}}.`, {
provider: model
});
responseMessage.done = true;
messages = messages;
}
......@@ -681,7 +695,12 @@
if (messages.length == 2) {
window.history.replaceState(history.state, '', `/c/${_chatId}`);
await setChatTitle(_chatId, userPrompt);
if ($settings?.titleAutoGenerateModel) {
await generateChatTitle(_chatId, userPrompt);
} else {
await setChatTitle(_chatId, userPrompt);
}
}
};
......@@ -712,25 +731,26 @@
responseMessage.done = false;
await tick();
const modelTag = $models.filter((m) => m.name === responseMessage.model).at(0);
if (modelTag?.external) {
await sendPromptOpenAI(
responseMessage.model,
history.messages[responseMessage.parentId].content,
responseMessage.id,
_chatId
);
} else if (modelTag) {
await sendPromptOllama(
responseMessage.model,
history.messages[responseMessage.parentId].content,
responseMessage.id,
_chatId
);
} else {
toast.error(`Model ${model} not found`);
const model = $models.filter((m) => m.id === responseMessage.model).at(0);
if (model) {
if (model?.external) {
await sendPromptOpenAI(
model,
history.messages[responseMessage.parentId].content,
responseMessage.id,
_chatId
);
} else
await sendPromptOllama(
model,
history.messages[responseMessage.parentId].content,
responseMessage.id,
_chatId
);
}
} else {
toast.error($i18n.t(`Model {{modelId}} not found`, { modelId }));
}
};
......@@ -738,6 +758,10 @@
if ($settings.titleAutoGenerate ?? true) {
const title = await generateTitle(
localStorage.token,
$settings?.titleGenerationPrompt ??
$i18n.t(
"Create a concise, 3-5 word phrase as a header for the following query, strictly adhering to the 3-5 word limit and avoiding the use of the word 'title':"
) + ' {{prompt}}',
$settings?.titleAutoGenerateModel ?? selectedModels[0],
userPrompt
);
......@@ -790,14 +814,25 @@
};
</script>
<svelte:head>
<title>
{title
? `${title.length > 30 ? `${title.slice(0, 30)}...` : title} | ${$WEBUI_NAME}`
: `${$WEBUI_NAME}`}
</title>
</svelte:head>
<div class="h-screen max-h-[100dvh] w-full flex flex-col">
<Navbar {title} shareEnabled={messages.length > 0} {initNewChat} {tags} {addTag} {deleteTag} />
<div class="flex flex-col flex-auto">
<div
class=" pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0"
id="messages-container"
bind:this={messagesContainerElement}
on:scroll={(e) => {
autoScroll = e.target.scrollHeight - e.target.scrollTop <= e.target.clientHeight + 50;
autoScroll =
messagesContainerElement.scrollHeight - messagesContainerElement.scrollTop <=
messagesContainerElement.clientHeight + 5;
}}
>
<div
......@@ -805,10 +840,7 @@
? 'max-w-full'
: 'max-w-2xl md:px-0'} mx-auto w-full px-4"
>
<ModelSelector
bind:selectedModels
disabled={messages.length > 0 && !selectedModels.includes('')}
/>
<ModelSelector bind:selectedModels />
</div>
<div class=" h-full w-full flex flex-col py-8">
......
<script>
import { WEBUI_API_BASE_URL } from '$lib/constants';
import { config, user } from '$lib/stores';
import { WEBUI_NAME, config, user } from '$lib/stores';
import { goto } from '$app/navigation';
import { onMount } from 'svelte';
import { onMount, getContext } from 'svelte';
import toast from 'svelte-french-toast';
import { toast } from 'svelte-sonner';
import { updateUserRole, getUsers, deleteUserById } from '$lib/apis/users';
import { getSignUpEnabledStatus, toggleSignUpEnabledStatus } from '$lib/apis/auths';
import EditUserModal from '$lib/components/admin/EditUserModal.svelte';
import SettingsModal from '$lib/components/admin/SettingsModal.svelte';
const i18n = getContext('i18n');
let loaded = false;
let users = [];
......@@ -37,7 +39,7 @@
});
if (res) {
users = await getUsers(localStorage.token);
toast.success('Successfully updated');
toast.success($i18n.t('Successfully updated.'));
}
};
......@@ -61,6 +63,10 @@
});
</script>
<svelte:head>
<title>{$i18n.t('Admin Panel')} | {$WEBUI_NAME}</title>
</svelte:head>
{#key selectedUser}
<EditUserModal
bind:show={showEditUserModal}
......@@ -74,19 +80,23 @@
<SettingsModal bind:show={showSettingsModal} />
<div
class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white bg-white dark:bg-gray-900 font-mona"
>
<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white font-mona">
{#if loaded}
<div class="overflow-y-auto w-full flex justify-center">
<div class="w-full max-w-3xl px-10 md:px-16 flex flex-col">
<div class="py-10 w-full">
<div class=" flex flex-col justify-between w-full overflow-y-auto">
<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10">
<div class="w-full">
<div class=" flex flex-col justify-center">
<div class=" flex justify-between items-center">
<div class=" text-2xl font-semibold">Users ({users.length})</div>
<div class="flex items-center text-2xl font-semibold">
{$i18n.t('All Users')}
<div class="flex self-center w-[1px] h-6 mx-2.5 bg-gray-200 dark:bg-gray-700" />
<span class="text-lg font-medium text-gray-500 dark:text-gray-300"
>{users.length}</span
>
</div>
<div>
<button
class="flex items-center space-x-1 border border-gray-200 dark:border-gray-600 px-3 py-1 rounded-lg"
class="flex items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 transition"
type="button"
on:click={() => {
showSettingsModal = !showSettingsModal;
......@@ -105,49 +115,38 @@
/>
</svg>
<div class=" text-xs">Admin Settings</div>
<div class=" text-xs">{$i18n.t('Admin Settings')}</div>
</button>
</div>
</div>
<div class=" text-gray-500 text-xs font-medium mt-1">
Click on the user role cell in the table to change a user's role.
<div class=" text-gray-500 text-xs mt-1">
ⓘ {$i18n.t("Click on the user role button to change a user's role.")}
</div>
<hr class=" my-3 dark:border-gray-600" />
<div class="scrollbar-hidden relative overflow-x-auto whitespace-nowrap">
<table class="w-full text-sm text-left text-gray-500 dark:text-gray-400">
<table class="w-full text-sm text-left text-gray-500 dark:text-gray-400 table-auto">
<thead
class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400"
>
<tr>
<th scope="col" class="px-6 py-3"> Name </th>
<th scope="col" class="px-6 py-3"> Email </th>
<th scope="col" class="px-6 py-3"> Role </th>
<th scope="col" class="px-6 py-3"> Action </th>
<th scope="col" class="px-3 py-2"> {$i18n.t('Role')} </th>
<th scope="col" class="px-3 py-2"> {$i18n.t('Name')} </th>
<th scope="col" class="px-3 py-2"> {$i18n.t('Email')} </th>
<th scope="col" class="px-3 py-2"> {$i18n.t('Action')} </th>
</tr>
</thead>
<tbody>
{#each users as user}
<tr class="bg-white border-b dark:bg-gray-900 dark:border-gray-700">
<th
scope="row"
class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white w-fit"
>
<div class="flex flex-row">
<img
class=" rounded-full max-w-[30px] max-h-[30px] object-cover mr-4"
src={user.profile_image_url}
alt="user"
/>
<div class=" font-semibold self-center">{user.name}</div>
</div>
</th>
<td class="px-6 py-4"> {user.email} </td>
<td class="px-6 py-4">
<tr class="bg-white border-b dark:bg-gray-900 dark:border-gray-700 text-xs">
<td class="px-3 py-2 min-w-[7rem] w-28">
<button
class=" dark:text-white underline"
class=" flex items-center gap-2 text-xs px-3 py-0.5 rounded-lg {user.role ===
'admin' &&
'text-sky-600 dark:text-sky-200 bg-sky-200/30'} {user.role === 'user' &&
'text-green-600 dark:text-green-200 bg-green-200/30'} {user.role ===
'pending' && 'text-gray-600 dark:text-gray-200 bg-gray-200/30'}"
on:click={() => {
if (user.role === 'user') {
updateRoleHandler(user.id, 'admin');
......@@ -156,52 +155,77 @@
} else {
updateRoleHandler(user.id, 'pending');
}
}}>{user.role}</button
>
</td>
<td class="px-6 py-4 space-x-1 text-center flex justify-center">
<button
class="self-center w-fit text-sm p-1.5 border dark:border-gray-600 rounded-xl flex"
on:click={async () => {
showEditUserModal = !showEditUserModal;
selectedUser = user;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M11.013 2.513a1.75 1.75 0 0 1 2.475 2.474L6.226 12.25a2.751 2.751 0 0 1-.892.596l-2.047.848a.75.75 0 0 1-.98-.98l.848-2.047a2.75 2.75 0 0 1 .596-.892l7.262-7.261Z"
clip-rule="evenodd"
/>
</svg>
</button>
<button
class="self-center w-fit text-sm p-1.5 border dark:border-gray-600 rounded-xl flex"
on:click={async () => {
deleteUserHandler(user.id);
}}
<div
class="w-1 h-1 rounded-full {user.role === 'admin' &&
'bg-sky-600 dark:bg-sky-300'} {user.role === 'user' &&
'bg-green-600 dark:bg-green-300'} {user.role === 'pending' &&
'bg-gray-600 dark:bg-gray-300'}"
/>
{$i18n.t(user.role)}</button
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
</td>
<td class="px-3 py-2 font-medium text-gray-900 dark:text-white w-max">
<div class="flex flex-row w-max">
<img
class=" rounded-full w-6 h-6 object-cover mr-2.5"
src={user.profile_image_url}
alt="user"
/>
<div class=" font-medium self-center">{user.name}</div>
</div>
</td>
<td class=" px-3 py-2"> {user.email} </td>
<td class="px-3 py-2">
<div class="flex justify-start w-full">
<button
class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
on:click={async () => {
showEditUserModal = !showEditUserModal;
selectedUser = user;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125"
/>
</svg>
</button>
<button
class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
on:click={async () => {
deleteUserHandler(user.id);
}}
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
/>
</svg>
</button>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
/>
</svg>
</button>
</div>
</td>
</tr>
{/each}
......
<script lang="ts">
import { v4 as uuidv4 } from 'uuid';
import toast from 'svelte-french-toast';
import { toast } from 'svelte-sonner';
import { onMount, tick } from 'svelte';
import { onMount, tick, getContext } from 'svelte';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
......@@ -14,6 +14,7 @@
chats,
chatId,
config,
WEBUI_NAME,
tags as _tags
} from '$lib/stores';
import { copyToClipboard, splitStream, convertMessagesToHistory } from '$lib/utils';
......@@ -37,13 +38,16 @@
import ModelSelector from '$lib/components/chat/ModelSelector.svelte';
import Navbar from '$lib/components/layout/Navbar.svelte';
import { RAGTemplate } from '$lib/utils/rag';
import { LITELLM_API_BASE_URL, OPENAI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
const i18n = getContext('i18n');
let loaded = false;
let stopResponseFlag = false;
let autoScroll = true;
let processing = '';
let messagesContainerElement: HTMLDivElement;
let currentRequestId = null;
// let chatId = $page.params.id;
......@@ -98,6 +102,10 @@
if (await loadChat()) {
await tick();
loaded = true;
window.setTimeout(() => scrollToBottom(), 0);
const chatInput = document.getElementById('chat-textarea');
chatInput?.focus();
} else {
await goto('/');
}
......@@ -154,8 +162,9 @@
};
const scrollToBottom = () => {
const element = document.getElementById('messages-container');
element.scrollTop = element.scrollHeight;
if (messagesContainerElement) {
messagesContainerElement.scrollTop = messagesContainerElement.scrollHeight;
}
};
//////////////////////////
......@@ -166,7 +175,7 @@
console.log('submitPrompt', $chatId);
if (selectedModels.includes('')) {
toast.error('Model not selected');
toast.error($i18n.t('Model not selected'));
} else if (messages.length != 0 && messages.at(-1).done != true) {
// Response not done
console.log('wait');
......@@ -212,7 +221,7 @@
if ($settings.saveChatHistory ?? true) {
chat = await createNewChat(localStorage.token, {
id: $chatId,
title: 'New Chat',
title: $i18n.t('New Chat'),
models: selectedModels,
system: $settings.system ?? undefined,
options: {
......@@ -237,90 +246,45 @@
await sendPrompt(userPrompt, userMessageId);
}
};
const sendPrompt = async (prompt, parentId) => {
const _chatId = JSON.parse(JSON.stringify($chatId));
const docs = messages
.filter((message) => message?.files ?? null)
.map((message) =>
message.files.filter((item) => item.type === 'doc' || item.type === 'collection')
)
.flat(1);
console.log(docs);
if (docs.length > 0) {
processing = 'Reading';
const query = history.messages[parentId].content;
let relevantContexts = await Promise.all(
docs.map(async (doc) => {
if (doc.type === 'collection') {
return await queryCollection(localStorage.token, doc.collection_names, query, 4).catch(
(error) => {
console.log(error);
return null;
}
);
} else {
return await queryDoc(localStorage.token, doc.collection_name, query, 4).catch(
(error) => {
console.log(error);
return null;
}
);
}
})
);
relevantContexts = relevantContexts.filter((context) => context);
const contextString = relevantContexts.reduce((a, context, i, arr) => {
return `${a}${context.documents.join(' ')}\n`;
}, '');
console.log(contextString);
history.messages[parentId].raContent = RAGTemplate(contextString, query);
history.messages[parentId].contexts = relevantContexts;
await tick();
processing = '';
}
await Promise.all(
selectedModels.map(async (model) => {
console.log(model);
const modelTag = $models.filter((m) => m.name === model).at(0);
// Create response message
let responseMessageId = uuidv4();
let responseMessage = {
parentId: parentId,
id: responseMessageId,
childrenIds: [],
role: 'assistant',
content: '',
model: model,
timestamp: Math.floor(Date.now() / 1000) // Unix epoch
};
// Add message to history and Set currentId to messageId
history.messages[responseMessageId] = responseMessage;
history.currentId = responseMessageId;
// Append messageId to childrenIds of parent message
if (parentId !== null) {
history.messages[parentId].childrenIds = [
...history.messages[parentId].childrenIds,
responseMessageId
];
}
selectedModels.map(async (modelId) => {
const model = $models.filter((m) => m.id === modelId).at(0);
if (model) {
// Create response message
let responseMessageId = uuidv4();
let responseMessage = {
parentId: parentId,
id: responseMessageId,
childrenIds: [],
role: 'assistant',
content: '',
model: model.id,
timestamp: Math.floor(Date.now() / 1000) // Unix epoch
};
// Add message to history and Set currentId to messageId
history.messages[responseMessageId] = responseMessage;
history.currentId = responseMessageId;
// Append messageId to childrenIds of parent message
if (parentId !== null) {
history.messages[parentId].childrenIds = [
...history.messages[parentId].childrenIds,
responseMessageId
];
}
if (modelTag?.external) {
await sendPromptOpenAI(model, prompt, responseMessageId, _chatId);
} else if (modelTag) {
await sendPromptOllama(model, prompt, responseMessageId, _chatId);
if (model?.external) {
await sendPromptOpenAI(model, prompt, responseMessageId, _chatId);
} else if (model) {
await sendPromptOllama(model, prompt, responseMessageId, _chatId);
}
} else {
toast.error(`Model ${model} not found`);
toast.error($i18n.t(`Model {{modelId}} not found`, { modelId }));
}
})
);
......@@ -329,6 +293,7 @@
};
const sendPromptOllama = async (model, userPrompt, responseMessageId, _chatId) => {
model = model.id;
const responseMessage = history.messages[responseMessageId];
// Wait until history/message have been updated
......@@ -347,15 +312,25 @@
...messages
]
.filter((message) => message)
.map((message, idx, arr) => ({
role: message.role,
content: arr.length - 2 !== idx ? message.content : message?.raContent ?? message.content,
...(message.files && {
images: message.files
.filter((file) => file.type === 'image')
.map((file) => file.url.slice(file.url.indexOf(',') + 1))
})
}));
.map((message, idx, arr) => {
// Prepare the base message object
const baseMessage = {
role: message.role,
content: arr.length - 2 !== idx ? message.content : message?.raContent ?? message.content
};
// Extract and format image URLs if any exist
const imageUrls = message.files
?.filter((file) => file.type === 'image')
.map((file) => file.url.slice(file.url.indexOf(',') + 1));
// Add images array only if it contains elements
if (imageUrls && imageUrls.length > 0 && message.role === 'user') {
baseMessage.images = imageUrls;
}
return baseMessage;
});
let lastImageIndex = -1;
......@@ -373,6 +348,13 @@
}
});
const docs = messages
.filter((message) => message?.files ?? null)
.map((message) =>
message.files.filter((item) => item.type === 'doc' || item.type === 'collection')
)
.flat(1);
const [res, controller] = await generateChatCompletion(localStorage.token, {
model: model,
messages: messagesBody,
......@@ -380,7 +362,8 @@
...($settings.options ?? {})
},
format: $settings.requestFormat ?? undefined,
keep_alive: $settings.keepAlive ?? undefined
keep_alive: $settings.keepAlive ?? undefined,
docs: docs.length > 0 ? docs : undefined
});
if (res && res.ok) {
......@@ -462,7 +445,7 @@
: `${model}`,
{
body: responseMessage.content,
icon: selectedModelfile?.imageUrl ?? '/favicon.png'
icon: selectedModelfile?.imageUrl ?? `${WEBUI_BASE_URL}/static/favicon.png`
}
);
}
......@@ -513,12 +496,18 @@
responseMessage.content = error.error;
}
} else {
toast.error(`Uh-oh! There was an issue connecting to Ollama.`);
responseMessage.content = `Uh-oh! There was an issue connecting to Ollama.`;
toast.error(
$i18n.t(`Uh-oh! There was an issue connecting to {{provider}}.`, { provider: 'Ollama' })
);
responseMessage.content = $i18n.t(`Uh-oh! There was an issue connecting to {{provider}}.`, {
provider: 'Ollama'
});
}
responseMessage.error = true;
responseMessage.content = `Uh-oh! There was an issue connecting to Ollama.`;
responseMessage.content = $i18n.t(`Uh-oh! There was an issue connecting to {{provider}}.`, {
provider: 'Ollama'
});
responseMessage.done = true;
messages = messages;
}
......@@ -538,57 +527,71 @@
const sendPromptOpenAI = async (model, userPrompt, responseMessageId, _chatId) => {
const responseMessage = history.messages[responseMessageId];
scrollToBottom();
const res = await generateOpenAIChatCompletion(localStorage.token, {
model: model,
stream: true,
messages: [
$settings.system
? {
role: 'system',
content: $settings.system
}
: undefined,
...messages
]
.filter((message) => message)
.map((message, idx, arr) => ({
role: message.role,
...(message.files?.filter((file) => file.type === 'image').length > 0 ?? false
const docs = messages
.filter((message) => message?.files ?? null)
.map((message) =>
message.files.filter((item) => item.type === 'doc' || item.type === 'collection')
)
.flat(1);
console.log(docs);
const res = await generateOpenAIChatCompletion(
localStorage.token,
{
model: model.id,
stream: true,
messages: [
$settings.system
? {
content: [
{
type: 'text',
text:
arr.length - 1 !== idx
? message.content
: message?.raContent ?? message.content
},
...message.files
.filter((file) => file.type === 'image')
.map((file) => ({
type: 'image_url',
image_url: {
url: file.url
}
}))
]
role: 'system',
content: $settings.system
}
: {
content:
arr.length - 1 !== idx ? message.content : message?.raContent ?? message.content
})
})),
seed: $settings?.options?.seed ?? undefined,
stop: $settings?.options?.stop ?? undefined,
temperature: $settings?.options?.temperature ?? undefined,
top_p: $settings?.options?.top_p ?? undefined,
num_ctx: $settings?.options?.num_ctx ?? undefined,
frequency_penalty: $settings?.options?.repeat_penalty ?? undefined,
max_tokens: $settings?.options?.num_predict ?? undefined
});
: undefined,
...messages
]
.filter((message) => message)
.map((message, idx, arr) => ({
role: message.role,
...((message.files?.filter((file) => file.type === 'image').length > 0 ?? false) &&
message.role === 'user'
? {
content: [
{
type: 'text',
text:
arr.length - 1 !== idx
? message.content
: message?.raContent ?? message.content
},
...message.files
.filter((file) => file.type === 'image')
.map((file) => ({
type: 'image_url',
image_url: {
url: file.url
}
}))
]
}
: {
content:
arr.length - 1 !== idx ? message.content : message?.raContent ?? message.content
})
})),
seed: $settings?.options?.seed ?? undefined,
stop: $settings?.options?.stop ?? undefined,
temperature: $settings?.options?.temperature ?? undefined,
top_p: $settings?.options?.top_p ?? undefined,
num_ctx: $settings?.options?.num_ctx ?? undefined,
frequency_penalty: $settings?.options?.repeat_penalty ?? undefined,
max_tokens: $settings?.options?.num_predict ?? undefined,
docs: docs.length > 0 ? docs : undefined
},
model.source === 'litellm' ? `${LITELLM_API_BASE_URL}/v1` : `${OPENAI_API_BASE_URL}`
);
if (res && res.ok) {
const reader = res.body
......@@ -633,7 +636,7 @@
if ($settings.notificationEnabled && !document.hasFocus()) {
const notification = new Notification(`OpenAI ${model}`, {
body: responseMessage.content,
icon: '/favicon.png'
icon: `${WEBUI_BASE_URL}/static/favicon.png`
});
}
......@@ -677,12 +680,18 @@
}
}
} else {
toast.error(`Uh-oh! There was an issue connecting to ${model}.`);
responseMessage.content = `Uh-oh! There was an issue connecting to ${model}.`;
toast.error(
$i18n.t(`Uh-oh! There was an issue connecting to {{provider}}.`, { provider: model })
);
responseMessage.content = $i18n.t(`Uh-oh! There was an issue connecting to {{provider}}.`, {
provider: model
});
}
responseMessage.error = true;
responseMessage.content = `Uh-oh! There was an issue connecting to ${model}.`;
responseMessage.content = $i18n.t(`Uh-oh! There was an issue connecting to {{provider}}.`, {
provider: model
});
responseMessage.done = true;
messages = messages;
}
......@@ -696,7 +705,12 @@
if (messages.length == 2) {
window.history.replaceState(history.state, '', `/c/${_chatId}`);
await setChatTitle(_chatId, userPrompt);
if ($settings?.titleAutoGenerateModel) {
await generateChatTitle(_chatId, userPrompt);
} else {
await setChatTitle(_chatId, userPrompt);
}
}
};
......@@ -714,25 +728,26 @@
responseMessage.done = false;
await tick();
const modelTag = $models.filter((m) => m.name === responseMessage.model).at(0);
if (modelTag?.external) {
await sendPromptOpenAI(
responseMessage.model,
history.messages[responseMessage.parentId].content,
responseMessage.id,
_chatId
);
} else if (modelTag) {
await sendPromptOllama(
responseMessage.model,
history.messages[responseMessage.parentId].content,
responseMessage.id,
_chatId
);
} else {
toast.error(`Model ${model} not found`);
const model = $models.filter((m) => m.id === responseMessage.model).at(0);
if (model) {
if (model?.external) {
await sendPromptOpenAI(
model,
history.messages[responseMessage.parentId].content,
responseMessage.id,
_chatId
);
} else
await sendPromptOllama(
model,
history.messages[responseMessage.parentId].content,
responseMessage.id,
_chatId
);
}
} else {
toast.error($i18n.t(`Model {{modelId}} not found`, { modelId }));
}
};
......@@ -751,7 +766,15 @@
const generateChatTitle = async (_chatId, userPrompt) => {
if ($settings.titleAutoGenerate ?? true) {
const title = await generateTitle(localStorage.token, selectedModels[0], userPrompt);
const title = await generateTitle(
localStorage.token,
$settings?.titleGenerationPrompt ??
$i18n.t(
"Create a concise, 3-5 word phrase as a header for the following query, strictly adhering to the 3-5 word limit and avoiding the use of the word 'title':"
) + ' {{prompt}}',
$settings?.titleAutoGenerateModel ?? selectedModels[0],
userPrompt
);
if (title) {
await setChatTitle(_chatId, title);
......@@ -805,6 +828,14 @@
});
</script>
<svelte:head>
<title>
{title
? `${title.length > 30 ? `${title.slice(0, 30)}...` : title} | ${$WEBUI_NAME}`
: `${$WEBUI_NAME}`}
</title>
</svelte:head>
{#if loaded}
<div class="min-h-screen max-h-screen w-full flex flex-col">
<Navbar
......@@ -826,8 +857,11 @@
<div
class=" pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0"
id="messages-container"
bind:this={messagesContainerElement}
on:scroll={(e) => {
autoScroll = e.target.scrollHeight - e.target.scrollTop <= e.target.clientHeight + 50;
autoScroll =
messagesContainerElement.scrollHeight - messagesContainerElement.scrollTop <=
messagesContainerElement.clientHeight + 5;
}}
>
<div
......@@ -835,10 +869,7 @@
? 'max-w-full'
: 'max-w-2xl md:px-0'} mx-auto w-full px-4"
>
<ModelSelector
bind:selectedModels
disabled={messages.length > 0 && !selectedModels.includes('')}
/>
<ModelSelector bind:selectedModels />
</div>
<div class=" h-full w-full flex flex-col py-8">
......
<script lang="ts">
import toast from 'svelte-french-toast';
import { toast } from 'svelte-sonner';
import fileSaver from 'file-saver';
const { saveAs } = fileSaver;
import { onMount } from 'svelte';
import { documents } from '$lib/stores';
import { onMount, getContext } from 'svelte';
import { WEBUI_NAME, documents } from '$lib/stores';
import { createNewDoc, deleteDocByName, getDocs } from '$lib/apis/documents';
import { SUPPORTED_FILE_TYPE, SUPPORTED_FILE_EXTENSIONS } from '$lib/constants';
import { uploadDocToVectorDB } from '$lib/apis/rag';
import { transformFileName } from '$lib/utils';
import Checkbox from '$lib/components/common/Checkbox.svelte';
import EditDocModal from '$lib/components/documents/EditDocModal.svelte';
import AddFilesPlaceholder from '$lib/components/AddFilesPlaceholder.svelte';
import SettingsModal from '$lib/components/documents/SettingsModal.svelte';
import AddDocModal from '$lib/components/documents/AddDocModal.svelte';
const i18n = getContext('i18n');
let importFiles = '';
let inputFiles = '';
let query = '';
let documentsImportInputElement: HTMLInputElement;
let tags = [];
let showSettingsModal = false;
let showAddDocModal = false;
let showEditDocModal = false;
let selectedDoc;
let selectedTag = '';
......@@ -31,6 +40,16 @@
await documents.set(await getDocs(localStorage.token));
};
const deleteDocs = async (docs) => {
const res = await Promise.all(
docs.map(async (doc) => {
return await deleteDocByName(localStorage.token, doc.name);
})
);
await documents.set(await getDocs(localStorage.token));
};
const uploadDoc = async (file) => {
const res = await uploadDocToVectorDB(localStorage.token, '', file).catch((error) => {
toast.error(error);
......@@ -104,7 +123,7 @@
}
}
} else {
toast.error(`File not found.`);
toast.error($i18n.t(`File not found.`));
}
}
......@@ -121,8 +140,21 @@
dropZone?.removeEventListener('dragleave', onDragLeave);
};
});
let filteredDocs;
$: filteredDocs = $documents.filter(
(doc) =>
(selectedTag === '' ||
(doc?.content?.tags ?? []).map((tag) => tag.name).includes(selectedTag)) &&
(query === '' || doc.name.includes(query))
);
</script>
<svelte:head>
<title>{$i18n.t('Documents')} | {$WEBUI_NAME}</title>
</svelte:head>
{#if dragged}
<div
class="fixed w-full h-full flex z-50 touch-none pointer-events-none"
......@@ -148,42 +180,45 @@
<EditDocModal bind:show={showEditDocModal} {selectedDoc} />
{/key}
<input
id="upload-doc-input"
bind:files={inputFiles}
type="file"
multiple
hidden
on:change={async (e) => {
if (inputFiles && inputFiles.length > 0) {
for (const file of inputFiles) {
console.log(file, file.name.split('.').at(-1));
if (
SUPPORTED_FILE_TYPE.includes(file['type']) ||
SUPPORTED_FILE_EXTENSIONS.includes(file.name.split('.').at(-1))
) {
uploadDoc(file);
} else {
toast.error(
`Unknown File Type '${file['type']}', but accepting and treating as plain text`
);
uploadDoc(file);
}
}
<AddDocModal bind:show={showAddDocModal} />
inputFiles = null;
e.target.value = '';
} else {
toast.error(`File not found.`);
}
}}
/>
<SettingsModal bind:show={showSettingsModal} />
<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
<div class=" py-2.5 flex flex-col justify-between w-full overflow-y-auto">
<div class=" flex flex-col justify-between w-full overflow-y-auto">
<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10">
<div class="mb-6 flex justify-between items-center">
<div class=" text-2xl font-semibold self-center">My Documents</div>
<div class="mb-6">
<div class="flex justify-between items-center">
<div class=" text-2xl font-semibold self-center">{$i18n.t('My Documents')}</div>
<div>
<button
class="flex items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 transition"
type="button"
on:click={() => {
showSettingsModal = !showSettingsModal;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M6.955 1.45A.5.5 0 0 1 7.452 1h1.096a.5.5 0 0 1 .497.45l.17 1.699c.484.12.94.312 1.356.562l1.321-1.081a.5.5 0 0 1 .67.033l.774.775a.5.5 0 0 1 .034.67l-1.08 1.32c.25.417.44.873.561 1.357l1.699.17a.5.5 0 0 1 .45.497v1.096a.5.5 0 0 1-.45.497l-1.699.17c-.12.484-.312.94-.562 1.356l1.082 1.322a.5.5 0 0 1-.034.67l-.774.774a.5.5 0 0 1-.67.033l-1.322-1.08c-.416.25-.872.44-1.356.561l-.17 1.699a.5.5 0 0 1-.497.45H7.452a.5.5 0 0 1-.497-.45l-.17-1.699a4.973 4.973 0 0 1-1.356-.562L4.108 13.37a.5.5 0 0 1-.67-.033l-.774-.775a.5.5 0 0 1-.034-.67l1.08-1.32a4.971 4.971 0 0 1-.561-1.357l-1.699-.17A.5.5 0 0 1 1 8.548V7.452a.5.5 0 0 1 .45-.497l1.699-.17c.12-.484.312-.94.562-1.356L2.629 4.107a.5.5 0 0 1 .034-.67l.774-.774a.5.5 0 0 1 .67-.033L5.43 3.71a4.97 4.97 0 0 1 1.356-.561l.17-1.699ZM6 8c0 .538.212 1.026.558 1.385l.057.057a2 2 0 0 0 2.828-2.828l-.058-.056A2 2 0 0 0 6 8Z"
clip-rule="evenodd"
/>
</svg>
<div class=" text-xs">{$i18n.t('Document Settings')}</div>
</button>
</div>
</div>
<div class=" text-gray-500 text-xs mt-1">
ⓘ {$i18n.t("Use '#' in the prompt input to load and select your documents.")}
</div>
</div>
<div class=" flex w-full space-x-2">
......@@ -205,15 +240,15 @@
<input
class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-none bg-transparent"
bind:value={query}
placeholder="Search Document"
placeholder={$i18n.t('Search Documents')}
/>
</div>
<div>
<button
class=" px-2 py-2 rounded-xl border border-gray-200 dark:border-gray-600 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 transition font-medium text-sm flex items-center space-x-1"
class=" px-2 py-2 rounded-xl border border-gray-200 dark:border-gray-600 dark:border-0 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 transition font-medium text-sm flex items-center space-x-1"
on:click={() => {
document.getElementById('upload-doc-input')?.click();
showAddDocModal = true;
}}
>
<svg
......@@ -240,7 +275,7 @@
on:dragleave={onDragLeave}
>
<div class=" pointer-events-none">
<div class="text-center dark:text-white text-2xl font-semibold z-50">Add Files</div>
<div class="text-center dark:text-white text-2xl font-semibold z-50">{$i18n.t('Add Files')}</div>
<div class=" mt-2 text-center text-sm dark:text-gray-200 w-full">
Drop any files here to add to my documents
......@@ -252,183 +287,247 @@
<hr class=" dark:border-gray-700 my-2.5" />
{#if tags.length > 0}
<div class="px-2.5 mt-0.5 mb-2 flex gap-1 flex-wrap">
<button
class="px-2 py-0.5 space-x-1 flex h-fit items-center rounded-full transition border dark:border-gray-600 dark:text-white"
on:click={async () => {
selectedTag = '';
// await chats.set(await getChatListByTagName(localStorage.token, tag.name));
}}
>
<div class=" text-xs font-medium self-center line-clamp-1">all</div>
</button>
{#each tags as tag}
<div class="px-2.5 pt-1 flex gap-1 flex-wrap">
<div class="ml-0.5 pr-3 my-auto flex items-center">
<Checkbox
state={filteredDocs.filter((doc) => doc?.selected === 'checked').length ===
filteredDocs.length
? 'checked'
: 'unchecked'}
indeterminate={filteredDocs.filter((doc) => doc?.selected === 'checked').length > 0 &&
filteredDocs.filter((doc) => doc?.selected === 'checked').length !==
filteredDocs.length}
on:change={(e) => {
if (e.detail === 'checked') {
filteredDocs = filteredDocs.map((doc) => ({ ...doc, selected: 'checked' }));
} else if (e.detail === 'unchecked') {
filteredDocs = filteredDocs.map((doc) => ({ ...doc, selected: 'unchecked' }));
}
}}
/>
</div>
{#if filteredDocs.filter((doc) => doc?.selected === 'checked').length === 0}
<button
class="px-2 py-0.5 space-x-1 flex h-fit items-center rounded-full transition border dark:border-gray-600 dark:text-white"
class="px-2 py-0.5 space-x-1 flex h-fit items-center rounded-full transition bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:text-white"
on:click={async () => {
selectedTag = tag;
selectedTag = '';
// await chats.set(await getChatListByTagName(localStorage.token, tag.name));
}}
>
<div class=" text-xs font-medium self-center line-clamp-1">
#{tag}
</div>
<div class=" text-xs font-medium self-center line-clamp-1">{$i18n.t('all')}</div>
</button>
{/each}
{#each tags as tag}
<button
class="px-2 py-0.5 space-x-1 flex h-fit items-center rounded-full transition bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:text-white"
on:click={async () => {
selectedTag = tag;
// await chats.set(await getChatListByTagName(localStorage.token, tag.name));
}}
>
<div class=" text-xs font-medium self-center line-clamp-1">
#{tag}
</div>
</button>
{/each}
{:else}
<div class="flex-1 flex w-full justify-between items-center">
<div class="text-xs font-medium py-0.5 self-center mr-1">
{filteredDocs.filter((doc) => doc?.selected === 'checked').length} Selected
</div>
<div class="flex gap-1">
<!-- <button
class="px-2 py-0.5 space-x-1 flex h-fit items-center rounded-full transition bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:text-white"
on:click={async () => {
selectedTag = '';
// await chats.set(await getChatListByTagName(localStorage.token, tag.name));
}}
>
<div class=" text-xs font-medium self-center line-clamp-1">{$i18n.t('add tags')}</div>
</button> -->
<button
class="px-2 py-0.5 space-x-1 flex h-fit items-center rounded-full transition bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:text-white"
on:click={async () => {
deleteDocs(filteredDocs.filter((doc) => doc.selected === 'checked'));
// await chats.set(await getChatListByTagName(localStorage.token, tag.name));
}}
>
<div class=" text-xs font-medium self-center line-clamp-1">
{$i18n.t('delete')}
</div>
</button>
</div>
</div>
{/if}
</div>
{/if}
{#each $documents.filter((doc) => (selectedTag === '' || (doc?.content?.tags ?? [])
.map((tag) => tag.name)
.includes(selectedTag)) && (query === '' || doc.name.includes(query))) as doc}
<div class=" flex space-x-4 cursor-pointer w-full mt-3 mb-3">
<div class=" flex flex-1 space-x-4 cursor-pointer w-full">
<div class=" flex items-center space-x-3">
<div class="p-2.5 bg-red-400 text-white rounded-lg">
{#if doc}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="w-6 h-6"
>
<path
fill-rule="evenodd"
d="M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0 0 16.5 9h-1.875a1.875 1.875 0 0 1-1.875-1.875V5.25A3.75 3.75 0 0 0 9 1.5H5.625ZM7.5 15a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 7.5 15Zm.75 2.25a.75.75 0 0 0 0 1.5H12a.75.75 0 0 0 0-1.5H8.25Z"
clip-rule="evenodd"
/>
<path
d="M12.971 1.816A5.23 5.23 0 0 1 14.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 0 1 3.434 1.279 9.768 9.768 0 0 0-6.963-6.963Z"
/>
</svg>
{:else}
<svg
class=" w-6 h-6 translate-y-[0.5px]"
fill="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
><style>
.spinner_qM83 {
animation: spinner_8HQG 1.05s infinite;
}
.spinner_oXPr {
animation-delay: 0.1s;
}
.spinner_ZTLf {
animation-delay: 0.2s;
}
@keyframes spinner_8HQG {
0%,
57.14% {
animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1);
transform: translate(0);
<div class="my-3 mb-5">
{#each filteredDocs as doc}
<button
class=" flex space-x-4 cursor-pointer text-left w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl"
on:click={() => {
if (doc?.selected === 'checked') {
doc.selected = 'unchecked';
} else {
doc.selected = 'checked';
}
}}
>
<div class="my-auto flex items-center">
<Checkbox state={doc?.selected ?? 'unchecked'} />
</div>
<div class=" flex flex-1 space-x-4 cursor-pointer w-full">
<div class=" flex items-center space-x-3">
<div class="p-2.5 bg-red-400 text-white rounded-lg">
{#if doc}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="w-6 h-6"
>
<path
fill-rule="evenodd"
d="M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0 0 16.5 9h-1.875a1.875 1.875 0 0 1-1.875-1.875V5.25A3.75 3.75 0 0 0 9 1.5H5.625ZM7.5 15a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 7.5 15Zm.75 2.25a.75.75 0 0 0 0 1.5H12a.75.75 0 0 0 0-1.5H8.25Z"
clip-rule="evenodd"
/>
<path
d="M12.971 1.816A5.23 5.23 0 0 1 14.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 0 1 3.434 1.279 9.768 9.768 0 0 0-6.963-6.963Z"
/>
</svg>
{:else}
<svg
class=" w-6 h-6 translate-y-[0.5px]"
fill="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
><style>
.spinner_qM83 {
animation: spinner_8HQG 1.05s infinite;
}
28.57% {
animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33);
transform: translateY(-6px);
.spinner_oXPr {
animation-delay: 0.1s;
}
100% {
transform: translate(0);
.spinner_ZTLf {
animation-delay: 0.2s;
}
}
</style><circle class="spinner_qM83" cx="4" cy="12" r="2.5" /><circle
class="spinner_qM83 spinner_oXPr"
cx="12"
cy="12"
r="2.5"
/><circle class="spinner_qM83 spinner_ZTLf" cx="20" cy="12" r="2.5" /></svg
>
{/if}
</div>
<div class=" flex-1 self-center flex-1">
<div class=" font-bold line-clamp-1">#{doc.name} ({doc.filename})</div>
<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1">
{doc.title}
@keyframes spinner_8HQG {
0%,
57.14% {
animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1);
transform: translate(0);
}
28.57% {
animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33);
transform: translateY(-6px);
}
100% {
transform: translate(0);
}
}
</style><circle class="spinner_qM83" cx="4" cy="12" r="2.5" /><circle
class="spinner_qM83 spinner_oXPr"
cx="12"
cy="12"
r="2.5"
/><circle class="spinner_qM83 spinner_ZTLf" cx="20" cy="12" r="2.5" /></svg
>
{/if}
</div>
<div class=" self-center flex-1">
<div class=" font-bold line-clamp-1">#{doc.name} ({doc.filename})</div>
<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1">
{doc.title}
</div>
</div>
</div>
</div>
</div>
<div class="flex flex-row space-x-1 self-center">
<button
class="self-center w-fit text-sm px-2 py-2 border dark:border-gray-600 rounded-xl"
type="button"
on:click={async () => {
showEditDocModal = !showEditDocModal;
selectedDoc = doc;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
<div class="flex flex-row space-x-1 self-center">
<button
class="self-center w-fit text-sm z-20 px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
type="button"
on:click={async (e) => {
e.stopPropagation();
showEditDocModal = !showEditDocModal;
selectedDoc = doc;
}}
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
/>
</svg>
</button>
<!-- <button
class="self-center w-fit text-sm px-2 py-2 border dark:border-gray-600 rounded-xl"
type="button"
on:click={() => {
console.log('download file');
}}
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M8.75 2.75a.75.75 0 0 0-1.5 0v5.69L5.03 6.22a.75.75 0 0 0-1.06 1.06l3.5 3.5a.75.75 0 0 0 1.06 0l3.5-3.5a.75.75 0 0 0-1.06-1.06L8.75 8.44V2.75Z"
/>
<path
d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
/>
</svg>
</button> -->
<button
class="self-center w-fit text-sm px-2 py-2 border dark:border-gray-600 rounded-xl"
type="button"
on:click={() => {
deleteDoc(doc.name);
}}
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
/>
</svg>
</button>
<!-- <button
class="self-center w-fit text-sm px-2 py-2 border dark:border-gray-600 rounded-xl"
type="button"
on:click={() => {
console.log('download file');
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
/>
</svg>
</button>
</div>
</div>
{/each}
<path
d="M8.75 2.75a.75.75 0 0 0-1.5 0v5.69L5.03 6.22a.75.75 0 0 0-1.06 1.06l3.5 3.5a.75.75 0 0 0 1.06 0l3.5-3.5a.75.75 0 0 0-1.06-1.06L8.75 8.44V2.75Z"
/>
<path
d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
/>
</svg>
</button> -->
{#if $documents.length > 0}
<hr class=" dark:border-gray-700 my-2.5" />
{/if}
<button
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
type="button"
on:click={(e) => {
e.stopPropagation();
<div class=" flex justify-between w-full mb-3">
deleteDoc(doc.name);
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
/>
</svg>
</button>
</div>
</button>
{/each}
</div>
<div class=" flex justify-end w-full mb-2">
<div class="flex space-x-2">
<input
id="documents-import-input"
bind:this={documentsImportInputElement}
bind:files={importFiles}
type="file"
accept=".json"
......@@ -462,12 +561,12 @@
/>
<button
class="self-center w-fit text-sm px-3 py-1 border dark:border-gray-600 rounded-xl flex"
on:click={async () => {
document.getElementById('documents-import-input')?.click();
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
on:click={() => {
documentsImportInputElement.click();
}}
>
<div class=" self-center mr-2 font-medium">Import Documents Mapping</div>
<div class=" self-center mr-2 font-medium">{$i18n.t('Import Documents Mapping')}</div>
<div class=" self-center">
<svg
......@@ -486,7 +585,7 @@
</button>
<button
class="self-center w-fit text-sm px-3 py-1 border dark:border-gray-600 rounded-xl flex"
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
on:click={async () => {
let blob = new Blob([JSON.stringify($documents)], {
type: 'application/json'
......@@ -494,7 +593,7 @@
saveAs(blob, `documents-mapping-export-${Date.now()}.json`);
}}
>
<div class=" self-center mr-2 font-medium">Export Documents Mapping</div>
<div class=" self-center mr-2 font-medium">{$i18n.t('Export Documents Mapping')}</div>
<div class=" self-center">
<svg
......@@ -513,29 +612,6 @@
</button>
</div>
</div>
<div class="text-xs flex items-center space-x-1">
<div>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-3 h-3"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z"
/>
</svg>
</div>
<div class="line-clamp-1">
Tip: Use '#' in the prompt input to swiftly load and select your documents.
</div>
</div>
</div>
</div>
</div>
<script lang="ts">
import toast from 'svelte-french-toast';
import { toast } from 'svelte-sonner';
import fileSaver from 'file-saver';
const { saveAs } = fileSaver;
import { onMount } from 'svelte';
import { onMount, getContext } from 'svelte';
import { modelfiles, settings, user } from '$lib/stores';
import { WEBUI_NAME, modelfiles, settings, user } from '$lib/stores';
import { createModel, deleteModel } from '$lib/apis/ollama';
import {
createNewModelfile,
deleteModelfileByTagName,
getModelfiles
} from '$lib/apis/modelfiles';
import { goto } from '$app/navigation';
const i18n = getContext('i18n');
let localModelfiles = [];
let importFiles;
let modelfilesImportInputElement: HTMLInputElement;
const deleteModelHandler = async (tagName) => {
let success = null;
success = await deleteModel(localStorage.token, tagName);
success = await deleteModel(localStorage.token, tagName).catch((err) => {
toast.error(err);
return null;
});
if (success) {
toast.success(`Deleted ${tagName}`);
toast.success($i18n.t(`Deleted {tagName}`, { tagName }));
}
return success;
......@@ -35,7 +41,7 @@
};
const shareModelfile = async (modelfile) => {
toast.success('Redirecting you to OpenWebUI Community');
toast.success($i18n.t('Redirecting you to OpenWebUI Community'));
const url = 'https://openwebui.com';
......@@ -68,12 +74,18 @@
});
</script>
<svelte:head>
<title>
{$i18n.t('Modelfiles')} | {$WEBUI_NAME}
</title>
</svelte:head>
<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
<div class=" py-2.5 flex flex-col justify-between w-full overflow-y-auto">
<div class="flex flex-col justify-between w-full overflow-y-auto">
<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10">
<div class=" text-2xl font-semibold mb-6">My Modelfiles</div>
<div class=" text-2xl font-semibold mb-3">{$i18n.t('My Modelfiles')}</div>
<a class=" flex space-x-4 cursor-pointer w-full mb-3" href="/modelfiles/create">
<a class=" flex space-x-4 cursor-pointer w-full mb-2 px-3 py-2" href="/modelfiles/create">
<div class=" self-center w-10">
<div
class="w-full h-10 flex justify-center rounded-full bg-transparent dark:bg-gray-700 border border-dashed border-gray-200"
......@@ -94,113 +106,141 @@
</div>
<div class=" self-center">
<div class=" font-bold">Create a modelfile</div>
<div class=" text-sm">Customize Ollama models for a specific purpose</div>
<div class=" font-bold">{$i18n.t('Create a modelfile')}</div>
<div class=" text-sm">{$i18n.t('Customize Ollama models for a specific purpose')}</div>
</div>
</a>
{#each $modelfiles as modelfile}
<hr class=" dark:border-gray-700 my-2.5" />
<div class=" flex space-x-4 cursor-pointer w-full mb-3">
<a
class=" flex flex-1 space-x-4 cursor-pointer w-full"
href={`/?models=${encodeURIComponent(modelfile.tagName)}`}
<hr class=" dark:border-gray-700" />
<div class=" my-2 mb-5">
{#each $modelfiles as modelfile}
<div
class=" flex space-x-4 cursor-pointer w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl"
>
<div class=" self-center w-10">
<div class=" rounded-full bg-stone-700">
<img
src={modelfile.imageUrl ?? '/user.png'}
alt="modelfile profile"
class=" rounded-full w-full h-auto object-cover"
/>
<a
class=" flex flex-1 space-x-4 cursor-pointer w-full"
href={`/?models=${encodeURIComponent(modelfile.tagName)}`}
>
<div class=" self-center w-10">
<div class=" rounded-full bg-stone-700">
<img
src={modelfile.imageUrl ?? '/user.png'}
alt="modelfile profile"
class=" rounded-full w-full h-auto object-cover"
/>
</div>
</div>
</div>
<div class=" flex-1 self-center">
<div class=" font-bold capitalize">{modelfile.title}</div>
<div class=" text-sm overflow-hidden text-ellipsis line-clamp-2">
{modelfile.desc}
<div class=" flex-1 self-center">
<div class=" font-bold capitalize">{modelfile.title}</div>
<div class=" text-sm overflow-hidden text-ellipsis line-clamp-1">
{modelfile.desc}
</div>
</div>
</div>
</a>
<div class="flex flex-row space-x-1 self-center">
<a
class="self-center w-fit text-sm px-2 py-2 border dark:border-gray-600 rounded-xl"
type="button"
href={`/modelfiles/edit?tag=${encodeURIComponent(modelfile.tagName)}`}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
/>
</svg>
</a>
<div class="flex flex-row space-x-1 self-center">
<a
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
type="button"
href={`/modelfiles/edit?tag=${encodeURIComponent(modelfile.tagName)}`}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125"
/>
</svg>
</a>
<button
class="self-center w-fit text-sm px-2 py-2 border dark:border-gray-600 rounded-xl"
type="button"
on:click={() => {
shareModelfile(modelfile);
}}
>
<!-- TODO: update to share icon -->
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
<button
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
type="button"
on:click={() => {
// console.log(modelfile);
sessionStorage.modelfile = JSON.stringify(modelfile);
goto('/modelfiles/create');
}}
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M7.217 10.907a2.25 2.25 0 100 2.186m0-2.186c.18.324.283.696.283 1.093s-.103.77-.283 1.093m0-2.186l9.566-5.314m-9.566 7.5l9.566 5.314m0 0a2.25 2.25 0 103.935 2.186 2.25 2.25 0 00-3.935-2.186zm0-12.814a2.25 2.25 0 103.933-2.185 2.25 2.25 0 00-3.933 2.185z"
/>
</svg>
</button>
<button
class="self-center w-fit text-sm px-2 py-2 border dark:border-gray-600 rounded-xl"
type="button"
on:click={() => {
deleteModelfile(modelfile.tagName);
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 0 1-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 0 1 1.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 0 0-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 0 1-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H9.75"
/>
</svg>
</button>
<button
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
type="button"
on:click={() => {
shareModelfile(modelfile);
}}
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
/>
</svg>
</button>
</div>
</div>
{/each}
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M7.217 10.907a2.25 2.25 0 1 0 0 2.186m0-2.186c.18.324.283.696.283 1.093s-.103.77-.283 1.093m0-2.186 9.566-5.314m-9.566 7.5 9.566 5.314m0 0a2.25 2.25 0 1 0 3.935 2.186 2.25 2.25 0 0 0-3.935-2.186Zm0-12.814a2.25 2.25 0 1 0 3.933-2.185 2.25 2.25 0 0 0-3.933 2.185Z"
/>
</svg>
</button>
<hr class=" dark:border-gray-700 my-2.5" />
<button
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
type="button"
on:click={() => {
deleteModelfile(modelfile.tagName);
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
/>
</svg>
</button>
</div>
</div>
{/each}
</div>
<div class=" flex justify-between w-full mb-3">
<div class=" flex justify-end w-full mb-3">
<div class="flex space-x-1">
<input
id="modelfiles-import-input"
bind:this={modelfilesImportInputElement}
bind:files={importFiles}
type="file"
accept=".json"
......@@ -227,12 +267,12 @@
/>
<button
class="self-center w-fit text-sm px-3 py-1 border dark:border-gray-600 rounded-xl flex"
on:click={async () => {
document.getElementById('modelfiles-import-input')?.click();
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
on:click={() => {
modelfilesImportInputElement.click();
}}
>
<div class=" self-center mr-2 font-medium">Import Modelfiles</div>
<div class=" self-center mr-2 font-medium">{$i18n.t('Import Modelfiles')}</div>
<div class=" self-center">
<svg
......@@ -251,12 +291,12 @@
</button>
<button
class="self-center w-fit text-sm px-3 py-1 border dark:border-gray-600 rounded-xl flex"
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
on:click={async () => {
saveModelfiles($modelfiles);
}}
>
<div class=" self-center mr-2 font-medium">Export Modelfiles</div>
<div class=" self-center mr-2 font-medium">{$i18n.t('Export Modelfiles')}</div>
<div class=" self-center">
<svg
......@@ -297,7 +337,7 @@
await modelfiles.set(await getModelfiles(localStorage.token));
}}
>
<div class=" self-center mr-2 font-medium">Sync All</div>
<div class=" self-center mr-2 font-medium">{$i18n.t('Sync All')}</div>
<div class=" self-center">
<svg
......@@ -348,10 +388,10 @@
</div>
<div class=" my-16">
<div class=" text-2xl font-semibold mb-6">Made by OpenWebUI Community</div>
<div class=" text-2xl font-semibold mb-3">{$i18n.t('Made by OpenWebUI Community')}</div>
<a
class=" flex space-x-4 cursor-pointer w-full mb-3"
class=" flex space-x-4 cursor-pointer w-full mb-2 px-3 py-2"
href="https://openwebui.com/"
target="_blank"
>
......@@ -375,8 +415,8 @@
</div>
<div class=" self-center">
<div class=" font-bold">Discover a modelfile</div>
<div class=" text-sm">Discover, download, and explore Ollama Modelfiles</div>
<div class=" font-bold">{$i18n.t('Discover a modelfile')}</div>
<div class=" text-sm">{$i18n.t('Discover, download, and explore model presets')}</div>
</div>
</a>
</div>
......
<script>
import { v4 as uuidv4 } from 'uuid';
import { toast } from 'svelte-french-toast';
import { toast } from 'svelte-sonner';
import { goto } from '$app/navigation';
import { settings, user, config, modelfiles, models } from '$lib/stores';
import AdvancedParams from '$lib/components/chat/Settings/Advanced/AdvancedParams.svelte';
import { splitStream } from '$lib/utils';
import { onMount, tick } from 'svelte';
import { onMount, tick, getContext } from 'svelte';
import { createModel } from '$lib/apis/ollama';
import { createNewModelfile, getModelfileByTagName, getModelfiles } from '$lib/apis/modelfiles';
const i18n = getContext('i18n');
let loading = false;
let filesInputElement;
......@@ -209,7 +211,7 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
success = false;
};
onMount(() => {
onMount(async () => {
window.addEventListener('message', async (event) => {
if (
![
......@@ -253,11 +255,36 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
if (window.opener ?? false) {
window.opener.postMessage('loaded', '*');
}
if (sessionStorage.modelfile) {
const modelfile = JSON.parse(sessionStorage.modelfile);
console.log(modelfile);
imageUrl = modelfile.imageUrl;
title = modelfile.title;
await tick();
tagName = modelfile.tagName;
desc = modelfile.desc;
content = modelfile.content;
suggestions =
modelfile.suggestionPrompts.length != 0
? modelfile.suggestionPrompts
: [
{
content: ''
}
];
for (const category of modelfile.categories) {
categories[category.toLowerCase()] = true;
}
sessionStorage.removeItem('modelfile');
}
});
</script>
<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
<div class=" py-2.5 flex flex-col justify-between w-full overflow-y-auto">
<div class=" flex flex-col justify-between w-full overflow-y-auto">
<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10">
<input
bind:this={filesInputElement}
......@@ -324,7 +351,7 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
}}
/>
<div class=" text-2xl font-semibold mb-6">My Modelfiles</div>
<div class=" text-2xl font-semibold mb-6">{$i18n.t('My Modelfiles')}</div>
<button
class="flex space-x-1"
......@@ -346,7 +373,7 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
/>
</svg>
</div>
<div class=" self-center font-medium text-sm">Back</div>
<div class=" self-center font-medium text-sm">{$i18n.t('Back')}</div>
</button>
<hr class="my-3 dark:border-gray-700" />
......@@ -393,12 +420,12 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
<div class="my-2 flex space-x-2">
<div class="flex-1">
<div class=" text-sm font-semibold mb-2">Name*</div>
<div class=" text-sm font-semibold mb-2">{$i18n.t('Name')}*</div>
<div>
<input
class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
placeholder="Name your modelfile"
placeholder={$i18n.t('Name your modelfile')}
bind:value={title}
required
/>
......@@ -406,12 +433,12 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
</div>
<div class="flex-1">
<div class=" text-sm font-semibold mb-2">Model Tag Name*</div>
<div class=" text-sm font-semibold mb-2">{$i18n.t('Model Tag Name')}*</div>
<div>
<input
class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
placeholder="Add a model tag name"
placeholder={$i18n.t('Add a model tag name')}
bind:value={tagName}
required
/>
......@@ -420,12 +447,12 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
</div>
<div class="my-2">
<div class=" text-sm font-semibold mb-2">Description*</div>
<div class=" text-sm font-semibold mb-2">{$i18n.t('Description')}*</div>
<div>
<input
class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
placeholder="Add a short description about what this modelfile does"
placeholder={$i18n.t('Add a short description about what this modelfile does')}
bind:value={desc}
required
/>
......@@ -434,7 +461,7 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
<div class="my-2">
<div class="flex w-full justify-between">
<div class=" self-center text-sm font-semibold">Modelfile</div>
<div class=" self-center text-sm font-semibold">{$i18n.t('Modelfile')}</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
......@@ -444,9 +471,9 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
}}
>
{#if raw}
<span class="ml-2 self-center"> Raw Format </span>
<span class="ml-2 self-center"> {$i18n.t('Raw Format')} </span>
{:else}
<span class="ml-2 self-center"> Builder Mode </span>
<span class="ml-2 self-center"> {$i18n.t('Builder Mode')} </span>
{/if}
</button>
</div>
......@@ -455,7 +482,7 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
{#if raw}
<div class="mt-2">
<div class=" text-xs font-semibold mb-2">Content*</div>
<div class=" text-xs font-semibold mb-2">{$i18n.t('Content')}*</div>
<div>
<textarea
......@@ -468,12 +495,13 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
</div>
<div class="text-xs text-gray-400 dark:text-gray-500">
Not sure what to write? Switch to <button
{$i18n.t('Not sure what to write? Switch to')}
<button
class="text-gray-500 dark:text-gray-300 font-medium cursor-pointer"
type="button"
on:click={() => {
raw = !raw;
}}>Builder Mode</button
}}>{$i18n.t('Builder Mode')}</button
>
or
<a
......@@ -481,13 +509,13 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
href="https://openwebui.com"
target="_blank"
>
Click here to check other modelfiles.
{$i18n.t('Click here to check other modelfiles.')}
</a>
</div>
</div>
{:else}
<div class="my-2">
<div class=" text-xs font-semibold mb-2">From (Base Model)*</div>
<div class=" text-xs font-semibold mb-2">{$i18n.t('From (Base Model)')}*</div>
<div>
<input
......@@ -499,16 +527,17 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
</div>
<div class="mt-1 text-xs text-gray-400 dark:text-gray-500">
To access the available model names for downloading, <a
{$i18n.t('To access the available model names for downloading,')}
<a
class=" text-gray-500 dark:text-gray-300 font-medium"
href="https://ollama.com/library"
target="_blank">click here.</a
target="_blank">{$i18n.t('click here.')}</a
>
</div>
</div>
<div class="my-1">
<div class=" text-xs font-semibold mb-2">System Prompt</div>
<div class=" text-xs font-semibold mb-2">{$i18n.t('System Prompt')}</div>
<div>
<textarea
......@@ -521,7 +550,9 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
</div>
<div class="flex w-full justify-between">
<div class=" self-center text-sm font-semibold">Modelfile Advanced Settings</div>
<div class=" self-center text-sm font-semibold">
{$i18n.t('Modelfile Advanced Settings')}
</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
......@@ -531,16 +562,16 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
}}
>
{#if advanced}
<span class="ml-2 self-center"> Custom </span>
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
{:else}
<span class="ml-2 self-center"> Default </span>
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{/if}
</button>
</div>
{#if advanced}
<div class="my-2">
<div class=" text-xs font-semibold mb-2">Template</div>
<div class=" text-xs font-semibold mb-2">{$i18n.t('Template')}</div>
<div>
<textarea
......@@ -553,7 +584,7 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
</div>
<div class="my-2">
<div class=" text-xs font-semibold mb-2">Parameters</div>
<div class=" text-xs font-semibold mb-2">{$i18n.t('Parameters')}</div>
<div>
<AdvancedParams bind:options />
......@@ -565,7 +596,7 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
<div class="my-2">
<div class="flex w-full justify-between mb-2">
<div class=" self-center text-sm font-semibold">Prompt suggestions</div>
<div class=" self-center text-sm font-semibold">{$i18n.t('Prompt suggestions')}</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
......@@ -593,7 +624,7 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
<div class=" flex border dark:border-gray-600 rounded-lg">
<input
class="px-3 py-1.5 text-sm w-full bg-transparent outline-none border-r dark:border-gray-600"
placeholder="Write a prompt suggestion (e.g. Who are you?)"
placeholder={$i18n.t('Write a prompt suggestion (e.g. Who are you?)')}
bind:value={prompt.content}
/>
......@@ -622,7 +653,7 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
</div>
<div class="my-2">
<div class=" text-sm font-semibold mb-2">Categories</div>
<div class=" text-sm font-semibold mb-2">{$i18n.t('Categories')}</div>
<div class="grid grid-cols-4">
{#each Object.keys(categories) as category}
......@@ -636,7 +667,7 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
{#if pullProgress !== null}
<div class="my-2">
<div class=" text-sm font-semibold mb-2">Pull Progress</div>
<div class=" text-sm font-semibold mb-2">{$i18n.t('Pull Progress')}</div>
<div class="w-full rounded-full dark:bg-gray-800">
<div
class="dark:bg-gray-600 bg-gray-500 text-xs font-medium text-gray-100 text-center p-0.5 leading-none rounded-full"
......@@ -659,7 +690,7 @@ SYSTEM """${system}"""`.replace(/^\s*\n/gm, '');
type="submit"
disabled={loading}
>
<div class=" self-center font-medium">Save & Create</div>
<div class=" self-center font-medium">{$i18n.t('Save & Create')}</div>
{#if loading}
<div class="ml-1.5 self-center">
......
<script>
import { v4 as uuidv4 } from 'uuid';
import { toast } from 'svelte-french-toast';
import { toast } from 'svelte-sonner';
import { goto } from '$app/navigation';
import { onMount } from 'svelte';
import { onMount, getContext } from 'svelte';
import { page } from '$app/stores';
import { settings, user, config, modelfiles } from '$lib/stores';
......@@ -14,6 +14,8 @@
import AdvancedParams from '$lib/components/chat/Settings/Advanced/AdvancedParams.svelte';
const i18n = getContext('i18n');
let loading = false;
let filesInputElement;
......@@ -181,7 +183,7 @@
</script>
<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
<div class=" py-2.5 flex flex-col justify-between w-full overflow-y-auto">
<div class="flex flex-col justify-between w-full overflow-y-auto">
<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10">
<input
bind:this={filesInputElement}
......@@ -248,7 +250,7 @@
}}
/>
<div class=" text-2xl font-semibold mb-6">My Modelfiles</div>
<div class=" text-2xl font-semibold mb-6">{$i18n.t('My Modelfiles')}</div>
<button
class="flex space-x-1"
......@@ -270,7 +272,7 @@
/>
</svg>
</div>
<div class=" self-center font-medium text-sm">Back</div>
<div class=" self-center font-medium text-sm">{$i18n.t('Back')}</div>
</button>
<hr class="my-3 dark:border-gray-700" />
......@@ -317,12 +319,12 @@
<div class="my-2 flex space-x-2">
<div class="flex-1">
<div class=" text-sm font-semibold mb-2">Name*</div>
<div class=" text-sm font-semibold mb-2">{$i18n.t('Name')}*</div>
<div>
<input
class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
placeholder="Name your modelfile"
placeholder={$i18n.t('Name your modelfile')}
bind:value={title}
required
/>
......@@ -330,12 +332,12 @@
</div>
<div class="flex-1">
<div class=" text-sm font-semibold mb-2">Model Tag Name*</div>
<div class=" text-sm font-semibold mb-2">{$i18n.t('Model Tag Name')}*</div>
<div>
<input
class="px-3 py-1.5 text-sm w-full bg-transparent disabled:text-gray-500 border dark:border-gray-600 outline-none rounded-lg"
placeholder="Add a model tag name"
placeholder={$i18n.t('Add a model tag name')}
value={tagName}
disabled
required
......@@ -345,12 +347,12 @@
</div>
<div class="my-2">
<div class=" text-sm font-semibold mb-2">Description*</div>
<div class=" text-sm font-semibold mb-2">{$i18n.t('Description')}*</div>
<div>
<input
class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
placeholder="Add a short description about what this modelfile does"
placeholder={$i18n.t('Add a short description about what this modelfile does')}
bind:value={desc}
required
/>
......@@ -359,13 +361,13 @@
<div class="my-2">
<div class="flex w-full justify-between">
<div class=" self-center text-sm font-semibold">Modelfile</div>
<div class=" self-center text-sm font-semibold">{$i18n.t('Modelfile')}</div>
</div>
<!-- <div class=" text-sm font-semibold mb-2"></div> -->
<div class="mt-2">
<div class=" text-xs font-semibold mb-2">Content*</div>
<div class=" text-xs font-semibold mb-2">{$i18n.t('Content')}*</div>
<div>
<textarea
......@@ -381,7 +383,7 @@
<div class="my-2">
<div class="flex w-full justify-between mb-2">
<div class=" self-center text-sm font-semibold">Prompt suggestions</div>
<div class=" self-center text-sm font-semibold">{$i18n.t('Prompt suggestions')}</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
......@@ -409,7 +411,7 @@
<div class=" flex border dark:border-gray-600 rounded-lg">
<input
class="px-3 py-1.5 text-sm w-full bg-transparent outline-none border-r dark:border-gray-600"
placeholder="Write a prompt suggestion (e.g. Who are you?)"
placeholder={$i18n.t('Write a prompt suggestion (e.g. Who are you?)')}
bind:value={prompt.content}
/>
......@@ -438,7 +440,7 @@
</div>
<div class="my-2">
<div class=" text-sm font-semibold mb-2">Categories</div>
<div class=" text-sm font-semibold mb-2">{$i18n.t('Categories')}</div>
<div class="grid grid-cols-4">
{#each Object.keys(categories) as category}
......@@ -453,7 +455,7 @@
{#if pullProgress !== null}
<div class="my-2">
<div class=" text-sm font-semibold mb-2">Pull Progress</div>
<div class=" text-sm font-semibold mb-2">{$i18n.t('Pull Progress')}</div>
<div class="w-full rounded-full dark:bg-gray-800">
<div
class="dark:bg-gray-600 text-xs font-medium text-blue-100 text-center p-0.5 leading-none rounded-full"
......@@ -476,7 +478,7 @@
type="submit"
disabled={loading}
>
<div class=" self-center font-medium">Save & Update</div>
<div class=" self-center font-medium">{$i18n.t('Save & Update')}</div>
{#if loading}
<div class="ml-1.5 self-center">
......
<script lang="ts">
import { goto } from '$app/navigation';
import { onMount, tick, getContext } from 'svelte';
import { toast } from 'svelte-sonner';
import {
LITELLM_API_BASE_URL,
OLLAMA_API_BASE_URL,
OPENAI_API_BASE_URL,
WEBUI_API_BASE_URL
} from '$lib/constants';
import { WEBUI_NAME, config, user, models, settings } from '$lib/stores';
import { cancelChatCompletion, generateChatCompletion } from '$lib/apis/ollama';
import { generateOpenAIChatCompletion } from '$lib/apis/openai';
import { splitStream } from '$lib/utils';
import ChatCompletion from '$lib/components/playground/ChatCompletion.svelte';
const i18n = getContext('i18n');
let mode = 'chat';
let loaded = false;
let text = '';
let selectedModelId = '';
let loading = false;
let currentRequestId = null;
let stopResponseFlag = false;
let messagesContainerElement: HTMLDivElement;
let textCompletionAreaElement: HTMLTextAreaElement;
let system = '';
let messages = [
{
role: 'user',
content: ''
}
];
const scrollToBottom = () => {
const element = mode === 'chat' ? messagesContainerElement : textCompletionAreaElement;
if (element) {
element.scrollTop = element?.scrollHeight;
}
};
// const cancelHandler = async () => {
// if (currentRequestId) {
// const res = await cancelChatCompletion(localStorage.token, currentRequestId);
// currentRequestId = null;
// loading = false;
// }
// };
const stopResponse = () => {
stopResponseFlag = true;
console.log('stopResponse');
};
const textCompletionHandler = async () => {
const model = $models.find((model) => model.id === selectedModelId);
const res = await generateOpenAIChatCompletion(
localStorage.token,
{
model: model.id,
stream: true,
messages: [
{
role: 'assistant',
content: text
}
]
},
model.external
? model.source === 'litellm'
? `${LITELLM_API_BASE_URL}/v1`
: `${OPENAI_API_BASE_URL}`
: `${OLLAMA_API_BASE_URL}/v1`
);
if (res && res.ok) {
const reader = res.body
.pipeThrough(new TextDecoderStream())
.pipeThrough(splitStream('\n'))
.getReader();
while (true) {
const { value, done } = await reader.read();
if (done || stopResponseFlag) {
if (stopResponseFlag) {
await cancelChatCompletion(localStorage.token, currentRequestId);
}
currentRequestId = null;
break;
}
try {
let lines = value.split('\n');
for (const line of lines) {
if (line !== '') {
if (line === 'data: [DONE]') {
// responseMessage.done = true;
console.log('done');
} else {
let data = JSON.parse(line.replace(/^data: /, ''));
console.log(data);
if ('request_id' in data) {
currentRequestId = data.request_id;
} else {
text += data.choices[0].delta.content ?? '';
}
}
}
}
} catch (error) {
console.log(error);
}
scrollToBottom();
}
}
};
const chatCompletionHandler = async () => {
const model = $models.find((model) => model.id === selectedModelId);
const res = await generateOpenAIChatCompletion(
localStorage.token,
{
model: model.id,
stream: true,
messages: [
system
? {
role: 'system',
content: system
}
: undefined,
...messages
].filter((message) => message)
},
model.external
? model.source === 'litellm'
? `${LITELLM_API_BASE_URL}/v1`
: `${OPENAI_API_BASE_URL}`
: `${OLLAMA_API_BASE_URL}/v1`
);
let responseMessage;
if (messages.at(-1)?.role === 'assistant') {
responseMessage = messages.at(-1);
} else {
responseMessage = {
role: 'assistant',
content: ''
};
messages.push(responseMessage);
messages = messages;
}
await tick();
const textareaElement = document.getElementById(`assistant-${messages.length - 1}-textarea`);
if (res && res.ok) {
const reader = res.body
.pipeThrough(new TextDecoderStream())
.pipeThrough(splitStream('\n'))
.getReader();
while (true) {
const { value, done } = await reader.read();
if (done || stopResponseFlag) {
if (stopResponseFlag) {
await cancelChatCompletion(localStorage.token, currentRequestId);
}
currentRequestId = null;
break;
}
try {
let lines = value.split('\n');
for (const line of lines) {
if (line !== '') {
console.log(line);
if (line === 'data: [DONE]') {
// responseMessage.done = true;
messages = messages;
} else {
let data = JSON.parse(line.replace(/^data: /, ''));
console.log(data);
if ('request_id' in data) {
currentRequestId = data.request_id;
} else {
if (responseMessage.content == '' && data.choices[0].delta.content == '\n') {
continue;
} else {
textareaElement.style.height = textareaElement.scrollHeight + 'px';
responseMessage.content += data.choices[0].delta.content ?? '';
messages = messages;
textareaElement.style.height = textareaElement.scrollHeight + 'px';
await tick();
}
}
}
}
}
} catch (error) {
console.log(error);
}
scrollToBottom();
}
}
};
const submitHandler = async () => {
if (selectedModelId) {
loading = true;
if (mode === 'complete') {
await textCompletionHandler();
} else if (mode === 'chat') {
await chatCompletionHandler();
}
loading = false;
stopResponseFlag = false;
currentRequestId = null;
}
};
onMount(async () => {
if ($user?.role !== 'admin') {
await goto('/');
}
if ($settings?.models) {
selectedModelId = $settings?.models[0];
} else if ($config?.default_models) {
selectedModelId = $config?.default_models.split(',')[0];
} else {
selectedModelId = '';
}
loaded = true;
});
</script>
<svelte:head>
<title>
{$i18n.t('Playground')} | {$WEBUI_NAME}
</title>
</svelte:head>
<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
<div class=" flex flex-col justify-between w-full overflow-y-auto h-[100dvh]">
<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10 h-full">
<div class=" flex flex-col h-full">
<div class="flex flex-col justify-between mb-2.5 gap-1">
<div class="flex justify-between items-center gap-2">
<div class=" text-2xl font-semibold self-center flex">
{$i18n.t('Playground')}
<span class=" text-xs text-gray-500 self-center ml-1">{$i18n.t('(Beta)')}</span>
</div>
<div>
<button
class=" flex items-center gap-0.5 text-xs px-2.5 py-0.5 rounded-lg {mode ===
'chat' && 'text-sky-600 dark:text-sky-200 bg-sky-200/30'} {mode === 'complete' &&
'text-green-600 dark:text-green-200 bg-green-200/30'} "
on:click={() => {
if (mode === 'complete') {
mode = 'chat';
} else {
mode = 'complete';
}
}}
>
{#if mode === 'complete'}
{$i18n.t('Text Completion')}
{:else if mode === 'chat'}
{$i18n.t('Chat')}
{/if}
<div>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-3 h-3"
>
<path
fill-rule="evenodd"
d="M5.22 10.22a.75.75 0 0 1 1.06 0L8 11.94l1.72-1.72a.75.75 0 1 1 1.06 1.06l-2.25 2.25a.75.75 0 0 1-1.06 0l-2.25-2.25a.75.75 0 0 1 0-1.06ZM10.78 5.78a.75.75 0 0 1-1.06 0L8 4.06 6.28 5.78a.75.75 0 0 1-1.06-1.06l2.25-2.25a.75.75 0 0 1 1.06 0l2.25 2.25a.75.75 0 0 1 0 1.06Z"
clip-rule="evenodd"
/>
</svg>
</div>
</button>
</div>
</div>
<div class=" flex gap-1 px-1">
<select
id="models"
class="outline-none bg-transparent text-sm font-medium rounded-lg w-full placeholder-gray-400"
bind:value={selectedModelId}
>
<option class=" text-gray-800" value="" selected disabled
>{$i18n.t('Select a model')}</option
>
{#each $models as model}
{#if model.name === 'hr'}
<hr />
{:else}
<option value={model.id} class="text-gray-800 text-lg"
>{model.name +
`${model.size ? ` (${(model.size / 1024 ** 3).toFixed(1)}GB)` : ''}`}</option
>
{/if}
{/each}
</select>
<!-- <button
class=" self-center dark:hover:text-gray-300"
id="open-settings-button"
on:click={async () => {}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
</button> -->
</div>
</div>
{#if mode === 'chat'}
<div class="p-1">
<div class="p-3 outline outline-1 outline-gray-200 dark:outline-gray-800 rounded-lg">
<div class=" text-sm font-medium">{$i18n.t('System')}</div>
<textarea
id="system-textarea"
class="w-full h-full bg-transparent resize-none outline-none text-sm"
bind:value={system}
placeholder={$i18n.t("You're a helpful assistant.")}
rows="4"
/>
</div>
</div>
{/if}
<div
class=" pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0"
id="messages-container"
bind:this={messagesContainerElement}
>
<div class=" h-full w-full flex flex-col">
<div class="flex-1 p-1">
{#if mode === 'complete'}
<textarea
id="text-completion-textarea"
bind:this={textCompletionAreaElement}
class="w-full h-full p-3 bg-transparent outline outline-1 outline-gray-200 dark:outline-gray-800 resize-none rounded-lg text-sm"
bind:value={text}
placeholder={$i18n.t("You're a helpful assistant.")}
/>
{:else if mode === 'chat'}
<ChatCompletion bind:messages />
{/if}
</div>
</div>
</div>
<div class="pb-2">
{#if !loading}
<button
class="px-3 py-1.5 text-sm font-medium bg-emerald-600 hover:bg-emerald-700 text-gray-50 transition rounded-lg"
on:click={() => {
submitHandler();
}}
>
{$i18n.t('Submit')}
</button>
{:else}
<button
class="px-3 py-1.5 text-sm font-medium bg-gray-100 hover:bg-gray-200 text-gray-900 transition rounded-lg"
on:click={() => {
stopResponse();
}}
>
{$i18n.t('Cancel')}
</button>
{/if}
</div>
</div>
</div>
</div>
</div>
<style>
.scrollbar-hidden::-webkit-scrollbar {
display: none; /* for Chrome, Safari and Opera */
}
.scrollbar-hidden {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
</style>
<script lang="ts">
import toast from 'svelte-french-toast';
import { toast } from 'svelte-sonner';
import fileSaver from 'file-saver';
const { saveAs } = fileSaver;
import { onMount } from 'svelte';
import { prompts } from '$lib/stores';
import { onMount, getContext } from 'svelte';
import { WEBUI_NAME, prompts } from '$lib/stores';
import { createNewPrompt, deletePromptByCommand, getPrompts } from '$lib/apis/prompts';
import { error } from '@sveltejs/kit';
import { goto } from '$app/navigation';
const i18n = getContext('i18n');
let importFiles = '';
let query = '';
let promptsImportInputElement: HTMLInputElement;
const sharePrompt = async (prompt) => {
toast.success('Redirecting you to OpenWebUI Community');
toast.success($i18n.t('Redirecting you to OpenWebUI Community'));
const url = 'https://openwebui.com';
......@@ -35,11 +38,17 @@
};
</script>
<svelte:head>
<title>
{$i18n.t('Prompts')} | {$WEBUI_NAME}
</title>
</svelte:head>
<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
<div class=" py-2.5 flex flex-col justify-between w-full overflow-y-auto">
<div class="flex flex-col justify-between w-full overflow-y-auto">
<div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10">
<div class="mb-6 flex justify-between items-center">
<div class=" text-2xl font-semibold self-center">My Prompts</div>
<div class=" text-2xl font-semibold self-center">{$i18n.t('My Prompts')}</div>
</div>
<div class=" flex w-full space-x-2">
......@@ -61,13 +70,13 @@
<input
class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-none bg-transparent"
bind:value={query}
placeholder="Search Prompt"
placeholder={$i18n.t('Search Prompts')}
/>
</div>
<div>
<a
class=" px-2 py-2 rounded-xl border border-gray-200 dark:border-gray-600 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 transition font-medium text-sm flex items-center space-x-1"
class=" px-2 py-2 rounded-xl border border-gray-200 dark:border-gray-600 dark:border-0 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 transition font-medium text-sm flex items-center space-x-1"
href="/prompts/create"
>
<svg
......@@ -83,13 +92,13 @@
</a>
</div>
</div>
<hr class=" dark:border-gray-700 my-2.5" />
{#if $prompts.length === 0}
<div />
{:else}
<div class="my-3 mb-5">
{#each $prompts.filter((p) => query === '' || p.command.includes(query)) as prompt}
<hr class=" dark:border-gray-700 my-2.5" />
<div class=" flex space-x-4 cursor-pointer w-full mb-3">
<div
class=" flex space-x-4 cursor-pointer w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl"
>
<div class=" flex flex-1 space-x-4 cursor-pointer w-full">
<a href={`/prompts/edit?command=${encodeURIComponent(prompt.command)}`}>
<div class=" flex-1 self-center pl-5">
......@@ -102,7 +111,7 @@
</div>
<div class="flex flex-row space-x-1 self-center">
<a
class="self-center w-fit text-sm px-2 py-2 border dark:border-gray-600 rounded-xl"
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
type="button"
href={`/prompts/edit?command=${encodeURIComponent(prompt.command)}`}
>
......@@ -123,7 +132,32 @@
</a>
<button
class="self-center w-fit text-sm px-2 py-2 border dark:border-gray-600 rounded-xl"
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
type="button"
on:click={() => {
// console.log(modelfile);
sessionStorage.prompt = JSON.stringify(prompt);
goto('/prompts/create');
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 0 1-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 0 1 1.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 0 0-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 0 1-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H9.75"
/>
</svg>
</button>
<button
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
type="button"
on:click={() => {
sharePrompt(prompt);
......@@ -146,7 +180,7 @@
</button>
<button
class="self-center w-fit text-sm px-2 py-2 border dark:border-gray-600 rounded-xl"
class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
type="button"
on:click={() => {
deletePrompt(prompt.command);
......@@ -170,14 +204,13 @@
</div>
</div>
{/each}
{/if}
<hr class=" dark:border-gray-700 my-2.5" />
</div>
<div class=" flex justify-between w-full mb-3">
<div class=" flex justify-end w-full mb-3">
<div class="flex space-x-2">
<input
id="prompts-import-input"
bind:this={promptsImportInputElement}
bind:files={importFiles}
type="file"
accept=".json"
......@@ -210,12 +243,12 @@
/>
<button
class="self-center w-fit text-sm px-3 py-1 border dark:border-gray-600 rounded-xl flex"
on:click={async () => {
document.getElementById('prompts-import-input')?.click();
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
on:click={() => {
promptsImportInputElement.click();
}}
>
<div class=" self-center mr-2 font-medium">Import Prompts</div>
<div class=" self-center mr-2 font-medium">{$i18n.t('Import Prompts')}</div>
<div class=" self-center">
<svg
......@@ -234,16 +267,16 @@
</button>
<button
class="self-center w-fit text-sm px-3 py-1 border dark:border-gray-600 rounded-xl flex"
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
on:click={async () => {
// document.getElementById('modelfiles-import-input')?.click();
// promptsImportInputElement.click();
let blob = new Blob([JSON.stringify($prompts)], {
type: 'application/json'
});
saveAs(blob, `prompts-export-${Date.now()}.json`);
}}
>
<div class=" self-center mr-2 font-medium">Export Prompts</div>
<div class=" self-center mr-2 font-medium">{$i18n.t('Export Prompts')}</div>
<div class=" self-center">
<svg
......@@ -272,10 +305,10 @@
</div>
<div class=" my-16">
<div class=" text-2xl font-semibold mb-6">Made by OpenWebUI Community</div>
<div class=" text-2xl font-semibold mb-3">{$i18n.t('Made by OpenWebUI Community')}</div>
<a
class=" flex space-x-4 cursor-pointer w-full mb-3"
class=" flex space-x-4 cursor-pointer w-full mb-3 px-3 py-2"
href="https://openwebui.com/?type=prompts"
target="_blank"
>
......@@ -299,8 +332,8 @@
</div>
<div class=" self-center">
<div class=" font-bold">Discover a prompt</div>
<div class=" text-sm">Discover, download, and explore custom Prompts</div>
<div class=" font-bold">{$i18n.t('Discover a prompt')}</div>
<div class=" text-sm">{$i18n.t('Discover, download, and explore custom prompts')}</div>
</div>
</a>
</div>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment