From afd62c3b227a93b35a14498832fe7abcfa4fb8c6 Mon Sep 17 00:00:00 2001 From: Que Nguyen Date: Sun, 2 Jun 2024 13:53:38 +0700 Subject: [PATCH 01/40] Update translation.json These changes serve to complete the Vietnamese translation. --- src/lib/i18n/locales/vi-VN/translation.json | 56 ++++++++++----------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/lib/i18n/locales/vi-VN/translation.json b/src/lib/i18n/locales/vi-VN/translation.json index 81cc687de3..152f10df38 100644 --- a/src/lib/i18n/locales/vi-VN/translation.json +++ b/src/lib/i18n/locales/vi-VN/translation.json @@ -8,13 +8,13 @@ "{{modelName}} is thinking...": "{{modelName}} đang suy nghĩ...", "{{user}}'s Chats": "{{user}}'s Chats", "{{webUIName}} Backend Required": "{{webUIName}} Yêu cầu Backend", - "A task model is used when performing tasks such as generating titles for chats and web search queries": "", + "A task model is used when performing tasks such as generating titles for chats and web search queries": "Mô hình tác vụ được sử dụng khi thực hiện các tác vụ như tạo tiêu đề cho cuộc trò chuyện và truy vấn tìm kiếm trên web", "a user": "người sử dụng", "About": "Giới thiệu", "Account": "Tài khoản", "Accurate information": "Thông tin chính xác", "Add": "Thêm", - "Add a model id": "", + "Add a model id": "Thêm model id", "Add a short description about what this model does": "Thêm mô tả ngắn về những khả năng của model", "Add a short title for this prompt": "Thêm tiêu đề ngắn cho prompt này", "Add a tag": "Thêm thẻ (tag)", @@ -31,7 +31,7 @@ "Admin Panel": "Trang Quản trị", "Admin Settings": "Cài đặt hệ thống", "Advanced Parameters": "Các tham số Nâng cao", - "Advanced Params": "", + "Advanced Params": "Các tham số Nâng cao", "all": "tất cả", "All Documents": "Tất cả tài liệu", "All Users": "Danh sách người sử dụng", @@ -93,14 +93,14 @@ "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 trong cột VAI TRÒ để thay đổi quyền của người sử dụng.", - "Clone": "", + "Clone": "Nhân bản", "Close": "Đóng", "Collection": "Tổng hợp mọi tài liệu", "ComfyUI": "ComfyUI", "ComfyUI Base URL": "ComfyUI Base URL", "ComfyUI Base URL is required.": "Base URL của ComfyUI là bắt buộc.", "Command": "Lệnh", - "Concurrent Requests": "", + "Concurrent Requests": "Các truy vấn đồng thời", "Confirm Password": "Xác nhận Mật khẩu", "Connections": "Kết nối", "Content": "Nội dung", @@ -131,7 +131,7 @@ "Default (Automatic1111)": "Mặc định (Automatic1111)", "Default (SentenceTransformers)": "Mặc định (SentenceTransformers)", "Default (Web API)": "Mặc định (Web API)", - "Default Model": "", + "Default Model": "Model mặc định", "Default model updated": "Mô hình mặc định đã được cập nhật", "Default Prompt Suggestions": "Đề xuất prompt mặc định", "Default User Role": "Vai trò mặc định", @@ -173,27 +173,27 @@ "Embedding Model Engine": "Trình xử lý embedding", "Embedding model set to \"{{embedding_model}}\"": "Mô hình embedding đã được thiết lập thành \"{{embedding_model}}\"", "Enable Chat History": "Bật Lịch sử chat", - "Enable Community Sharing": "", + "Enable Community Sharing": "Kích hoạt Chia sẻ Cộng đồng", "Enable New Sign Ups": "Cho phép đăng ký mới", - "Enable Web Search": "", + "Enable Web Search": "Kích hoạt tìm kiếm Web", "Enabled": "Đã bật", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Đảm bảo tệp CSV của bạn bao gồm 4 cột theo thứ tự sau: Name, Email, Password, Role.", "Enter {{role}} message here": "Nhập yêu cầu của {{role}} ở đây", "Enter a detail about yourself for your LLMs to recall": "Nhập chi tiết về bản thân của bạn để LLMs có thể nhớ", - "Enter Brave Search API Key": "", + "Enter Brave Search API Key": "Nhập API key cho Brave Search", "Enter Chunk Overlap": "Nhập Chunk chồng lấn (overlap)", "Enter Chunk Size": "Nhập Kích thước Chunk", - "Enter Github Raw URL": "", - "Enter Google PSE API Key": "", - "Enter Google PSE Engine Id": "", + "Enter Github Raw URL": "Nhập URL cho Github Raw", + "Enter Google PSE API Key": "Nhập Google PSE API Key", + "Enter Google PSE Engine Id": "Nhập Google PSE Engine Id", "Enter Image Size (e.g. 512x512)": "Nhập Kích thước ảnh (vd: 512x512)", "Enter language codes": "Nhập mã ngôn ngữ", "Enter model tag (e.g. {{modelTag}})": "Nhập thẻ mô hình (vd: {{modelTag}})", "Enter Number of Steps (e.g. 50)": "Nhập số Steps (vd: 50)", "Enter Score": "Nhập Score", - "Enter Searxng Query URL": "", - "Enter Serper API Key": "", - "Enter Serpstack API Key": "", + "Enter Searxng Query URL": "Nhập Query URL cho Searxng", + "Enter Serper API Key": "Nhập Serper API Key", + "Enter Serpstack API Key": "Nhập Serpstack API Key", "Enter stop sequence": "Nhập stop sequence", "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/)", @@ -207,7 +207,7 @@ "Export All Chats (All Users)": "Tải về tất cả nội dung chat (tất cả mọi người)", "Export Chats": "Tải nội dung chat về máy", "Export Documents Mapping": "Tải cấu trúc tài liệu về máy", - "Export Models": "", + "Export Models": "Tải Models về máy", "Export Prompts": "Tải các prompt về máy", "Failed to create API Key.": "Lỗi khởi tạo API Key", "Failed to read clipboard contents": "Không thể đọc nội dung clipboard", @@ -224,7 +224,7 @@ "Full Screen Mode": "Chế độ Toàn màn hình", "General": "Cài đặt chung", "General Settings": "Cấu hình chung", - "Generating search query": "", + "Generating search query": "Tạo truy vấn tìm kiếm", "Generation Info": "Thông tin chung", "Good Response": "Trả lời tốt", "Google PSE API Key": "", @@ -253,7 +253,7 @@ "January": "Tháng 1", "join our Discord for help.": "tham gia Discord của chúng tôi để được trợ giúp.", "JSON": "JSON", - "JSON Preview": "", + "JSON Preview": "Xem trước JSON", "July": "Tháng 7", "June": "Tháng 6", "JWT Expiration": "JWT Hết hạn", @@ -270,9 +270,9 @@ "Make sure to enclose them with": "Hãy chắc chắn bao quanh chúng bằng", "Manage Models": "Quản lý mô hình", "Manage Ollama Models": "Quản lý mô hình với Ollama", - "Manage Pipelines": "", + "Manage Pipelines": "Quản lý Pipelines", "March": "Tháng 3", - "Max Tokens (num_predict)": "", + "Max Tokens (num_predict)": "Tokens tối đa (num_predict)", "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.", "May": "Tháng 5", "Memories accessible by LLMs will be shown here.": "Memory có thể truy cập bởi LLMs sẽ hiển thị ở đây.", @@ -304,7 +304,7 @@ "New Chat": "Tạo chat mới", "New Password": "Mật khẩu mới", "No results found": "Không tìm thấy kết quả", - "No search query generated": "", + "No search query generated": "Không có truy vấn tìm kiếm nào được tạo ra", "No source available": "Không có nguồn", "None": "", "Not factually correct": "Không chính xác so với thực tế", @@ -391,9 +391,9 @@ "Search Documents": "Tìm tài liệu", "Search Models": "Tìm model", "Search Prompts": "Tìm prompt", - "Search Result Count": "", - "Searched {{count}} sites_other": "", - "Searching the web for '{{searchQuery}}'": "", + "Search Result Count": "Số kết quả tìm kiếm", + "Searched {{count}} sites_other": "Đã tìm {{count}} sites_other", + "Searching the web for '{{searchQuery}}'": "Tìm kiếm web cho '{{searchQuery}}'", "Searxng Query URL": "", "See readme.md for instructions": "Xem readme.md để biết hướng dẫn", "See what's new": "Xem những cập nhật mới", @@ -405,7 +405,7 @@ "Select a pipeline url": "", "Select an Ollama instance": "Chọn một thực thể Ollama", "Select model": "Chọn model", - "Selected model(s) do not support image inputs": "", + "Selected model(s) do not support image inputs": "Model được lựa chọn không hỗ trợ đầu vào là hình ảnh", "Send": "Gửi", "Send a Message": "Gửi yêu cầu", "Send message": "Gửi yêu cầu", @@ -486,7 +486,7 @@ "Update and Copy Link": "Cập nhật và sao chép link", "Update password": "Cập nhật mật khẩu", "Upload a GGUF model": "Tải lên mô hình GGUF", - "Upload Files": "", + "Upload Files": "Tải tệp lên máy chủ", "Upload Progress": "Tiến trình tải tệp lên hệ thống", "URL Mode": "Chế độ URL", "Use '#' in the prompt input to load and select your documents.": "Sử dụng '#' trong đầu vào của prompt để tải về và lựa chọn tài liệu của bạn cần truy vấn.", @@ -505,8 +505,8 @@ "Web": "Web", "Web Loader Settings": "Cài đặt Web Loader", "Web Params": "Web Params", - "Web Search": "", - "Web Search Engine": "", + "Web Search": "Tìm kiếm Web", + "Web Search Engine": "Chức năng Tìm kiếm Web", "Webhook URL": "Webhook URL", "WebUI Add-ons": "Tiện ích WebUI", "WebUI Settings": "Cài đặt WebUI", From 45bff743a28451ff877dd7aeb87dcf16b6fa24d3 Mon Sep 17 00:00:00 2001 From: Jun Siang Cheah Date: Sun, 2 Jun 2024 10:01:01 +0100 Subject: [PATCH 02/40] fix: docker build on tag broke due to cache --- .github/workflows/docker-build.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml index 86d27f4dc7..b37a419919 100644 --- a/.github/workflows/docker-build.yaml +++ b/.github/workflows/docker-build.yaml @@ -70,6 +70,7 @@ jobs: images: ${{ env.FULL_IMAGE_NAME }} tags: | type=ref,event=branch + type=ref,event=tag flavor: | prefix=cache-${{ matrix.platform }}- From 80c6a39fab70c3d3a2395906cdf69e7b6e116869 Mon Sep 17 00:00:00 2001 From: SimonOriginal Date: Sun, 2 Jun 2024 14:42:15 +0200 Subject: [PATCH 03/40] Updated Ukrainian translation to v0.2.0 --- src/lib/i18n/locales/uk-UA/translation.json | 146 ++++++++++---------- 1 file changed, 73 insertions(+), 73 deletions(-) diff --git a/src/lib/i18n/locales/uk-UA/translation.json b/src/lib/i18n/locales/uk-UA/translation.json index ca5efeba2b..14ae51153b 100644 --- a/src/lib/i18n/locales/uk-UA/translation.json +++ b/src/lib/i18n/locales/uk-UA/translation.json @@ -3,19 +3,19 @@ "(Beta)": "(Beta)", "(e.g. `sh webui.sh --api`)": "(e.g. `sh webui.sh --api`)", "(latest)": "(остання)", - "{{ models }}": "", - "{{ owner }}: You cannot delete a base model": "", + "{{ models }}": "{{ models }}", + "{{ owner }}: You cannot delete a base model": "{{ owner }}: Ви не можете видалити базову модель.", "{{modelName}} is thinking...": "{{modelName}} думає...", "{{user}}'s Chats": "Чати {{user}}а", "{{webUIName}} Backend Required": "Необхідно підключення бекенду {{webUIName}}", - "A task model is used when performing tasks such as generating titles for chats and web search queries": "", + "A task model is used when performing tasks such as generating titles for chats and web search queries": "Модель задач використовується при виконанні таких завдань, як генерація заголовків для чатів та пошукових запитів в Інтернеті", "a user": "користувача", "About": "Про програму", "Account": "Обліковий запис", "Accurate information": "Точна інформація", "Add": "Додати", - "Add a model id": "", - "Add a short description about what this model does": "", + "Add a model id": "Додайте id моделі", + "Add a short description about what this model does": "Додайте короткий опис того, що робить ця модель", "Add a short title for this prompt": "Додати коротку назву для цього промту", "Add a tag": "Додайте тег", "Add custom prompt": "Додати користувацьку підказку", @@ -28,10 +28,10 @@ "Add User": "Додати користувача", "Adjusting these settings will apply changes universally to all users.": "Зміни в цих налаштуваннях будуть застосовані для всіх користувачів.", "admin": "адмін", - "Admin Panel": "Панель адміністратора", + "Admin Panel": "Адмін-панель", "Admin Settings": "Налаштування адміністратора", "Advanced Parameters": "Розширені параметри", - "Advanced Params": "", + "Advanced Params": "Розширені параметри", "all": "всі", "All Documents": "Усі документи", "All Users": "Всі користувачі", @@ -48,7 +48,7 @@ "API keys": "Ключі API", "April": "Квітень", "Archive": "Архів", - "Archive All Chats": "", + "Archive All Chats": "Архівувати всі чати", "Archived Chats": "Архівовані чати", "are allowed - Activate this command by typing": "дозволено - активізуйте цю команду набором", "Are you sure?": "Ви впевнені?", @@ -63,14 +63,14 @@ "available!": "доступно!", "Back": "Назад", "Bad Response": "Неправильна відповідь", - "Banners": "", - "Base Model (From)": "", + "Banners": "Прапори", + "Base Model (From)": "Базова модель (від)", "before": "до того, як", "Being lazy": "Не поспішати", - "Brave Search API Key": "", + "Brave Search API Key": "Ключ API пошуку Brave", "Bypass SSL verification for Websites": "Обхід SSL-перевірки для веб-сайтів", "Cancel": "Скасувати", - "Capabilities": "", + "Capabilities": "Можливості", "Change Password": "Змінити пароль", "Chat": "Чат", "Chat Bubble UI": "Бульбашковий UI чату", @@ -93,14 +93,14 @@ "Click here to select documents.": "Натисніть тут, щоб вибрати документи.", "click here.": "клацніть тут.", "Click on the user role button to change a user's role.": "Натисніть кнопку ролі користувача, щоб змінити роль користувача.", - "Clone": "", + "Clone": "Клонувати", "Close": "Закрити", "Collection": "Колекція", "ComfyUI": "ComfyUI", "ComfyUI Base URL": "URL-адреса ComfyUI", "ComfyUI Base URL is required.": "Необхідно вказати URL-адресу ComfyUI.", "Command": "Команда", - "Concurrent Requests": "", + "Concurrent Requests": "Одночасні запити", "Confirm Password": "Підтвердіть пароль", "Connections": "З'єднання", "Content": "Зміст", @@ -114,7 +114,7 @@ "Copy Link": "Копіювати посилання", "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 model": "", + "Create a model": "Створити модель", "Create Account": "Створити обліковий запис", "Create new key": "Створити новий ключ", "Create new secret key": "Створити новий секретний ключ", @@ -123,7 +123,7 @@ "Current Model": "Поточна модель", "Current Password": "Поточний пароль", "Custom": "Налаштувати", - "Customize models for a specific purpose": "", + "Customize models for a specific purpose": "Налаштуйте моделі для конкретних цілей", "Dark": "Темна", "Database": "База даних", "December": "Грудень", @@ -131,24 +131,24 @@ "Default (Automatic1111)": "За замовчуванням (Automatic1111)", "Default (SentenceTransformers)": "За замовчуванням (SentenceTransformers)", "Default (Web API)": "За замовчуванням (Web API)", - "Default Model": "", + "Default Model": "Модель за замовчуванням", "Default model updated": "Модель за замовчуванням оновлено", "Default Prompt Suggestions": "Пропозиції промтів замовчуванням", "Default User Role": "Роль користувача за замовчуванням", "delete": "видалити", "Delete": "Видалити", "Delete a model": "Видалити модель", - "Delete All Chats": "", + "Delete All Chats": "Видалити усі чати", "Delete chat": "Видалити чат", "Delete Chat": "Видалити чат", "delete this link": "видалити це посилання", "Delete User": "Видалити користувача", "Deleted {{deleteModelTag}}": "Видалено {{deleteModelTag}}", - "Deleted {{name}}": "", + "Deleted {{name}}": "Видалено {{name}}", "Description": "Опис", "Didn't fully follow instructions": "Не повністю дотримувалися інструкцій", "Disabled": "Вимкнено", - "Discover a model": "", + "Discover a model": "Знайдіть модель", "Discover a prompt": "Знайти промт", "Discover, download, and explore custom prompts": "Знайдіть, завантажте та досліджуйте налаштовані промти", "Discover, download, and explore model presets": "Знайдіть, завантажте та досліджуйте налаштовані налаштування моделі", @@ -173,27 +173,27 @@ "Embedding Model Engine": "Двигун модели встраивания ", "Embedding model set to \"{{embedding_model}}\"": "Встановлена модель вбудовування \"{{embedding_model}}\"", "Enable Chat History": "Увімкнути історію чату", - "Enable Community Sharing": "", + "Enable Community Sharing": "Ввімкніть спільний доступ до спільноти", "Enable New Sign Ups": "Дозволити нові реєстрації", - "Enable Web Search": "", + "Enable Web Search": "Увімкнути веб-пошук", "Enabled": "Увімкнено", "Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Переконайтеся, що ваш CSV-файл містить 4 колонки в такому порядку: Ім'я, Email, Пароль, Роль.", "Enter {{role}} message here": "Введіть повідомлення {{role}} тут", "Enter a detail about yourself for your LLMs to recall": "Введіть відомості про себе для запам'ятовування вашими LLM.", - "Enter Brave Search API Key": "", + "Enter Brave Search API Key": "Введіть ключ API для пошуку Brave", "Enter Chunk Overlap": "Введіть перекриття фрагменту", "Enter Chunk Size": "Введіть розмір фрагменту", - "Enter Github Raw URL": "", - "Enter Google PSE API Key": "", - "Enter Google PSE Engine Id": "", + "Enter Github Raw URL": "Введіть Raw URL-адресу Github", + "Enter Google PSE API Key": "Введіть ключ API Google PSE", + "Enter Google PSE Engine Id": "Введіть Google PSE Engine Id", "Enter Image Size (e.g. 512x512)": "Введіть розмір зображення (напр., 512x512)", "Enter language codes": "Введіть мовні коди", "Enter model tag (e.g. {{modelTag}})": "Введіть тег моделі (напр., {{modelTag}})", "Enter Number of Steps (e.g. 50)": "Введіть кількість кроків (напр., 50)", "Enter Score": "Введіть бал", - "Enter Searxng Query URL": "", - "Enter Serper API Key": "", - "Enter Serpstack API Key": "", + "Enter Searxng Query URL": "Введіть URL-адресу запиту Searxng", + "Enter Serper API Key": "Введіть ключ API Serper", + "Enter Serpstack API Key": "Введіть ключ API Serpstack", "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/)", @@ -202,12 +202,12 @@ "Enter Your Full Name": "Введіть ваше ім'я", "Enter Your Password": "Введіть ваш пароль", "Enter Your Role": "Введіть вашу роль", - "Error": "", + "Error": "Помилка", "Experimental": "Експериментальне", "Export All Chats (All Users)": "Експортувати всі чати (всі користувачі)", "Export Chats": "Експортувати чати", "Export Documents Mapping": "Експортувати відображення документів", - "Export Models": "", + "Export Models": "Експорт моделей", "Export Prompts": "Експортувати промти", "Failed to create API Key.": "Не вдалося створити API ключ.", "Failed to read clipboard contents": "Не вдалося прочитати вміст буфера обміну", @@ -220,15 +220,15 @@ "Focus chat input": "Фокус вводу чату", "Followed instructions perfectly": "Бездоганно дотримувався інструкцій", "Format your variables using square brackets like this:": "Форматуйте свої змінні квадратними дужками так:", - "Frequency Penalty": "", + "Frequency Penalty": "Штраф за частоту", "Full Screen Mode": "Режим повного екрану", "General": "Загальні", "General Settings": "Загальні налаштування", - "Generating search query": "", + "Generating search query": "Сформувати пошуковий запит", "Generation Info": "Інформація про генерацію", "Good Response": "Гарна відповідь", - "Google PSE API Key": "", - "Google PSE Engine Id": "", + "Google PSE API Key": "Ключ API Google PSE", + "Google PSE Engine Id": "Id двигуна Google PSE", "h:mm a": "h:mm a", "has no conversations.": "не має розмов.", "Hello, {{name}}": "Привіт, {{name}}", @@ -242,18 +242,18 @@ "Images": "Зображення", "Import Chats": "Імпортувати чати", "Import Documents Mapping": "Імпортувати відображення документів", - "Import Models": "", + "Import Models": "Імпорт моделей", "Import Prompts": "Імпортувати промти", "Include `--api` flag when running stable-diffusion-webui": "Включіть прапор `--api` при запуску stable-diffusion-webui", - "Info": "", + "Info": "Інфо", "Input commands": "Команди вводу", - "Install from Github URL": "", + "Install from Github URL": "Встановіть з URL-адреси Github", "Interface": "Інтерфейс", "Invalid Tag": "Недійсний тег", "January": "Січень", "join our Discord for help.": "приєднуйтеся до нашого Discord для допомоги.", "JSON": "JSON", - "JSON Preview": "", + "JSON Preview": "Перегляд JSON", "July": "Липень", "June": "Червень", "JWT Expiration": "Термін дії JWT", @@ -270,9 +270,9 @@ "Make sure to enclose them with": "Переконайтеся, що вони закриті", "Manage Models": "Керування моделями", "Manage Ollama Models": "Керування моделями Ollama", - "Manage Pipelines": "", + "Manage Pipelines": "Управління Pipelines", "March": "Березень", - "Max Tokens (num_predict)": "", + "Max Tokens (num_predict)": "Макс токенів (num_predict)", "Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Максимум 3 моделі можна завантажити одночасно. Будь ласка, спробуйте пізніше.", "May": "Травень", "Memories accessible by LLMs will be shown here.": "Пам'ять, яка доступна LLM, буде показана тут.", @@ -287,12 +287,12 @@ "Model '{{modelName}}' has been successfully downloaded.": "Модель '{{modelName}}' успішно завантажено.", "Model '{{modelTag}}' is already in queue for downloading.": "Модель '{{modelTag}}' вже знаходиться в черзі на завантаження.", "Model {{modelId}} not found": "Модель {{modelId}} не знайдено", - "Model {{modelName}} is not vision capable": "", - "Model {{name}} is now {{status}}": "", + "Model {{modelName}} is not vision capable": "Модель {{modelName}} не здатна бачити", + "Model {{name}} is now {{status}}": "Модель {{name}} тепер має {{status}}", "Model filesystem path detected. Model shortname is required for update, cannot continue.": "Виявлено шлях до файлової системи моделі. Для оновлення потрібно вказати коротке ім'я моделі, не вдасться продовжити.", - "Model ID": "", + "Model ID": "ID моделі", "Model not selected": "Модель не вибрана", - "Model Params": "", + "Model Params": "Параметри моделі", "Model Whitelisting": "Модель білого списку", "Model(s) Whitelisted": "Модель(і) білого списку", "Modelfile Content": "Вміст файлу моделі", @@ -300,13 +300,13 @@ "More": "Більше", "Name": "Ім'я", "Name Tag": "Назва тегу", - "Name your model": "", + "Name your model": "Назвіть свою модель", "New Chat": "Новий чат", "New Password": "Новий пароль", "No results found": "Не знайдено жодного результату", - "No search query generated": "", + "No search query generated": "Пошуковий запит не сформовано", "No source available": "Джерело не доступне", - "None": "", + "None": "Нема", "Not factually correct": "Не відповідає дійсності", "Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Примітка: Якщо ви встановите мінімальну кількість балів, пошук поверне лише документи з кількістю балів, більшою або рівною мінімальній кількості балів.", "Notifications": "Сповіщення", @@ -316,7 +316,7 @@ "Okay, Let's Go!": "Гаразд, давайте почнемо!", "OLED Dark": "Темний OLED", "Ollama": "Ollama", - "Ollama API": "", + "Ollama API": "Ollama API", "Ollama Version": "Версія Ollama", "On": "Увімк", "Only": "Тільки", @@ -341,8 +341,8 @@ "pending": "на розгляді", "Permission denied when accessing microphone: {{error}}": "Доступ до мікрофона заборонено: {{error}}", "Personalization": "Персоналізація", - "Pipelines": "", - "Pipelines Valves": "", + "Pipelines": "Pipelines", + "Pipelines Valves": "Pipelines Valves", "Plain text (.txt)": "Простий текст (.txt)", "Playground": "Майданчик", "Positive attitude": "Позитивне ставлення", @@ -387,34 +387,34 @@ "Scan for documents from {{path}}": "Сканування документів з {{path}}", "Search": "Пошук", "Search a model": "Шукати модель", - "Search Chats": "", + "Search Chats": "Пошук в чатах", "Search Documents": "Пошук документів", - "Search Models": "", + "Search Models": "Пошук моделей", "Search Prompts": "Пошук промтів", - "Search Result Count": "", - "Searched {{count}} sites_one": "", - "Searched {{count}} sites_few": "", - "Searched {{count}} sites_many": "", - "Searched {{count}} sites_other": "", - "Searching the web for '{{searchQuery}}'": "", - "Searxng Query URL": "", + "Search Result Count": "Кількість результатів пошуку", + "Searched {{count}} sites_one": "Переглянуто {{count}} сайт", + "Searched {{count}} sites_few": "Переглянуто {{count}} сайти", + "Searched {{count}} sites_many": "Переглянуто {{count}} сайтів", + "Searched {{count}} sites_other": "Переглянуто {{count}} сайтів", + "Searching the web for '{{searchQuery}}'": "Пошук в Інтернеті за запитом '{{searchQuery}}'", + "Searxng Query URL": "URL-адреса запиту Searxng", "See readme.md for instructions": "Див. readme.md для інструкцій", "See what's new": "Подивіться, що нового", "Seed": "Сід", - "Select a base model": "", + "Select a base model": "Вибрати базову модель", "Select a mode": "Оберіть режим", "Select a model": "Виберіть модель", - "Select a pipeline": "", - "Select a pipeline url": "", + "Select a pipeline": "Виберіть pipeline", + "Select a pipeline url": "Виберіть адресу pipeline", "Select an Ollama instance": "Виберіть екземпляр Ollama", "Select model": "Вибрати модель", - "Selected model(s) do not support image inputs": "", + "Selected model(s) do not support image inputs": "Вибрані модель(і) не підтримують вхідні зображення", "Send": "Надіслати", "Send a Message": "Надіслати повідомлення", "Send message": "Надіслати повідомлення", "September": "Вересень", - "Serper API Key": "", - "Serpstack API Key": "", + "Serper API Key": "Ключ API Serper", + "Serpstack API Key": "Ключ API Serpstack", "Server connection verified": "З'єднання з сервером підтверджено", "Set as default": "Встановити за замовчуванням", "Set Default Model": "Встановити модель за замовчуванням", @@ -423,7 +423,7 @@ "Set Model": "Встановити модель", "Set reranking model (e.g. {{model}})": "Встановити модель переранжування (напр., {{model}})", "Set Steps": "Встановити кроки", - "Set Task Model": "", + "Set Task Model": "Встановити модель задач", "Set Voice": "Встановити голос", "Settings": "Налаштування", "Settings saved successfully!": "Налаштування успішно збережено!", @@ -482,14 +482,14 @@ "Top P": "Top P", "Trouble accessing Ollama?": "Проблеми з доступом до Ollama?", "TTS Settings": "Налаштування TTS", - "Type": "", + "Type": "Тип", "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 and Copy Link": "Оновлення та копіювання посилання", "Update password": "Оновити пароль", "Upload a GGUF model": "Завантажити GGUF модель", - "Upload Files": "", + "Upload Files": "Завантажити файли", "Upload Progress": "Прогрес завантаження", "URL Mode": "Режим URL-адреси", "Use '#' in the prompt input to load and select your documents.": "Для введення промтів до веб-сторінок (URL) або вибору документів, будь ласка, використовуйте символ '#'.", @@ -503,13 +503,13 @@ "variable": "змінна", "variable to have them replaced with clipboard content.": "змінна, щоб замінити їх вмістом буфера обміну.", "Version": "Версія", - "Warning": "", + "Warning": "Увага!", "Warning: If you update or change your embedding model, you will need to re-import all documents.": "Попередження: Якщо ви оновлюєте або змінюєте модель вбудовування, вам потрібно буде повторно імпортувати всі документи.", "Web": "Веб", "Web Loader Settings": "Налаштування веб-завантажувача", "Web Params": "Налаштування веб-завантажувача", - "Web Search": "", - "Web Search Engine": "", + "Web Search": "Веб-пошук", + "Web Search Engine": "Веб-пошукова система", "Webhook URL": "URL веб-запиту", "WebUI Add-ons": "Додатки WebUI", "WebUI Settings": "Налаштування WebUI", @@ -522,7 +522,7 @@ "Write a summary in 50 words that summarizes [topic or keyword].": "Напишіть стислий зміст у 50 слів, який узагальнює [тема або ключове слово].", "Yesterday": "Вчора", "You": "Ви", - "You cannot clone a base model": "", + "You cannot clone a base model": "Базову модель не можна клонувати", "You have no archived conversations.": "У вас немає архівованих розмов.", "You have shared this chat": "Ви поділилися цим чатом", "You're a helpful assistant.": "Ви корисний асистент.", From 24c35c308d75140717cab82a15a06d3fac55ab89 Mon Sep 17 00:00:00 2001 From: Jun Siang Cheah Date: Sun, 2 Jun 2024 17:09:15 +0100 Subject: [PATCH 04/40] fix: stream defaults to true, return request ID --- backend/apps/ollama/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/apps/ollama/main.py b/backend/apps/ollama/main.py index 1c2bea6833..3ad3d88081 100644 --- a/backend/apps/ollama/main.py +++ b/backend/apps/ollama/main.py @@ -993,7 +993,7 @@ async def generate_chat_completion( def stream_content(): try: - if payload.get("stream", None): + if payload.get("stream", True): yield json.dumps({"id": request_id, "done": False}) + "\n" for chunk in r.iter_content(chunk_size=8192): @@ -1522,7 +1522,7 @@ async def deprecated_proxy( if path == "generate": data = json.loads(body.decode("utf-8")) - if not ("stream" in data and data["stream"] == False): + if data.get("stream", True): yield json.dumps({"id": request_id, "done": False}) + "\n" elif path == "chat": From 4dd51badfe2be1a0a85b817a1325227e92938949 Mon Sep 17 00:00:00 2001 From: Jun Siang Cheah Date: Sun, 2 Jun 2024 18:06:12 +0100 Subject: [PATCH 05/40] fix: ollama streaming cancellation using aiohttp --- backend/apps/ollama/main.py | 399 +++--------------- src/lib/apis/ollama/index.ts | 25 +- src/lib/components/chat/Chat.svelte | 109 +++-- .../chat/ModelSelector/Selector.svelte | 42 +- .../components/chat/Settings/Models.svelte | 55 +-- .../components/workspace/Playground.svelte | 14 +- 6 files changed, 154 insertions(+), 490 deletions(-) diff --git a/backend/apps/ollama/main.py b/backend/apps/ollama/main.py index 3ad3d88081..76709b0ee6 100644 --- a/backend/apps/ollama/main.py +++ b/backend/apps/ollama/main.py @@ -29,6 +29,8 @@ import time from urllib.parse import urlparse from typing import Optional, List, Union +from starlette.background import BackgroundTask + from apps.webui.models.models import Models from apps.webui.models.users import Users from constants import ERROR_MESSAGES @@ -75,9 +77,6 @@ app.state.config.OLLAMA_BASE_URLS = OLLAMA_BASE_URLS app.state.MODELS = {} -REQUEST_POOL = [] - - # TODO: Implement a more intelligent load balancing mechanism for distributing requests among multiple backend instances. # Current implementation uses a simple round-robin approach (random.choice). Consider incorporating algorithms like weighted round-robin, # least connections, or least response time for better resource utilization and performance optimization. @@ -132,16 +131,6 @@ async def update_ollama_api_url(form_data: UrlUpdateForm, user=Depends(get_admin return {"OLLAMA_BASE_URLS": app.state.config.OLLAMA_BASE_URLS} -@app.get("/cancel/{request_id}") -async def cancel_ollama_request(request_id: str, user=Depends(get_current_user)): - if user: - if request_id in REQUEST_POOL: - REQUEST_POOL.remove(request_id) - return True - else: - raise HTTPException(status_code=401, detail=ERROR_MESSAGES.ACCESS_PROHIBITED) - - async def fetch_url(url): timeout = aiohttp.ClientTimeout(total=5) try: @@ -154,6 +143,45 @@ async def fetch_url(url): return None +async def cleanup_response( + response: Optional[aiohttp.ClientResponse], + session: Optional[aiohttp.ClientSession], +): + if response: + response.close() + if session: + await session.close() + + +async def post_streaming_url(url, payload): + r = None + try: + session = aiohttp.ClientSession() + r = await session.post(url, data=payload) + r.raise_for_status() + + return StreamingResponse( + r.content, + status_code=r.status, + headers=dict(r.headers), + background=BackgroundTask(cleanup_response, response=r, session=session), + ) + except Exception as e: + error_detail = "Open WebUI: Server Connection Error" + if r is not None: + try: + res = await r.json() + if "error" in res: + error_detail = f"Ollama: {res['error']}" + except: + error_detail = f"Ollama: {e}" + + raise HTTPException( + status_code=r.status if r else 500, + detail=error_detail, + ) + + def merge_models_lists(model_lists): merged_models = {} @@ -313,65 +341,7 @@ async def pull_model( # Admin should be able to pull models from any source payload = {**form_data.model_dump(exclude_none=True), "insecure": True} - def get_request(): - nonlocal url - nonlocal r - - request_id = str(uuid.uuid4()) - try: - REQUEST_POOL.append(request_id) - - def stream_content(): - try: - yield json.dumps({"id": request_id, "done": False}) + "\n" - - for chunk in r.iter_content(chunk_size=8192): - if request_id in REQUEST_POOL: - yield chunk - else: - log.warning("User: canceled request") - break - finally: - if hasattr(r, "close"): - r.close() - if request_id in REQUEST_POOL: - REQUEST_POOL.remove(request_id) - - r = requests.request( - method="POST", - url=f"{url}/api/pull", - data=json.dumps(payload), - stream=True, - ) - - r.raise_for_status() - - return StreamingResponse( - stream_content(), - status_code=r.status_code, - headers=dict(r.headers), - ) - except Exception as e: - raise e - - try: - return await run_in_threadpool(get_request) - - except Exception as e: - log.exception(e) - error_detail = "Open WebUI: Server Connection Error" - if r is not None: - try: - res = r.json() - if "error" in res: - error_detail = f"Ollama: {res['error']}" - except: - error_detail = f"Ollama: {e}" - - raise HTTPException( - status_code=r.status_code if r else 500, - detail=error_detail, - ) + return await post_streaming_url(f"{url}/api/pull", json.dumps(payload)) class PushModelForm(BaseModel): @@ -399,50 +369,9 @@ async def push_model( url = app.state.config.OLLAMA_BASE_URLS[url_idx] log.debug(f"url: {url}") - r = None - - def get_request(): - nonlocal url - nonlocal r - try: - - def stream_content(): - for chunk in r.iter_content(chunk_size=8192): - yield chunk - - r = requests.request( - method="POST", - url=f"{url}/api/push", - data=form_data.model_dump_json(exclude_none=True).encode(), - ) - - r.raise_for_status() - - return StreamingResponse( - stream_content(), - status_code=r.status_code, - headers=dict(r.headers), - ) - except Exception as e: - raise e - - try: - return await run_in_threadpool(get_request) - except Exception as e: - log.exception(e) - error_detail = "Open WebUI: Server Connection Error" - if r is not None: - try: - res = r.json() - if "error" in res: - error_detail = f"Ollama: {res['error']}" - except: - error_detail = f"Ollama: {e}" - - raise HTTPException( - status_code=r.status_code if r else 500, - detail=error_detail, - ) + return await post_streaming_url( + f"{url}/api/push", form_data.model_dump_json(exclude_none=True).encode() + ) class CreateModelForm(BaseModel): @@ -461,53 +390,9 @@ async def create_model( url = app.state.config.OLLAMA_BASE_URLS[url_idx] log.info(f"url: {url}") - r = None - - def get_request(): - nonlocal url - nonlocal r - try: - - def stream_content(): - for chunk in r.iter_content(chunk_size=8192): - yield chunk - - r = requests.request( - method="POST", - url=f"{url}/api/create", - data=form_data.model_dump_json(exclude_none=True).encode(), - stream=True, - ) - - r.raise_for_status() - - log.debug(f"r: {r}") - - return StreamingResponse( - stream_content(), - status_code=r.status_code, - headers=dict(r.headers), - ) - except Exception as e: - raise e - - try: - return await run_in_threadpool(get_request) - except Exception as e: - log.exception(e) - error_detail = "Open WebUI: Server Connection Error" - if r is not None: - try: - res = r.json() - if "error" in res: - error_detail = f"Ollama: {res['error']}" - except: - error_detail = f"Ollama: {e}" - - raise HTTPException( - status_code=r.status_code if r else 500, - detail=error_detail, - ) + return await post_streaming_url( + f"{url}/api/create", form_data.model_dump_json(exclude_none=True).encode() + ) class CopyModelForm(BaseModel): @@ -797,66 +682,9 @@ async def generate_completion( url = app.state.config.OLLAMA_BASE_URLS[url_idx] log.info(f"url: {url}") - r = None - - def get_request(): - nonlocal form_data - nonlocal r - - request_id = str(uuid.uuid4()) - try: - REQUEST_POOL.append(request_id) - - def stream_content(): - try: - if form_data.stream: - yield json.dumps({"id": request_id, "done": False}) + "\n" - - for chunk in r.iter_content(chunk_size=8192): - if request_id in REQUEST_POOL: - yield chunk - else: - log.warning("User: canceled request") - break - finally: - if hasattr(r, "close"): - r.close() - if request_id in REQUEST_POOL: - REQUEST_POOL.remove(request_id) - - r = requests.request( - method="POST", - url=f"{url}/api/generate", - data=form_data.model_dump_json(exclude_none=True).encode(), - stream=True, - ) - - r.raise_for_status() - - return StreamingResponse( - stream_content(), - status_code=r.status_code, - headers=dict(r.headers), - ) - except Exception as e: - raise e - - try: - return await run_in_threadpool(get_request) - except Exception as e: - error_detail = "Open WebUI: Server Connection Error" - if r is not None: - try: - res = r.json() - if "error" in res: - error_detail = f"Ollama: {res['error']}" - except: - error_detail = f"Ollama: {e}" - - raise HTTPException( - status_code=r.status_code if r else 500, - detail=error_detail, - ) + return await post_streaming_url( + f"{url}/api/generate", form_data.model_dump_json(exclude_none=True).encode() + ) class ChatMessage(BaseModel): @@ -981,67 +809,7 @@ async def generate_chat_completion( print(payload) - r = None - - def get_request(): - nonlocal payload - nonlocal r - - request_id = str(uuid.uuid4()) - try: - REQUEST_POOL.append(request_id) - - def stream_content(): - try: - if payload.get("stream", True): - yield json.dumps({"id": request_id, "done": False}) + "\n" - - for chunk in r.iter_content(chunk_size=8192): - if request_id in REQUEST_POOL: - yield chunk - else: - log.warning("User: canceled request") - break - finally: - if hasattr(r, "close"): - r.close() - if request_id in REQUEST_POOL: - REQUEST_POOL.remove(request_id) - - r = requests.request( - method="POST", - url=f"{url}/api/chat", - data=json.dumps(payload), - stream=True, - ) - - r.raise_for_status() - - return StreamingResponse( - stream_content(), - status_code=r.status_code, - headers=dict(r.headers), - ) - except Exception as e: - log.exception(e) - raise e - - try: - return await run_in_threadpool(get_request) - except Exception as e: - error_detail = "Open WebUI: Server Connection Error" - if r is not None: - try: - res = r.json() - if "error" in res: - error_detail = f"Ollama: {res['error']}" - except: - error_detail = f"Ollama: {e}" - - raise HTTPException( - status_code=r.status_code if r else 500, - detail=error_detail, - ) + return await post_streaming_url(f"{url}/api/chat", json.dumps(payload)) # TODO: we should update this part once Ollama supports other types @@ -1132,68 +900,7 @@ async def generate_openai_chat_completion( url = app.state.config.OLLAMA_BASE_URLS[url_idx] log.info(f"url: {url}") - r = None - - def get_request(): - nonlocal payload - nonlocal r - - request_id = str(uuid.uuid4()) - try: - REQUEST_POOL.append(request_id) - - def stream_content(): - try: - if payload.get("stream"): - yield json.dumps( - {"request_id": request_id, "done": False} - ) + "\n" - - for chunk in r.iter_content(chunk_size=8192): - if request_id in REQUEST_POOL: - yield chunk - else: - log.warning("User: canceled request") - break - finally: - if hasattr(r, "close"): - r.close() - if request_id in REQUEST_POOL: - REQUEST_POOL.remove(request_id) - - r = requests.request( - method="POST", - url=f"{url}/v1/chat/completions", - data=json.dumps(payload), - stream=True, - ) - - r.raise_for_status() - - return StreamingResponse( - stream_content(), - status_code=r.status_code, - headers=dict(r.headers), - ) - except Exception as e: - raise e - - try: - return await run_in_threadpool(get_request) - except Exception as e: - error_detail = "Open WebUI: Server Connection Error" - if r is not None: - try: - res = r.json() - if "error" in res: - error_detail = f"Ollama: {res['error']}" - except: - error_detail = f"Ollama: {e}" - - raise HTTPException( - status_code=r.status_code if r else 500, - detail=error_detail, - ) + return await post_streaming_url(f"{url}/v1/chat/completions", json.dumps(payload)) @app.get("/v1/models") diff --git a/src/lib/apis/ollama/index.ts b/src/lib/apis/ollama/index.ts index 5ab2363cbb..aa1ac182bc 100644 --- a/src/lib/apis/ollama/index.ts +++ b/src/lib/apis/ollama/index.ts @@ -369,27 +369,6 @@ export const generateChatCompletion = async (token: string = '', body: object) = return [res, controller]; }; -export const cancelOllamaRequest = async (token: string = '', requestId: string) => { - let error = null; - - const res = await fetch(`${OLLAMA_API_BASE_URL}/cancel/${requestId}`, { - method: 'GET', - headers: { - 'Content-Type': 'text/event-stream', - Authorization: `Bearer ${token}` - } - }).catch((err) => { - error = err; - return null; - }); - - if (error) { - throw error; - } - - return res; -}; - export const createModel = async (token: string, tagName: string, content: string) => { let error = null; @@ -461,8 +440,10 @@ export const deleteModel = async (token: string, tagName: string, urlIdx: string export const pullModel = async (token: string, tagName: string, urlIdx: string | null = null) => { let error = null; + const controller = new AbortController(); const res = await fetch(`${OLLAMA_API_BASE_URL}/api/pull${urlIdx !== null ? `/${urlIdx}` : ''}`, { + signal: controller.signal, method: 'POST', headers: { Accept: 'application/json', @@ -485,7 +466,7 @@ export const pullModel = async (token: string, tagName: string, urlIdx: string | if (error) { throw error; } - return res; + return [res, controller]; }; export const downloadModel = async ( diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index 7e389f458a..d1bea2e897 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -26,7 +26,7 @@ splitStream } from '$lib/utils'; - import { cancelOllamaRequest, generateChatCompletion } from '$lib/apis/ollama'; + import { generateChatCompletion } from '$lib/apis/ollama'; import { addTagById, createNewChat, @@ -65,7 +65,6 @@ let autoScroll = true; let processing = ''; let messagesContainerElement: HTMLDivElement; - let currentRequestId = null; let showModelSelector = true; @@ -130,10 +129,6 @@ ////////////////////////// const initNewChat = async () => { - if (currentRequestId !== null) { - await cancelOllamaRequest(localStorage.token, currentRequestId); - currentRequestId = null; - } window.history.replaceState(history.state, '', `/`); await chatId.set(''); @@ -616,7 +611,6 @@ if (stopResponseFlag) { controller.abort('User: Stop Response'); - await cancelOllamaRequest(localStorage.token, currentRequestId); } else { const messages = createMessagesList(responseMessageId); const res = await chatCompleted(localStorage.token, { @@ -647,8 +641,6 @@ } } - currentRequestId = null; - break; } @@ -669,63 +661,58 @@ throw data; } - if ('id' in data) { - console.log(data); - currentRequestId = data.id; - } else { - if (data.done == false) { - if (responseMessage.content == '' && data.message.content == '\n') { - continue; - } else { - responseMessage.content += data.message.content; - messages = messages; - } + if (data.done == false) { + if (responseMessage.content == '' && data.message.content == '\n') { + continue; } else { - responseMessage.done = true; - - if (responseMessage.content == '') { - responseMessage.error = { - code: 400, - content: `Oops! No text generated from Ollama, Please try again.` - }; - } - - responseMessage.context = data.context ?? null; - responseMessage.info = { - total_duration: data.total_duration, - load_duration: data.load_duration, - sample_count: data.sample_count, - sample_duration: data.sample_duration, - prompt_eval_count: data.prompt_eval_count, - prompt_eval_duration: data.prompt_eval_duration, - eval_count: data.eval_count, - eval_duration: data.eval_duration - }; + responseMessage.content += data.message.content; messages = messages; + } + } else { + responseMessage.done = true; - if ($settings.notificationEnabled && !document.hasFocus()) { - const notification = new Notification( - selectedModelfile - ? `${ - selectedModelfile.title.charAt(0).toUpperCase() + - selectedModelfile.title.slice(1) - }` - : `${model}`, - { - body: responseMessage.content, - icon: selectedModelfile?.imageUrl ?? `${WEBUI_BASE_URL}/static/favicon.png` - } - ); - } + if (responseMessage.content == '') { + responseMessage.error = { + code: 400, + content: `Oops! No text generated from Ollama, Please try again.` + }; + } - if ($settings.responseAutoCopy) { - copyToClipboard(responseMessage.content); - } + responseMessage.context = data.context ?? null; + responseMessage.info = { + total_duration: data.total_duration, + load_duration: data.load_duration, + sample_count: data.sample_count, + sample_duration: data.sample_duration, + prompt_eval_count: data.prompt_eval_count, + prompt_eval_duration: data.prompt_eval_duration, + eval_count: data.eval_count, + eval_duration: data.eval_duration + }; + messages = messages; - if ($settings.responseAutoPlayback) { - await tick(); - document.getElementById(`speak-button-${responseMessage.id}`)?.click(); - } + if ($settings.notificationEnabled && !document.hasFocus()) { + const notification = new Notification( + selectedModelfile + ? `${ + selectedModelfile.title.charAt(0).toUpperCase() + + selectedModelfile.title.slice(1) + }` + : `${model}`, + { + body: responseMessage.content, + icon: selectedModelfile?.imageUrl ?? `${WEBUI_BASE_URL}/static/favicon.png` + } + ); + } + + if ($settings.responseAutoCopy) { + copyToClipboard(responseMessage.content); + } + + if ($settings.responseAutoPlayback) { + await tick(); + document.getElementById(`speak-button-${responseMessage.id}`)?.click(); } } } diff --git a/src/lib/components/chat/ModelSelector/Selector.svelte b/src/lib/components/chat/ModelSelector/Selector.svelte index 868c75da73..dad1fa512c 100644 --- a/src/lib/components/chat/ModelSelector/Selector.svelte +++ b/src/lib/components/chat/ModelSelector/Selector.svelte @@ -8,7 +8,7 @@ import Check from '$lib/components/icons/Check.svelte'; import Search from '$lib/components/icons/Search.svelte'; - import { cancelOllamaRequest, deleteModel, getOllamaVersion, pullModel } from '$lib/apis/ollama'; + import { deleteModel, getOllamaVersion, pullModel } from '$lib/apis/ollama'; import { user, MODEL_DOWNLOAD_POOL, models, mobile } from '$lib/stores'; import { toast } from 'svelte-sonner'; @@ -72,10 +72,12 @@ return; } - const res = await pullModel(localStorage.token, sanitizedModelTag, '0').catch((error) => { - toast.error(error); - return null; - }); + const [res, controller] = await pullModel(localStorage.token, sanitizedModelTag, '0').catch( + (error) => { + toast.error(error); + return null; + } + ); if (res) { const reader = res.body @@ -83,6 +85,16 @@ .pipeThrough(splitStream('\n')) .getReader(); + MODEL_DOWNLOAD_POOL.set({ + ...$MODEL_DOWNLOAD_POOL, + [sanitizedModelTag]: { + ...$MODEL_DOWNLOAD_POOL[sanitizedModelTag], + abortController: controller, + reader, + done: false + } + }); + while (true) { try { const { value, done } = await reader.read(); @@ -101,19 +113,6 @@ throw data.detail; } - if (data.id) { - MODEL_DOWNLOAD_POOL.set({ - ...$MODEL_DOWNLOAD_POOL, - [sanitizedModelTag]: { - ...$MODEL_DOWNLOAD_POOL[sanitizedModelTag], - requestId: data.id, - reader, - done: false - } - }); - console.log(data); - } - if (data.status) { if (data.digest) { let downloadProgress = 0; @@ -181,11 +180,12 @@ }); const cancelModelPullHandler = async (model: string) => { - const { reader, requestId } = $MODEL_DOWNLOAD_POOL[model]; + const { reader, abortController } = $MODEL_DOWNLOAD_POOL[model]; + if (abortController) { + abortController.abort(); + } if (reader) { await reader.cancel(); - - await cancelOllamaRequest(localStorage.token, requestId); delete $MODEL_DOWNLOAD_POOL[model]; MODEL_DOWNLOAD_POOL.set({ ...$MODEL_DOWNLOAD_POOL diff --git a/src/lib/components/chat/Settings/Models.svelte b/src/lib/components/chat/Settings/Models.svelte index 7254f2d270..eb4b7f7b55 100644 --- a/src/lib/components/chat/Settings/Models.svelte +++ b/src/lib/components/chat/Settings/Models.svelte @@ -8,7 +8,6 @@ getOllamaUrls, getOllamaVersion, pullModel, - cancelOllamaRequest, uploadModel } from '$lib/apis/ollama'; @@ -67,12 +66,14 @@ console.log(model); updateModelId = model.id; - const res = await pullModel(localStorage.token, model.id, selectedOllamaUrlIdx).catch( - (error) => { - toast.error(error); - return null; - } - ); + const [res, controller] = await pullModel( + localStorage.token, + model.id, + selectedOllamaUrlIdx + ).catch((error) => { + toast.error(error); + return null; + }); if (res) { const reader = res.body @@ -141,10 +142,12 @@ return; } - const res = await pullModel(localStorage.token, sanitizedModelTag, '0').catch((error) => { - toast.error(error); - return null; - }); + const [res, controller] = await pullModel(localStorage.token, sanitizedModelTag, '0').catch( + (error) => { + toast.error(error); + return null; + } + ); if (res) { const reader = res.body @@ -152,6 +155,16 @@ .pipeThrough(splitStream('\n')) .getReader(); + MODEL_DOWNLOAD_POOL.set({ + ...$MODEL_DOWNLOAD_POOL, + [sanitizedModelTag]: { + ...$MODEL_DOWNLOAD_POOL[sanitizedModelTag], + abortController: controller, + reader, + done: false + } + }); + while (true) { try { const { value, done } = await reader.read(); @@ -170,19 +183,6 @@ throw data.detail; } - if (data.id) { - MODEL_DOWNLOAD_POOL.set({ - ...$MODEL_DOWNLOAD_POOL, - [sanitizedModelTag]: { - ...$MODEL_DOWNLOAD_POOL[sanitizedModelTag], - requestId: data.id, - reader, - done: false - } - }); - console.log(data); - } - if (data.status) { if (data.digest) { let downloadProgress = 0; @@ -416,11 +416,12 @@ }; const cancelModelPullHandler = async (model: string) => { - const { reader, requestId } = $MODEL_DOWNLOAD_POOL[model]; + const { reader, abortController } = $MODEL_DOWNLOAD_POOL[model]; + if (abortController) { + abortController.abort(); + } if (reader) { await reader.cancel(); - - await cancelOllamaRequest(localStorage.token, requestId); delete $MODEL_DOWNLOAD_POOL[model]; MODEL_DOWNLOAD_POOL.set({ ...$MODEL_DOWNLOAD_POOL diff --git a/src/lib/components/workspace/Playground.svelte b/src/lib/components/workspace/Playground.svelte index 476ce774dc..b7453e3f35 100644 --- a/src/lib/components/workspace/Playground.svelte +++ b/src/lib/components/workspace/Playground.svelte @@ -8,7 +8,7 @@ import { 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 { cancelOllamaRequest, generateChatCompletion } from '$lib/apis/ollama'; + import { generateChatCompletion } from '$lib/apis/ollama'; import { generateOpenAIChatCompletion } from '$lib/apis/openai'; import { splitStream } from '$lib/utils'; @@ -24,7 +24,6 @@ let selectedModelId = ''; let loading = false; - let currentRequestId = null; let stopResponseFlag = false; let messagesContainerElement: HTMLDivElement; @@ -46,14 +45,6 @@ } }; - // const cancelHandler = async () => { - // if (currentRequestId) { - // const res = await cancelOllamaRequest(localStorage.token, currentRequestId); - // currentRequestId = null; - // loading = false; - // } - // }; - const stopResponse = () => { stopResponseFlag = true; console.log('stopResponse'); @@ -171,8 +162,6 @@ if (stopResponseFlag) { controller.abort('User: Stop Response'); } - - currentRequestId = null; break; } @@ -229,7 +218,6 @@ loading = false; stopResponseFlag = false; - currentRequestId = null; } }; From 7f74426a228d592cf4822eff94220547e0142e04 Mon Sep 17 00:00:00 2001 From: Jun Siang Cheah Date: Sun, 2 Jun 2024 18:48:45 +0100 Subject: [PATCH 06/40] fix: openai streaming cancellation using aiohttp --- backend/apps/ollama/main.py | 2 +- backend/apps/openai/main.py | 44 +++++++++++++++++++++++++------------ 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/backend/apps/ollama/main.py b/backend/apps/ollama/main.py index 76709b0ee6..2c84f602e8 100644 --- a/backend/apps/ollama/main.py +++ b/backend/apps/ollama/main.py @@ -153,7 +153,7 @@ async def cleanup_response( await session.close() -async def post_streaming_url(url, payload): +async def post_streaming_url(url: str, payload: str): r = None try: session = aiohttp.ClientSession() diff --git a/backend/apps/openai/main.py b/backend/apps/openai/main.py index 6a83476281..ea623345ff 100644 --- a/backend/apps/openai/main.py +++ b/backend/apps/openai/main.py @@ -9,6 +9,7 @@ import json import logging from pydantic import BaseModel +from starlette.background import BackgroundTask from apps.webui.models.models import Models from apps.webui.models.users import Users @@ -194,6 +195,16 @@ async def fetch_url(url, key): return None +async def cleanup_response( + response: Optional[aiohttp.ClientResponse], + session: Optional[aiohttp.ClientSession], +): + if response: + response.close() + if session: + await session.close() + + def merge_models_lists(model_lists): log.debug(f"merge_models_lists {model_lists}") merged_list = [] @@ -426,40 +437,45 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)): headers["Content-Type"] = "application/json" r = None + session = None + streaming = False try: - r = requests.request( - method=request.method, - url=target_url, - data=payload if payload else body, - headers=headers, - stream=True, + session = aiohttp.ClientSession() + r = await session.request( + method=request.method, url=target_url, data=payload, headers=headers ) r.raise_for_status() # Check if response is SSE if "text/event-stream" in r.headers.get("Content-Type", ""): + streaming = True return StreamingResponse( - r.iter_content(chunk_size=8192), - status_code=r.status_code, + r.content, + status_code=r.status, headers=dict(r.headers), + background=BackgroundTask( + cleanup_response, response=r, session=session + ), ) else: - response_data = r.json() + response_data = await r.json() return response_data except Exception as e: log.exception(e) error_detail = "Open WebUI: Server Connection Error" if r is not None: try: - res = r.json() + res = await r.json() print(res) if "error" in res: error_detail = f"External: {res['error']['message'] if 'message' in res['error'] else res['error']}" except: error_detail = f"External: {e}" - - raise HTTPException( - status_code=r.status_code if r else 500, detail=error_detail - ) + raise HTTPException(status_code=r.status if r else 500, detail=error_detail) + finally: + if not streaming and session: + if r: + r.close() + await session.close() From b5b2b70f4a4bc8e608f16e6c9febddaa2e110d84 Mon Sep 17 00:00:00 2001 From: Jun Siang Cheah Date: Sun, 2 Jun 2024 19:40:18 +0100 Subject: [PATCH 07/40] fix: bad payload refactor --- backend/apps/openai/main.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/apps/openai/main.py b/backend/apps/openai/main.py index ea623345ff..1407a52d03 100644 --- a/backend/apps/openai/main.py +++ b/backend/apps/openai/main.py @@ -443,7 +443,10 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)): try: session = aiohttp.ClientSession() r = await session.request( - method=request.method, url=target_url, data=payload, headers=headers + method=request.method, + url=target_url, + data=payload if payload else body, + headers=headers, ) r.raise_for_status() From e0ba585204fdd3ea26fefc64a751191501703629 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sun, 2 Jun 2024 13:20:38 -0700 Subject: [PATCH 08/40] feat: include num_thread in advanced params --- backend/apps/ollama/main.py | 97 +++++++++++++------ .../Settings/Advanced/AdvancedParams.svelte | 91 +++++++++++++++++ 2 files changed, 156 insertions(+), 32 deletions(-) diff --git a/backend/apps/ollama/main.py b/backend/apps/ollama/main.py index 1c2bea6833..84d581504a 100644 --- a/backend/apps/ollama/main.py +++ b/backend/apps/ollama/main.py @@ -906,44 +906,77 @@ async def generate_chat_completion( if model_info.params: payload["options"] = {} - payload["options"]["mirostat"] = model_info.params.get("mirostat", None) - payload["options"]["mirostat_eta"] = model_info.params.get( - "mirostat_eta", None - ) - payload["options"]["mirostat_tau"] = model_info.params.get( - "mirostat_tau", None - ) - payload["options"]["num_ctx"] = model_info.params.get("num_ctx", None) + if model_info.params.get("mirostat", None): + payload["options"]["mirostat"] = model_info.params.get("mirostat", None) - payload["options"]["repeat_last_n"] = model_info.params.get( - "repeat_last_n", None - ) - payload["options"]["repeat_penalty"] = model_info.params.get( - "frequency_penalty", None - ) + if model_info.params.get("mirostat_eta", None): + payload["options"]["mirostat_eta"] = model_info.params.get( + "mirostat_eta", None + ) - payload["options"]["temperature"] = model_info.params.get( - "temperature", None - ) - payload["options"]["seed"] = model_info.params.get("seed", None) + if model_info.params.get("mirostat_tau", None): - payload["options"]["stop"] = ( - [ - bytes(stop, "utf-8").decode("unicode_escape") - for stop in model_info.params["stop"] - ] - if model_info.params.get("stop", None) - else None - ) + payload["options"]["mirostat_tau"] = model_info.params.get( + "mirostat_tau", None + ) - payload["options"]["tfs_z"] = model_info.params.get("tfs_z", None) + if model_info.params.get("num_ctx", None): + payload["options"]["num_ctx"] = model_info.params.get("num_ctx", None) - payload["options"]["num_predict"] = model_info.params.get( - "max_tokens", None - ) - payload["options"]["top_k"] = model_info.params.get("top_k", None) + if model_info.params.get("repeat_last_n", None): + payload["options"]["repeat_last_n"] = model_info.params.get( + "repeat_last_n", None + ) - payload["options"]["top_p"] = model_info.params.get("top_p", None) + if model_info.params.get("frequency_penalty", None): + payload["options"]["repeat_penalty"] = model_info.params.get( + "frequency_penalty", None + ) + + if model_info.params.get("temperature", None): + payload["options"]["temperature"] = model_info.params.get( + "temperature", None + ) + + if model_info.params.get("seed", None): + payload["options"]["seed"] = model_info.params.get("seed", None) + + if model_info.params.get("stop", None): + payload["options"]["stop"] = ( + [ + bytes(stop, "utf-8").decode("unicode_escape") + for stop in model_info.params["stop"] + ] + if model_info.params.get("stop", None) + else None + ) + + if model_info.params.get("tfs_z", None): + payload["options"]["tfs_z"] = model_info.params.get("tfs_z", None) + + if model_info.params.get("max_tokens", None): + payload["options"]["num_predict"] = model_info.params.get( + "max_tokens", None + ) + + if model_info.params.get("top_k", None): + payload["options"]["top_k"] = model_info.params.get("top_k", None) + + if model_info.params.get("top_p", None): + payload["options"]["top_p"] = model_info.params.get("top_p", None) + + if model_info.params.get("use_mmap", None): + payload["options"]["use_mmap"] = model_info.params.get("use_mmap", None) + + if model_info.params.get("use_mlock", None): + payload["options"]["use_mlock"] = model_info.params.get( + "use_mlock", None + ) + + if model_info.params.get("num_thread", None): + payload["options"]["num_thread"] = model_info.params.get( + "num_thread", None + ) if model_info.params.get("system", None): # Check if the payload already has a system message diff --git a/src/lib/components/chat/Settings/Advanced/AdvancedParams.svelte b/src/lib/components/chat/Settings/Advanced/AdvancedParams.svelte index 55648df54a..0a74d1d10e 100644 --- a/src/lib/components/chat/Settings/Advanced/AdvancedParams.svelte +++ b/src/lib/components/chat/Settings/Advanced/AdvancedParams.svelte @@ -20,6 +20,9 @@ tfs_z: '', num_ctx: '', max_tokens: '', + use_mmap: null, + use_mlock: null, + num_thread: null, template: null }; @@ -559,6 +562,7 @@ {/if} +
{$i18n.t('Max Tokens (num_predict)')}
@@ -604,6 +608,93 @@
{/if}
+ +
+
+
{$i18n.t('use_mmap (Ollama)')}
+ + +
+
+ +
+
+
{$i18n.t('use_mlock (Ollama)')}
+ + +
+
+ +
+
+
{$i18n.t('num_thread (Ollama)')}
+ + +
+ + {#if (params?.num_thread ?? null) !== null} +
+
+ +
+
+ +
+
+ {/if} +
+
{$i18n.t('Template')}
From 41442897e3d1be4f641fe8a7b307d1d1a17abba1 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sun, 2 Jun 2024 13:45:04 -0700 Subject: [PATCH 09/40] fix: connections --- .../chat/Settings/Connections.svelte | 49 +++++++++++++++---- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/src/lib/components/chat/Settings/Connections.svelte b/src/lib/components/chat/Settings/Connections.svelte index 0d5f7e50a6..80fdcf45fe 100644 --- a/src/lib/components/chat/Settings/Connections.svelte +++ b/src/lib/components/chat/Settings/Connections.svelte @@ -1,6 +1,6 @@
- {#if ollamaVersion !== null} -
-
{$i18n.t('Manage Ollama Models')}
+ {#if ollamaEnabled} + {#if ollamaVersion !== null} +
+
{$i18n.t('Manage Ollama Models')}
- {#if OLLAMA_URLS.length > 0} -
-
- -
- -
-
- - - -
-
-
- - {#if updateModelId} - Updating "{updateModelId}" {updateProgress ? `(${updateProgress}%)` : ''} - {/if} - {/if} - -
-
-
{$i18n.t('Pull a model from Ollama.com')}
-
-
- -
- -
- -
- {$i18n.t('To access the available model names for downloading,')} - {$i18n.t('click here.')} -
- - {#if Object.keys($MODEL_DOWNLOAD_POOL).length > 0} - {#each Object.keys($MODEL_DOWNLOAD_POOL) as model} - {#if 'pullProgress' in $MODEL_DOWNLOAD_POOL[model]} -
-
{model}
-
-
-
-
- {$MODEL_DOWNLOAD_POOL[model].pullProgress ?? 0}% -
-
- - - - -
- {#if 'digest' in $MODEL_DOWNLOAD_POOL[model]} -
- {$MODEL_DOWNLOAD_POOL[model].digest} -
- {/if} -
-
- {/if} - {/each} - {/if} -
- -
-
{$i18n.t('Delete a model')}
-
-
+ {#if OLLAMA_URLS.length > 0} +
+
- -
-
-
-
-
{$i18n.t('Experimental')}
- -
-
- - {#if showExperimentalOllama} -
{ - uploadModelHandler(); - }} - > -
-
{$i18n.t('Upload a GGUF model')}
- - -
- -
-
- {#if modelUploadMode === 'file'} -
- { - console.log(modelInputFile); - }} - accept=".gguf,.safetensors" - required - hidden - /> - - -
- {:else} -
- -
- {/if} -
- - {#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')} - - {/if} + + +
+
+
+ + {#if updateModelId} + Updating "{updateModelId}" {updateProgress ? `(${updateProgress}%)` : ''} + {/if} + {/if} + +
+
+
{$i18n.t('Pull a model from Ollama.com')}
+
+
+ +
+
- {#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')} -
-
-
{$i18n.t('Modelfile Content')}
-