From 4c8311b60618cbf24799f47adf99ffacab972dfd Mon Sep 17 00:00:00 2001 From: Ruben Talstra Date: Thu, 13 Feb 2025 14:20:11 +0100 Subject: [PATCH 01/23] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20chore:=20patch=20?= =?UTF-8?q?`elliptic`=20to=20address=20GHSA-vjh7-7g9h-fjfh=20(#5848)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 20 +++++++++++++------- package.json | 4 +++- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7ec6e32237..77a282d807 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "@playwright/test": "^1.50.1", "@types/react-virtualized": "^9.22.0", "cross-env": "^7.0.3", + "elliptic": "^6.6.1", "eslint": "^9.20.1", "eslint-config-prettier": "^10.0.1", "eslint-import-resolver-typescript": "^3.7.0", @@ -20147,9 +20148,10 @@ "integrity": "sha512-VufdJl+rzaKZoYVUijN13QcXVF5dWPZANeFTLNy+OSpHdDL5ynXTF35+60RSBbaQYB1ae723lQXHCrf4pyLsMw==" }, "node_modules/elliptic": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.0.tgz", - "integrity": "sha512-dpwoQcLc/2WLQvJvLRHKZ+f9FgOdjnq11rurqwekGQygGPsYSK29OMMD2WalatiqQ+XGFDglTNixpPfI+lpaAA==", + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", + "license": "MIT", "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -20161,9 +20163,10 @@ } }, "node_modules/elliptic/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", + "license": "MIT" }, "node_modules/emittery": { "version": "0.13.1", @@ -22840,6 +22843,7 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "license": "MIT", "dependencies": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" @@ -23083,6 +23087,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "license": "MIT", "dependencies": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", @@ -28063,7 +28068,8 @@ "node_modules/minimalistic-crypto-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "license": "MIT" }, "node_modules/minimatch": { "version": "3.1.2", diff --git a/package.json b/package.json index c0b55a4363..99f9fd908e 100644 --- a/package.json +++ b/package.json @@ -107,9 +107,11 @@ "prettier": "^3.5.0", "prettier-eslint": "^16.3.0", "prettier-plugin-tailwindcss": "^0.6.11", - "typescript-eslint": "^8.24.0" + "typescript-eslint": "^8.24.0", + "elliptic": "^6.6.1" }, "overrides": { + "elliptic": "^6.6.1", "mdast-util-gfm-autolink-literal": "2.0.0", "remark-gfm": { "mdast-util-gfm-autolink-literal": "2.0.0" From e402979cc5e81749594a029ee3e238029241a7c6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 13 Feb 2025 09:34:14 -0500 Subject: [PATCH 02/23] =?UTF-8?q?=F0=9F=8C=8D=20i18n:=20Update=20translati?= =?UTF-8?q?on.json=20with=20latest=20translations=20(#5849)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- client/src/locales/de/translation.json | 50 +- client/src/locales/et/translation.json | 788 +++++++++++++++++++++++++ client/src/locales/zh/translation.json | 44 +- 3 files changed, 878 insertions(+), 4 deletions(-) create mode 100644 client/src/locales/et/translation.json diff --git a/client/src/locales/de/translation.json b/client/src/locales/de/translation.json index c0cb405620..ac5bc4fc27 100644 --- a/client/src/locales/de/translation.json +++ b/client/src/locales/de/translation.json @@ -18,13 +18,16 @@ "com_agents_not_available": "Agent nicht verfügbar", "com_agents_search_name": "Agenten nach Namen suchen", "com_agents_update_error": "Beim Aktualisieren deines Agenten ist ein Fehler aufgetreten.", + "com_assistants_action_attempt": "Assistent möchte kommunizieren mit {{0}}", "com_assistants_actions": "Aktionen", "com_assistants_actions_disabled": "Du müsst einen Assistenten erstellen, bevor du Aktionen hinzufügen kannst.", "com_assistants_actions_info": "Lasse deinen Assistenten Informationen abrufen oder Aktionen über APIs ausführen", "com_assistants_add_actions": "Aktionen hinzufügen", "com_assistants_add_tools": "Werkzeuge hinzufügen", + "com_assistants_allow_sites_you_trust": "Erlaube nur Webseiten, denen du vertraust.", "com_assistants_append_date": "Aktuelles Datum & Uhrzeit anhängen", "com_assistants_append_date_tooltip": "Wenn aktiviert, werden das aktuelle Client-Datum und die Uhrzeit an die Systemanweisungen des Assistenten angehängt.", + "com_assistants_attempt_info": "Assistent möchte Folgendes senden:", "com_assistants_available_actions": "Verfügbare Aktionen", "com_assistants_capabilities": "Fähigkeiten", "com_assistants_code_interpreter": "Code-Interpreter", @@ -59,6 +62,7 @@ "com_assistants_update_error": "Bei der Aktualisierung deines Assistenten ist ein Fehler aufgetreten.", "com_assistants_update_success": "Erfolgreich aktualisiert", "com_auth_already_have_account": "Hast du bereits ein Konto?", + "com_auth_apple_login": "Mit Apple anmelden", "com_auth_back_to_login": "Zurück zur Anmeldung", "com_auth_click": "Klicke", "com_auth_click_here": "Klicke hier", @@ -195,6 +199,7 @@ "com_endpoint_openai_max_tokens": "Optionales 'max_tokens'-Feld, das die maximale Anzahl von Token darstellt, die in der Chat-Vervollständigung generiert werden können. Die Gesamtlänge der Eingabe-Token und der generierten Token ist durch die Kontextlänge des Modells begrenzt. Du kannst Fehler erleben, wenn diese Zahl die maximalen Kontext-Token überschreitet.", "com_endpoint_openai_pres": "Zahl zwischen -2,0 und 2,0. Positive Werte bestrafen neue Token basierend darauf, ob sie im bisherigen Text vorkommen, wodurch die Wahrscheinlichkeit des Modells erhöht wird, über neue Themen zu sprechen.", "com_endpoint_openai_prompt_prefix_placeholder": "Lege benutzerdefinierte Anweisungen fest, die in die Systemnachricht an die KI aufgenommen werden sollen. Standard: keine", + "com_endpoint_openai_reasoning_effort": "Nur für o1-Modelle: Begrenzt den Aufwand des Nachdenkens bei Schlussfolgerungsmodellen. Die Reduzierung des Nachdenkeaufwands kann zu schnelleren Antworten und weniger Token führen, die für das Überlegen vor einer Antwort verwendet werden.", "com_endpoint_openai_resend": "Alle im Chat zuvor angehängten Bilder mit jeder neuen Nachricht erneut senden. Hinweis: Dies kann die Kosten der Anfrage aufgrund höherer Token-Anzahl erheblich erhöhen und du kannst bei vielen Bildanhängen Fehler erleben.", "com_endpoint_openai_resend_files": "Alle im Chat zuvor angehängten Dateien mit jeder neuen Nachricht erneut senden. Hinweis: Dies wird die Kosten der Anfrage aufgrund höherer Token-Anzahl erheblich erhöhen und du kannst bei vielen Anhängen Fehler erleben.", "com_endpoint_openai_stop": "Bis zu 4 Sequenzen, bei denen die API keine weiteren Token generiert.", @@ -228,6 +233,7 @@ "com_endpoint_prompt_prefix_assistants": "Zusätzliche Anweisungen", "com_endpoint_prompt_prefix_assistants_placeholder": "Lege zusätzliche Anweisungen oder Kontext zusätzlich zu den Hauptanweisungen des Assistenten fest. Wird ignoriert, wenn leer.", "com_endpoint_prompt_prefix_placeholder": "Lege benutzerdefinierte Anweisungen oder Kontext fest. Wird ignoriert, wenn leer.", + "com_endpoint_reasoning_effort": "Denkaufwand", "com_endpoint_save_as_preset": "Voreinstellung speichern", "com_endpoint_search": "Endpunkt nach Namen suchen", "com_endpoint_set_custom_name": "Lege einen benutzerdefinierten Namen fest, falls du diese Voreinstellung wiederfinden möchtest", @@ -329,6 +335,7 @@ "com_nav_info_include_shadcnui": "Wenn aktiviert, werden Anweisungen zur Verwendung von shadcn/ui-Komponenten eingeschlossen. shadcn/ui ist eine Sammlung wiederverwendbarer Komponenten, die mit Radix UI und Tailwind CSS erstellt wurden. Hinweis: Dies sind umfangreiche Anweisungen, die Sie nur aktivieren sollten, wenn es Ihnen wichtig ist, das KI-Modell über die korrekten Importe und Komponenten zu informieren. Weitere Informationen zu diesen Komponenten finden Sie unter: https://ui.shadcn.com/", "com_nav_info_latex_parsing": "Wenn aktiviert, wird LaTeX-Code in Nachrichten als mathematische Gleichungen gerendert. Das Deaktivieren kann die Leistung verbessern, wenn du keine LaTeX-Darstellung benötigst.", "com_nav_info_save_draft": "Wenn aktiviert, werden der Text und die Anhänge, die du in das Chat-Formular eingibst, automatisch lokal als Entwürfe gespeichert. Diese Entwürfe sind auch verfügbar, wenn du die Seite neu lädst oder zu einer anderen Konversation wechseln. Entwürfe werden lokal auf deinem Gerät gespeichert und werden gelöscht, sobald die Nachricht gesendet wird.", + "com_nav_info_show_thinking": "Wenn aktiviert, sind die Denkprozess-Dropdowns standardmäßig geöffnet, sodass du die Gedankengänge der KI in Echtzeit sehen kannst. Wenn deaktiviert, bleiben sie standardmäßig geschlossen, für eine übersichtlichere Oberfläche.", "com_nav_info_user_name_display": "Wenn aktiviert, wird der Benutzername des Absenders über jeder Nachricht angezeigt, die du sendest. Wenn deaktiviert, siehst du nur \"Du\" über deinen Nachrichten.", "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "Automatisch erkennen", @@ -371,6 +378,7 @@ "com_nav_plus_command_description": "Schaltet den Befehl \"+\" zum Hinzufügen einer Mehrfachantwort-Einstellung um", "com_nav_profile_picture": "Profilbild", "com_nav_save_drafts": "Entwürfe lokal speichern", + "com_nav_scroll_button": "Zum Ende scrollen Button", "com_nav_search_placeholder": "Nachrichten durchsuchen", "com_nav_send_message": "Nachricht senden", "com_nav_setting_account": "Konto", @@ -383,6 +391,7 @@ "com_nav_shared_links": "Geteilte Links", "com_nav_shared_links_manage": "Verwalten", "com_nav_show_code": "Code immer anzeigen, wenn der Code-Interpreter verwendet wird", + "com_nav_show_thinking": "Denkprozess-Dropdowns standardmäßig öffnen", "com_nav_slash_command": "/-Befehl", "com_nav_slash_command_description": "Schaltet den Befehl \"/\" zur Auswahl einer Eingabeaufforderung über die Tastatur um", "com_nav_source_buffer_error": "Fehler beim Einrichten der Audiowiedergabe. Bitte lade die Seite neu.", @@ -418,7 +427,7 @@ "com_sidepanel_conversation_tags": "Lesezeichen", "com_sidepanel_hide_panel": "Seitenleiste ausblenden", "com_sidepanel_manage_files": "Dateien verwalten", - "com_sidepanel_parameters": "Parameter", + "com_sidepanel_parameters": "KI-Einstellungen", "com_sidepanel_select_agent": "Wähle einen Agenten", "com_sidepanel_select_assistant": "Assistenten auswählen", "com_ui_accept": "Ich akzeptiere", @@ -435,12 +444,14 @@ "com_ui_agent_duplicate_error": "Beim Duplizieren des Assistenten ist ein Fehler aufgetreten", "com_ui_agent_duplicated": "Agent wurde erfolgreich dupliziert", "com_ui_agent_editing_allowed": "Andere Nutzende können diesen Agenten bereits bearbeiten", + "com_ui_agent_shared_to_all": "Hier muss etwas eingegeben werden. War leer.", "com_ui_agents": "Agenten", "com_ui_agents_allow_create": "Erstellung von Assistenten erlauben", "com_ui_agents_allow_share_global": "Assistenten für alle Nutzenden freigeben", "com_ui_agents_allow_use": "Verwendung von Agenten erlauben", "com_ui_all": "alle", "com_ui_all_proper": "Alle", + "com_ui_api_key": "API-Schlüssel", "com_ui_archive": "Archivieren", "com_ui_archive_error": "Konversation konnte nicht archiviert werden", "com_ui_artifact_click": "Zum Öffnen klicken", @@ -458,10 +469,16 @@ "com_ui_attach_error_type": "Nicht unterstützter Dateityp für Endpunkt:", "com_ui_attach_warn_endpoint": "Nicht-Assistentendateien werden möglicherweise ohne kompatibles Werkzeug ignoriert", "com_ui_attachment": "Anhang", + "com_ui_auth_type": "Authentifizierungstyp", + "com_ui_auth_url": "Autorisierungs-URL", "com_ui_authentication": "Authentifizierung", + "com_ui_authentication_type": "Authentifizierungstyp", "com_ui_avatar": "Avatar", "com_ui_back_to_chat": "Zurück zum Chat", "com_ui_back_to_prompts": "Zurück zu den Prompts", + "com_ui_basic": "Basic", + "com_ui_basic_auth_header": "Basic-Authentifizierungsheader", + "com_ui_bearer": "Bearer", "com_ui_bookmark_delete_confirm": "Bist du sicher, dass du dieses Lesezeichen löschen möchtest?", "com_ui_bookmarks": "Lesezeichen", "com_ui_bookmarks_add": "Lesezeichen hinzufügen", @@ -480,17 +497,23 @@ "com_ui_bookmarks_title": "Titel", "com_ui_bookmarks_update_error": "Beim Aktualisieren des Lesezeichens ist ein Fehler aufgetreten", "com_ui_bookmarks_update_success": "Lesezeichen erfolgreich aktualisiert", + "com_ui_bulk_delete_error": "Geteilte Links konnten nicht gelöscht werden.", + "com_ui_callback_url": "Callback-URL", "com_ui_cancel": "Abbrechen", "com_ui_chat": "Chat", "com_ui_chat_history": "Chatverlauf", "com_ui_clear": "Löschen", "com_ui_clear_all": "Auswahl löschen", + "com_ui_client_secret": "Client Secret", "com_ui_close": "Schließen", + "com_ui_close_menu": "Menü schließen", "com_ui_code": "Code", "com_ui_collapse_chat": "Chat einklappen", "com_ui_command_placeholder": "Optional: Gib einen Promptbefehl ein oder den Namen.", "com_ui_command_usage_placeholder": "Wähle einen Prompt nach Befehl oder Name aus", "com_ui_confirm_action": "Aktion bestätigen", + "com_ui_confirm_admin_use_change": "Wenn du diese Einstellung änderst, wird der Zugriff für Administratoren, einschließlich dir selbst, gesperrt. Bist du sicher, dass du fortfahren möchtest?", + "com_ui_confirm_change": "Änderung bestätigen", "com_ui_context": "Kontext", "com_ui_continue": "Fortfahren", "com_ui_controls": "Steuerung", @@ -502,6 +525,8 @@ "com_ui_create": "Erstellen", "com_ui_create_link": "Link erstellen", "com_ui_create_prompt": "Prompt erstellen", + "com_ui_currently_production": "Aktuell im Produktivbetrieb", + "com_ui_custom": "Benutzerdefiniert", "com_ui_custom_prompt_mode": "Benutzerdefinierter Promptmodus für Artefakte", "com_ui_dashboard": "Dashboard", "com_ui_date": "Datum", @@ -537,6 +562,8 @@ "com_ui_descending": "Absteigend", "com_ui_description": "Beschreibung", "com_ui_description_placeholder": "Optional: Gib eine Beschreibung für den Prompt ein", + "com_ui_download": "Herunterladen", + "com_ui_download_artifact": "Artefakt herunterladen", "com_ui_download_error": "Fehler beim Herunterladen der Datei. Die Datei wurde möglicherweise gelöscht.", "com_ui_dropdown_variables": "Dropdown-Variablen:", "com_ui_dropdown_variables_info": "Erstellen Sie benutzerdefinierte Dropdown-Menüs für Ihre Eingabeaufforderungen: `{{variable_name:option1|option2|option3}}`", @@ -558,6 +585,7 @@ "com_ui_examples": "Beispiele", "com_ui_export_convo_modal": "Konversation exportieren", "com_ui_field_required": "Dieses Feld ist erforderlich", + "com_ui_filter_prompts": "Prompts filtern", "com_ui_filter_prompts_name": "Prompts nach Namen filtern", "com_ui_fork": "Abzweigen", "com_ui_fork_all_target": "Alle bis/von hier einbeziehen", @@ -581,8 +609,10 @@ "com_ui_fork_split_target_setting": "Abzweigung standardmäßig von der Zielnachricht beginnen", "com_ui_fork_success": "Konversation erfolgreich abgezweigt", "com_ui_fork_visible": "Nur sichtbare Nachrichten", + "com_ui_go_back": "Zurück", "com_ui_go_to_conversation": "Zur Konversation gehen", "com_ui_happy_birthday": "Es ist mein 1. Geburtstag!", + "com_ui_hide_qr": "QR-Code ausblenden", "com_ui_host": "Host", "com_ui_image_gen": "Bildgenerierung", "com_ui_import_conversation": "Importieren", @@ -594,6 +624,8 @@ "com_ui_input": "Eingabe", "com_ui_instructions": "Anweisungen", "com_ui_latest_footer": "Alle KIs für alle.", + "com_ui_latest_production_version": "Neueste Produktiv-Version", + "com_ui_latest_version": "Neueste Version", "com_ui_librechat_code_api_key": "Hole dir deinen LibreChat Code Interpreter API-Schlüssel", "com_ui_librechat_code_api_subtitle": "Sicher. Mehrsprachig. Ein-/Ausgabedateien.", "com_ui_librechat_code_api_title": "KI-Code ausführen", @@ -617,8 +649,10 @@ "com_ui_no_category": "Keine Kategorie", "com_ui_no_changes": "Keine Änderungen zum Aktualisieren", "com_ui_no_terms_content": "Keine Inhalte der Allgemeinen Geschäftsbedingungen zum Anzeigen", + "com_ui_none": "Keine", "com_ui_none_selected": "Nichts ausgewählt", "com_ui_nothing_found": "Nichts gefunden", + "com_ui_oauth": "OAuth", "com_ui_of": "von", "com_ui_off": "Aus", "com_ui_on": "An", @@ -641,9 +675,12 @@ "com_ui_prompts_allow_use": "Verwendung von Prompts erlauben", "com_ui_provider": "Anbieter", "com_ui_read_aloud": "Vorlesen", + "com_ui_refresh_link": "Link aktualisieren", "com_ui_regenerate": "Neu generieren", "com_ui_region": "Region", "com_ui_rename": "Umbenennen", + "com_ui_rename_prompt": "Prompt umbenennen", + "com_ui_requires_auth": "Authentifizierung erforderlich", "com_ui_reset_var": "{{0}} zurücksetzen", "com_ui_result": "Ergebnis", "com_ui_revoke": "Widerrufen", @@ -659,6 +696,7 @@ "com_ui_save_submit": "Speichern & Absenden", "com_ui_saved": "Gespeichert!", "com_ui_schema": "Schema", + "com_ui_search": "Suche", "com_ui_select": "Auswählen", "com_ui_select_file": "Datei auswählen", "com_ui_select_model": "Ein KI-Modell auswählen", @@ -677,9 +715,13 @@ "com_ui_share_to_all_users": "Mit allen Benutzern teilen", "com_ui_share_update_message": "Ihr Name, benutzerdefinierte Anweisungen und alle Nachrichten, die du nach dem Teilen hinzufügen, bleiben privat.", "com_ui_share_var": "{{0}} teilen", + "com_ui_shared_link_bulk_delete_success": "Geteilte Links erfolgreich gelöscht", + "com_ui_shared_link_delete_success": "Geteilter Link erfolgreich gelöscht", "com_ui_shared_link_not_found": "Geteilter Link nicht gefunden", "com_ui_shared_prompts": "Geteilte Prompts", "com_ui_show_all": "Alle anzeigen", + "com_ui_show_qr": "QR-Code anzeigen", + "com_ui_sign_in_to_domain": "Anmelden bei {{0}}", "com_ui_simple": "Einfach", "com_ui_size": "Größe", "com_ui_special_variables": "Spezielle Variablen:", @@ -688,8 +730,13 @@ "com_ui_stop": "Stopp", "com_ui_storage": "Speicher", "com_ui_submit": "Absenden", + "com_ui_temporary_chat": "Temporärer Chat", "com_ui_terms_and_conditions": "Allgemeine Geschäftsbedingungen", "com_ui_terms_of_service": "Nutzungsbedingungen", + "com_ui_thinking": "Nachdenken...", + "com_ui_thoughts": "Gedanken", + "com_ui_token_exchange_method": "Token-Austauschmethode", + "com_ui_token_url": "Token-URL", "com_ui_tools": "Werkzeuge", "com_ui_unarchive": "Aus Archiv holen", "com_ui_unarchive_error": "Konversation konnte nicht aus dem Archiv geholt werden", @@ -713,6 +760,7 @@ "com_ui_variables_info": "Verwende doppelte geschweifte Klammern in Ihrem Text, um Variablen zu erstellen, z.B. {{Beispielvariable}}, die du später beim Verwenden des Prompts ausfüllen kannst.", "com_ui_version_var": "Version {{0}}", "com_ui_versions": "Versionen", + "com_ui_view_source": "Quell-Chat anzeigen", "com_ui_yes": "Ja", "com_ui_zoom": "Zoom", "com_user_message": "Du", diff --git a/client/src/locales/et/translation.json b/client/src/locales/et/translation.json new file mode 100644 index 0000000000..96aa423ba6 --- /dev/null +++ b/client/src/locales/et/translation.json @@ -0,0 +1,788 @@ +{ + "chat_direction_left_to_right": "midagi peaks siia minema. Oli tühi", + "chat_direction_right_to_left": "midagi peaks siia minema. Oli tühi", + "com_a11y_ai_composing": "AI genereerib vastust.", + "com_a11y_end": "AI on oma vastuse lõpetanud.", + "com_a11y_start": "AI on oma vastuse andmise alustanud.", + "com_agents_allow_editing": "Luba teistel kasutajatel sinu agenti muuta", + "com_agents_by_librechat": "LibreChatilt", + "com_agents_code_interpreter": "Kui see on lubatud, saab sinu agent kasutada LibreChati koodiinterpreteerimise API-t genereeritud koodi turvaliseks käivitamiseks, sealhulgas failide töötlemiseks. Vajalik on kehtiv API võti.", + "com_agents_code_interpreter_title": "Koodiinterpreteerimise API", + "com_agents_create_error": "Agendi loomisel tekkis viga.", + "com_agents_description_placeholder": "Valikuline: Kirjelda oma agenti siin", + "com_agents_enable_file_search": "Luba failiotsing", + "com_agents_file_search_disabled": "Agent tuleb luua enne failide üleslaadimist failiotsinguks.", + "com_agents_file_search_info": "Kui see on lubatud, teavitatakse agenti täpselt allpool loetletud failinimedest, mis võimaldab tal nendest failidest asjakohast konteksti hankida.", + "com_agents_instructions_placeholder": "Süsteemijuhised, mida agent kasutab", + "com_agents_missing_provider_model": "Enne agendi loomist valige teenusepakkuja ja mudel.", + "com_agents_name_placeholder": "Valikuline: Agendi nimi", + "com_agents_no_access": "Sul pole õigust seda agenti muuta.", + "com_agents_not_available": "Agent pole saadaval", + "com_agents_search_name": "Otsi agente nime järgi", + "com_agents_update_error": "Agendi uuendamisel tekkis viga.", + "com_assistants_action_attempt": "Assistent soovib suhelda: {{0}}", + "com_assistants_actions": "Tegevused", + "com_assistants_actions_disabled": "Enne tegevuste lisamist peate looma assistendi.", + "com_assistants_actions_info": "Lubage oma assistendil API-de kaudu teavet hankida või toiminguid teha", + "com_assistants_add_actions": "Lisa tegevusi", + "com_assistants_add_tools": "Lisa tööriistu", + "com_assistants_allow_sites_you_trust": "Luba ainult usaldusväärseid saite.", + "com_assistants_append_date": "Lisa praegune kuupäev ja kellaaeg", + "com_assistants_append_date_tooltip": "Kui see on lubatud, lisatakse praegune kliendi kuupäev ja kellaaeg assistendi süsteemijuhistele.", + "com_assistants_attempt_info": "Assistent soovib saata järgmist:", + "com_assistants_available_actions": "Saadaolevad tegevused", + "com_assistants_capabilities": "Võimed", + "com_assistants_code_interpreter": "Koodiinterpreteerija", + "com_assistants_code_interpreter_files": "Allpool olevad failid on ainult koodiinterpreteerijale:", + "com_assistants_code_interpreter_info": "Koodiinterpreteerija võimaldab assistendil kirjutada ja käivitada koodi. See tööriist saab töödelda mitmekesise andmestruktuuri ja vorminguga faile ning genereerida faile, näiteks graafikuid.", + "com_assistants_completed_action": "Suhtles {{0}}", + "com_assistants_completed_function": "Käivitas {{0}}", + "com_assistants_conversation_starters": "Vestluse alustajad", + "com_assistants_conversation_starters_placeholder": "Sisesta vestluse alustaja", + "com_assistants_create_error": "Assistendi loomisel tekkis viga.", + "com_assistants_create_success": "Loomine õnnestus", + "com_assistants_delete_actions_error": "Tegevuse kustutamisel tekkis viga.", + "com_assistants_delete_actions_success": "Tegevuse kustutamine assistendilt õnnestus", + "com_assistants_description_placeholder": "Valikuline: Kirjelda oma assistenti siin", + "com_assistants_domain_info": "Assistent saatis selle teabe aadressile {{0}}", + "com_assistants_file_search": "Failiotsing", + "com_assistants_file_search_info": "Failiotsing võimaldab assistendil kasutada teadmisi failidest, mille sina või sinu kasutajad üles laadite. Kui fail on üles laaditud, otsustab assistent automaatselt, millal kasutajate päringute põhjal sisu hankida. Vektorhoidlate lisamine failiotsinguks pole veel toetatud. Saate neid lisada teenusepakkuja mänguväljakult või lisada faile sõnumitele, et neid lõimes failiotsinguks kasutada.", + "com_assistants_function_use": "Assistent kasutas {{0}}", + "com_assistants_image_vision": "Pildi nägemine", + "com_assistants_instructions_placeholder": "Süsteemijuhised, mida assistent kasutab", + "com_assistants_knowledge": "Teadmised", + "com_assistants_knowledge_disabled": "Enne failide teadmistena üleslaadimist tuleb assistent luua ning koodiinterpreteerija või otsing lubada ja salvestada.", + "com_assistants_knowledge_info": "Kui laadite faile üles jaotise Teadmised alla, võivad vestlused teie assistendiga sisaldada failide sisu.", + "com_assistants_max_starters_reached": "Vestluse alustajate maksimaalne arv on saavutatud", + "com_assistants_name_placeholder": "Valikuline: Assistendi nimi", + "com_assistants_non_retrieval_model": "Selles mudelis pole failiotsing lubatud. Valige mõni muu mudel.", + "com_assistants_retrieval": "Otsing", + "com_assistants_running_action": "Käivitatakse tegevust", + "com_assistants_search_name": "Otsi assistente nime järgi", + "com_assistants_update_actions_error": "Tegevuse loomisel või uuendamisel tekkis viga.", + "com_assistants_update_actions_success": "Tegevuse loomine või uuendamine õnnestus", + "com_assistants_update_error": "Assistendi uuendamisel tekkis viga.", + "com_assistants_update_success": "Uuendamine õnnestus", + "com_auth_already_have_account": "Sul on juba konto?", + "com_auth_apple_login": "Logi sisse Apple'iga", + "com_auth_back_to_login": "Tagasi sisselogimisele", + "com_auth_click": "Vajuta", + "com_auth_click_here": "Vajuta siia", + "com_auth_continue": "Jätka", + "com_auth_create_account": "Loo oma konto", + "com_auth_discord_login": "Jätka Discordiga", + "com_auth_email": "E-post", + "com_auth_email_address": "E-posti aadress", + "com_auth_email_max_length": "E-post ei tohiks olla pikem kui 120 tähemärki", + "com_auth_email_min_length": "E-post peab olema vähemalt 6 tähemärki", + "com_auth_email_pattern": "Sa pead sisestama kehtiva e-posti aadressi", + "com_auth_email_required": "E-post on kohustuslik", + "com_auth_email_resend_link": "Saada e-kiri uuesti", + "com_auth_email_resent_failed": "E-kirja uuesti saatmine ebaõnnestus", + "com_auth_email_resent_success": "Kinnituskiri saadeti edukalt uuesti", + "com_auth_email_verification_failed": "E-posti kinnitamine ebaõnnestus", + "com_auth_email_verification_failed_token_missing": "Kinnitamine ebaõnnestus, tunnus puudub", + "com_auth_email_verification_in_progress": "Sinu e-posti kinnitamine, palun oota", + "com_auth_email_verification_invalid": "Vigane e-posti kinnitus", + "com_auth_email_verification_redirecting": "Suunatakse ümber {{0}} sekundi pärast...", + "com_auth_email_verification_resend_prompt": "Kas sa ei saanud e-kirja?", + "com_auth_email_verification_success": "E-post kinnitatud", + "com_auth_error_create": "Konto registreerimisel tekkis viga. Proovige uuesti.", + "com_auth_error_invalid_reset_token": "See parooli lähtestamise tunnus pole enam kehtiv.", + "com_auth_error_login": "Sisselogimine esitatud teabega ei õnnestu. Palun kontrolli oma andmeid ja proovi uuesti.", + "com_auth_error_login_ban": "Sinu konto on ajutiselt blokeeritud meie teenuse rikkumiste tõttu.", + "com_auth_error_login_rl": "Liiga palju sisselogimiskatseid lühikese aja jooksul. Palun proovi hiljem uuesti.", + "com_auth_error_login_server": "Tekkis sisemine serveriviga. Palun oota hetk ja proovi uuesti.", + "com_auth_error_login_unverified": "Sinu kontot pole veel kinnitatud. Palun leia kinnituslink enda e-postist.", + "com_auth_facebook_login": "Jätka Facebookiga", + "com_auth_full_name": "Täisnimi", + "com_auth_github_login": "Jätka Githubiga", + "com_auth_google_login": "Jätka Google'iga", + "com_auth_here": "SIIA", + "com_auth_login": "Logi sisse", + "com_auth_login_with_new_password": "Nüüd saad sisse logida oma uue parooliga.", + "com_auth_name_max_length": "Nimi peab olema vähem kui 80 tähemärki", + "com_auth_name_min_length": "Nimi peab olema vähemalt 3 tähemärki", + "com_auth_name_required": "Nimi on kohustuslik", + "com_auth_no_account": "Sul pole kontot?", + "com_auth_password": "Parool", + "com_auth_password_confirm": "Kinnita parool", + "com_auth_password_forgot": "Unustasid parooli?", + "com_auth_password_max_length": "Parool peab olema vähem kui 128 tähemärki", + "com_auth_password_min_length": "Parool peab olema vähemalt 8 tähemärki", + "com_auth_password_not_match": "Paroolid ei ühti", + "com_auth_password_required": "Parool on kohustuslik", + "com_auth_registration_success_generic": "Palun vaata oma e-posti, et oma e-posti aadress kinnitada.", + "com_auth_registration_success_insecure": "Registreerimine õnnestus.", + "com_auth_reset_password": "Lähtesta oma parool", + "com_auth_reset_password_if_email_exists": "Kui selle e-postiga konto on olemas, on saadetud parooli lähtestamise juhistega e-kiri. Palun kontrolli oma rämpsposti kausta.", + "com_auth_reset_password_link_sent": "E-kiri saadetud", + "com_auth_reset_password_success": "Parooli lähtestamine õnnestus", + "com_auth_sign_in": "Logi sisse", + "com_auth_sign_up": "Registreeru", + "com_auth_submit_registration": "Saada registreerimine", + "com_auth_to_reset_your_password": "parooli lähtestamiseks.", + "com_auth_to_try_again": "uuesti proovimiseks.", + "com_auth_username": "Kasutajanimi (valikuline)", + "com_auth_username_max_length": "Kasutajanimi peab olema vähem kui 20 tähemärki", + "com_auth_username_min_length": "Kasutajanimi peab olema vähemalt 2 tähemärki", + "com_auth_welcome_back": "Teretulemast tagasi", + "com_click_to_download": "(vajuta siia, et alla laadida)", + "com_download_expired": "(allalaadimine aegunud)", + "com_download_expires": "(vajuta siia, et alla laadida - aegub {{0}})", + "com_endpoint": "Otspunkt", + "com_endpoint_agent": "Agent", + "com_endpoint_agent_model": "Agendi mudel (soovitatav: GPT-3.5)", + "com_endpoint_agent_placeholder": "Palun vali agent", + "com_endpoint_ai": "AI", + "com_endpoint_anthropic_maxoutputtokens": "Maksimaalne märkide arv, mida vastuses genereerida saab. Lühemate vastuste jaoks määrake madalam väärtus ja pikemate vastuste jaoks kõrgem väärtus. Märkus: mudelid võivad peatuda enne selle maksimumi saavutamist.", + "com_endpoint_anthropic_prompt_cache": "Päringu vahemällu salvestamine võimaldab suurt konteksti või juhiseid API-kõnede vahel uuesti kasutada, vähendades kulusid ja latentsust", + "com_endpoint_anthropic_temp": "Vahemikus 0 kuni 1. Kasutage analüütiliste / valikvastustega küsimuste puhul väärtust, mis on lähemal 0-le, ning loominguliste ja genereerivate ülesannete puhul väärtust, mis on lähemal 1-le. Soovitame muuta kas seda või Top P-d, aga mitte mõlemat.", + "com_endpoint_anthropic_topk": "Top-k muudab seda, kuidas mudel valib väljundi jaoks märgid. Top-k väärtus 1 tähendab, et valitud märk on kõige tõenäolisem kõigi mudeli sõnavaras olevate märkide seas (nimetatakse ka ahneks dekodeerimiseks), samas kui top-k väärtus 3 tähendab, et järgmine märk valitakse 3 kõige tõenäolisema märgi seast (kasutades temperatuuri).", + "com_endpoint_anthropic_topp": "Top-p muudab seda, kuidas mudel valib väljundi jaoks märgid. Märgid valitakse kõige tõenäolisemast K (vt parameetrit topK) kuni vähim tõenäoliseni, kuni nende tõenäosuste summa on võrdne top-p väärtusega.", + "com_endpoint_assistant": "Assistent", + "com_endpoint_assistant_model": "Assistendi mudel", + "com_endpoint_assistant_placeholder": "Palun vali assistent parempoolsest külgpaneelist", + "com_endpoint_completion": "Lõpetamine", + "com_endpoint_completion_model": "Lõpetamise mudel (soovitatav: GPT-4)", + "com_endpoint_config_click_here": "Klõpsa siia", + "com_endpoint_config_google_api_info": "Et saada oma Generative Language API võtit (Gemini jaoks),", + "com_endpoint_config_google_api_key": "Google API võti", + "com_endpoint_config_google_cloud_platform": "(Google Cloud Platformilt)", + "com_endpoint_config_google_gemini_api": "(Gemini API)", + "com_endpoint_config_google_service_key": "Google'i teenusekonto võti", + "com_endpoint_config_key": "Määra API võti", + "com_endpoint_config_key_encryption": "Sinu võti krüpteeritakse ja kustutatakse", + "com_endpoint_config_key_for": "Määra API võti", + "com_endpoint_config_key_google_need_to": "Sa pead", + "com_endpoint_config_key_google_service_account": "Loo teenusekonto", + "com_endpoint_config_key_google_vertex_ai": "Luba Vertex AI", + "com_endpoint_config_key_google_vertex_api": "API Google Cloudis, seejärel", + "com_endpoint_config_key_google_vertex_api_role": "Veenduge, et klõpsate 'Loo ja jätka', et anda vähemalt roll 'Vertex AI kasutaja'. Lõpuks looge siia importimiseks JSON-võti.", + "com_endpoint_config_key_import_json_key": "Impordi teenusekonto JSON-võti.", + "com_endpoint_config_key_import_json_key_invalid": "Vigane teenusekonto JSON-võti, kas importisite õige faili?", + "com_endpoint_config_key_import_json_key_success": "Teenusekonto JSON-võtme importimine õnnestus", + "com_endpoint_config_key_name": "Võti", + "com_endpoint_config_key_never_expires": "Sinu võti ei aegu kunagi", + "com_endpoint_config_placeholder": "Määra oma võti päise menüüs vestlemiseks.", + "com_endpoint_config_value": "Sisesta väärtus", + "com_endpoint_context": "Kontekst", + "com_endpoint_context_info": "Maksimaalne märkide arv, mida konteksti jaoks kasutada saab. Kasutage seda selleks, et kontrollida, mitu märki taotluse kohta saadetakse. Kui seda pole määratud, kasutatakse süsteemi vaikesätteid, mis põhinevad teadaolevate mudelite konteksti suurusel. Kõrgemate väärtuste määramine võib põhjustada vigu ja/või suuremaid märgikulusid.", + "com_endpoint_context_tokens": "Maksimum kontekstimärgid", + "com_endpoint_custom_name": "Kohandatud nimi", + "com_endpoint_default": "vaikimisi", + "com_endpoint_default_blank": "vaikimisi: tühi", + "com_endpoint_default_empty": "vaikimisi: tühi", + "com_endpoint_default_with_num": "vaikimisi: {{0}}", + "com_endpoint_examples": "Eelseadistused", + "com_endpoint_export": "Ekspordi", + "com_endpoint_export_share": "Ekspordi/Jaga", + "com_endpoint_frequency_penalty": "Sageduse karistus", + "com_endpoint_func_hover": "Luba pluginate kasutamine OpenAI funktsioonidena", + "com_endpoint_google_custom_name_placeholder": "Määra Google'ile kohandatud nimi", + "com_endpoint_google_maxoutputtokens": "Maksimaalne märkide arv, mida vastuses genereerida saab. Lühemate vastuste jaoks määrake madalam väärtus ja pikemate vastuste jaoks kõrgem väärtus. Märkus: mudelid võivad peatuda enne selle maksimumi saavutamist.", + "com_endpoint_google_temp": "Kõrgemad väärtused = juhuslikum, samas kui madalamad väärtused = keskendunum ja deterministlikum. Soovitame muuta kas seda või Top P-d, aga mitte mõlemat.", + "com_endpoint_google_topk": "Top-k muudab seda, kuidas mudel valib väljundi jaoks märgid. Top-k väärtus 1 tähendab, et valitud märk on kõige tõenäolisem kõigi mudeli sõnavaras olevate märkide seas (nimetatakse ka ahneks dekodeerimiseks), samas kui top-k väärtus 3 tähendab, et järgmine märk valitakse 3 kõige tõenäolisema märgi seast (kasutades temperatuuri).", + "com_endpoint_google_topp": "Top-p muudab seda, kuidas mudel valib väljundi jaoks märgid. Märgid valitakse kõige tõenäolisemast K (vt parameetrit topK) kuni vähim tõenäoliseni, kuni nende tõenäosuste summa on võrdne top-p väärtusega.", + "com_endpoint_import": "Impordi", + "com_endpoint_instructions_assistants": "Tühista juhised", + "com_endpoint_instructions_assistants_placeholder": "Tühistab assistendi juhised. See on kasulik käitumise muutmiseks käivituse kohta.", + "com_endpoint_max_output_tokens": "Maksimaalsed väljundmärgid", + "com_endpoint_message": "Sõnum", + "com_endpoint_message_new": "Sõnum {{0}}", + "com_endpoint_message_not_appendable": "Muuda oma sõnumit või genereeri uuesti.", + "com_endpoint_my_preset": "Minu eelseadistus", + "com_endpoint_no_presets": "Eelseadistusi pole veel, kasutage ühe loomiseks seadete nuppu", + "com_endpoint_open_menu": "Ava menüü", + "com_endpoint_openai_custom_name_placeholder": "Määra AI-le kohandatud nimi", + "com_endpoint_openai_detail": "Visioonitaotluste eraldusvõime. \"Madal\" on odavam ja kiirem, \"Kõrge\" on detailsem ja kallim ning \"Automaatne\" valib automaatselt kahe vahel vastavalt pildi eraldusvõimele.", + "com_endpoint_openai_freq": "Arv vahemikus -2,0 kuni 2,0. Positiivsed väärtused karistavad uusi märke nende senise sageduse alusel tekstis, vähendades mudeli tõenäosust sama rida sõnasõnaliselt korrata.", + "com_endpoint_openai_max": "Maksimaalsed genereeritavad märgid. Sisendmärkide ja genereeritud märkide kogupikkus on piiratud mudeli konteksti pikkusega.", + "com_endpoint_openai_max_tokens": "Valikuline väli 'max_tokens', mis esindab vestluse lõpetamisel genereeritavate märkide maksimaalset arvu. Sisendmärkide ja genereeritud märkide kogupikkus on piiratud mudelite konteksti pikkusega. Kui see arv ületab maksimaalseid kontekstimärke, võivad tekkida vead.", + "com_endpoint_openai_pres": "Arv vahemikus -2,0 kuni 2,0. Positiivsed väärtused karistavad uusi märke selle alusel, kas need esinevad senises tekstis, suurendades mudeli tõenäosust rääkida uutest teemadest.", + "com_endpoint_openai_prompt_prefix_placeholder": "Määra kohandatud juhised süsteemiteatesse lisamiseks. Vaikimisi: puudub", + "com_endpoint_openai_reasoning_effort": "Ainult o1 mudelid: piirab põhjendusmudelite põhjendustegevust. Põhjendustegevuse vähendamine võib tuua kaasa kiiremad vastused ja vähem põhjendamiseks kasutatud märke vastuses.", + "com_endpoint_openai_resend": "Saada kõik varem lisatud pildid uuesti. Märkus: see võib märgikulusid oluliselt suurendada ja paljude pildimanustega võivad tekkida vead.", + "com_endpoint_openai_resend_files": "Saada kõik varem lisatud failid uuesti. Märkus: see suurendab märgikulusid ja paljude manustega võivad tekkida vead.", + "com_endpoint_openai_stop": "Kuni 4 järjestust, mille korral API lõpetab täiendavate märkide genereerimise.", + "com_endpoint_openai_temp": "Kõrgemad väärtused = juhuslikum, samas kui madalamad väärtused = keskendunum ja deterministlikum. Soovitame muuta kas seda või Top P-d, aga mitte mõlemat.", + "com_endpoint_openai_topp": "Alternatiiviks temperatuuriga näidiste võtmisele on tuumaproovide võtmine, kus mudel arvestab märkide tulemustega, millel on top_p tõenäosuse mass. Seega 0,1 tähendab, et arvesse võetakse ainult märke, mis moodustavad 10% suurima tõenäosusega massi. Soovitame muuta kas seda või temperatuuri, aga mitte mõlemat.", + "com_endpoint_output": "Väljund", + "com_endpoint_plug_image_detail": "Pildi detailid", + "com_endpoint_plug_resend_files": "Saada failid uuesti", + "com_endpoint_plug_set_custom_instructions_for_gpt_placeholder": "Määra kohandatud juhised süsteemiteatesse lisamiseks. Vaikimisi: puudub", + "com_endpoint_plug_skip_completion": "Jäta lõpetamine vahele", + "com_endpoint_plug_use_functions": "Kasuta funktsioone", + "com_endpoint_presence_penalty": "Olekukaristus", + "com_endpoint_preset": "eelseadistus", + "com_endpoint_preset_custom_name_placeholder": "midagi peaks siia minema. Oli tühi", + "com_endpoint_preset_default": "on nüüd vaike-eelseadistus.", + "com_endpoint_preset_default_item": "Vaikimisi:", + "com_endpoint_preset_default_none": "Vaikimisi eelseadistus pole aktiivne.", + "com_endpoint_preset_default_removed": "pole enam vaike-eelseadistus.", + "com_endpoint_preset_delete_confirm": "Oled sa kindel, et sa soovid selle eelseadistuse kustutada?", + "com_endpoint_preset_delete_error": "Eelseadistuse kustutamisel tekkis viga. Palun proovi uuesti.", + "com_endpoint_preset_import": "Eelseadistus imporditud!", + "com_endpoint_preset_import_error": "Eelseadistuse importimisel tekkis viga. Palun proovi uuesti.", + "com_endpoint_preset_name": "Eelseadistuse nimi", + "com_endpoint_preset_save_error": "Eelseadistuse salvestamisel tekkis viga. Palun proovi uuesti.", + "com_endpoint_preset_selected": "Eelseadistus aktiivne!", + "com_endpoint_preset_selected_title": "Aktiivne!", + "com_endpoint_preset_title": "Eelseadistus", + "com_endpoint_presets": "eelseadistused", + "com_endpoint_presets_clear_warning": "Oled sa kindel, et sa soovid kõik eelseadistused kustutada? See on pöördumatu.", + "com_endpoint_prompt_cache": "Kasuta päringu vahemällu salvestamist", + "com_endpoint_prompt_prefix": "Kohandatud juhised", + "com_endpoint_prompt_prefix_assistants": "Täiendavad juhised", + "com_endpoint_prompt_prefix_assistants_placeholder": "Määra täiendavad juhised või kontekst lisaks assistendi peamistele juhistele. Kui see on tühi, ignoreeritakse seda.", + "com_endpoint_prompt_prefix_placeholder": "Määra kohandatud juhised või kontekst. Kui see on tühi, ignoreeritakse seda.", + "com_endpoint_reasoning_effort": "Põhjendustegevus", + "com_endpoint_save_as_preset": "Salvesta eelseadistusena", + "com_endpoint_search": "Otsi otspunkti nime järgi", + "com_endpoint_set_custom_name": "Määra kohandatud nimi, juhuks kui sa selle eelseadistuse leiad", + "com_endpoint_skip_hover": "Luba lõpetamise etapi vahelejätmine, mis vaatab üle lõpliku vastuse ja genereeritud sammud", + "com_endpoint_stop": "Peatamise järjestused", + "com_endpoint_stop_placeholder": "Eralda väärtused, vajutades `Enter`", + "com_endpoint_temperature": "Temperatuur", + "com_endpoint_top_k": "Top K", + "com_endpoint_top_p": "Top P", + "com_endpoint_use_active_assistant": "Kasuta aktiivset assistenti", + "com_error_expired_user_key": "Esitatud võti {{0}} aegus {{1}}. Esitage uus võti ja proovige uuesti.", + "com_error_files_dupe": "Leiti duplikaatfail.", + "com_error_files_empty": "Tühjad failid pole lubatud.", + "com_error_files_process": "Faili töötlemisel tekkis viga.", + "com_error_files_unsupported_capability": "Ühtegi seda failitüüpi toetavat võimalust pole lubatud.", + "com_error_files_upload": "Faili üleslaadimisel tekkis viga.", + "com_error_files_upload_canceled": "Faili üleslaadimise taotlus tühistati. Märkus: faili üleslaadimine võib endiselt olla pooleli ja see tuleb käsitsi kustutada.", + "com_error_files_validation": "Faili valideerimisel tekkis viga.", + "com_error_input_length": "Viimase sõnumi märkide arv on liiga pikk, ületades märkide limiidi (vastavalt {{0}}). Palun lühendage oma sõnumit, kohandage vestluse parameetrites maksimaalset konteksti suurust või jätkamiseks hargnege vestlus.", + "com_error_invalid_user_key": "Esitati vigane võti. Esitage kehtiv võti ja proovige uuesti.", + "com_error_moderation": "Tundub, et esitatud sisu on meie modereerimissüsteemi poolt märgistatud kui meie kogukonna juhistega mitte vastav. Me ei saa selle konkreetse teemaga jätkata. Kui teil on muid küsimusi või teemasid, mida soovite uurida, muutke oma sõnumit või looge uus vestlus.", + "com_error_no_base_url": "Baasaadressi ei leitud. Palun lisage see ja proovige uuesti.", + "com_error_no_user_key": "Võtit ei leitud. Palun sisestage võti ja proovige uuesti.", + "com_files_filter": "Filtreeri faile...", + "com_files_no_results": "Tulemusi pole.", + "com_files_number_selected": "{{0}} / {{1}} üksust valitud", + "com_files_table": "Mmdagi peaks siia minema. Oli tühi", + "com_generated_files": "Genereeritud failid:", + "com_hide_examples": "Peida näited", + "com_nav_account_settings": "Konto seaded", + "com_nav_always_make_prod": "Tee uued versioonid alati toodangusse", + "com_nav_archive_created_at": "Arhiveerimise kuupäev", + "com_nav_archive_name": "Nimi", + "com_nav_archived_chats": "Arhiveeritud vestlused", + "com_nav_archived_chats_empty": "Sul ei ole arhiveeritud vestlusi.", + "com_nav_archived_chats_manage": "Halda", + "com_nav_at_command": "@-käsk", + "com_nav_at_command_description": "Lülita käsk \"@\" sisse/välja lõpp-punktide, mudelite, eelseadistuste jms vahetamiseks.", + "com_nav_audio_play_error": "Viga heli esitamisel: {{0}}", + "com_nav_audio_process_error": "Viga heli töötlemisel: {{0}}", + "com_nav_auto_scroll": "Automaatne kerimine vestluse avamisel viimase sõnumini", + "com_nav_auto_send_prompts": "Saada vihjed automaatselt", + "com_nav_auto_send_text": "Saada tekst automaatselt", + "com_nav_auto_send_text_disabled": "Keelamiseks määra -1", + "com_nav_auto_transcribe_audio": "Transkribeeri heli automaatselt", + "com_nav_automatic_playback": "Esita viimane sõnum automaatselt", + "com_nav_balance": "Saldo", + "com_nav_browser": "Brauser", + "com_nav_buffer_append_error": "Probleem heli voogedastusega. Taasesitus võib katkeda.", + "com_nav_change_picture": "Muuda pilti", + "com_nav_chat_commands": "Vestluskäsud", + "com_nav_chat_commands_info": "Need käsud aktiveeritakse konkreetsete märkide sisestamisel sõnumi alguses. Iga käsk käivitatakse sellele määratud eesliitega. Saate need keelata, kui kasutate neid märke sageli sõnumite alustamiseks.", + "com_nav_chat_direction": "Vestluse suund", + "com_nav_clear_all_chats": "Tühjenda kõik vestlused", + "com_nav_clear_cache_confirm_message": "Oled sa kindel, et sa soovid vahemälu tühjendada?", + "com_nav_clear_conversation": "Tühjenda vestlused", + "com_nav_clear_conversation_confirm_message": "Oled sa kindel, et sa soovid kõik vestlused tühjendada? See on pöördumatu.", + "com_nav_close_sidebar": "Sulge külgriba", + "com_nav_commands": "Käsud", + "com_nav_confirm_clear": "Kinnita tühjendamine", + "com_nav_conversation_mode": "Vestlusrežiim", + "com_nav_convo_menu_options": "Vestluse menüü valikud", + "com_nav_db_sensitivity": "Detsibelli tundlikkus", + "com_nav_delete_account": "Kustuta konto", + "com_nav_delete_account_button": "Kustuta minu konto jäädavalt", + "com_nav_delete_account_confirm": "Kustuta konto - oled sa kindel?", + "com_nav_delete_account_email_placeholder": "Palun sisesta oma konto e-post", + "com_nav_delete_cache_storage": "Kustuta TTS vahemälu", + "com_nav_delete_data_info": "Kõik sinu andmed kustutatakse.", + "com_nav_delete_warning": "HOIATUS: See kustutab sinu konto jäädavalt.", + "com_nav_edge": "Edge", + "com_nav_enable_cache_tts": "Luba TTS vahemälu", + "com_nav_enable_cloud_browser_voice": "Kasuta pilvepõhiseid hääli", + "com_nav_enabled": "Lubatud", + "com_nav_engine": "Mootor", + "com_nav_enter_to_send": "Vajuta Enter sõnumite saatmiseks", + "com_nav_export": "Ekspordi", + "com_nav_export_all_message_branches": "Ekspordi kõik sõnumite harud", + "com_nav_export_conversation": "Ekspordi vestlus", + "com_nav_export_filename": "Failinimi", + "com_nav_export_filename_placeholder": "Määra failinimi", + "com_nav_export_include_endpoint_options": "Kaasa otspunkti valikud", + "com_nav_export_recursive": "Rekursiivne", + "com_nav_export_recursive_or_sequential": "Rekursiivne või järjestikune?", + "com_nav_export_type": "Tüüp", + "com_nav_external": "Väline", + "com_nav_font_size": "Sõnumi fondi suurus", + "com_nav_font_size_base": "Keskmine", + "com_nav_font_size_lg": "Suur", + "com_nav_font_size_sm": "Väike", + "com_nav_font_size_xl": "Eriti suur", + "com_nav_font_size_xs": "Eriti väike", + "com_nav_help_faq": "Abi ja KKK", + "com_nav_hide_panel": "Peida kõige parempoolsem külgpaneel", + "com_nav_info_code_artifacts": "Võimaldab katsetuslike koodiartefaktide kuvamist vestluse kõrval", + "com_nav_info_code_artifacts_agent": "Võimaldab selle agendi jaoks koodiartefaktide kasutamist. Vaikimisi lisatakse artefaktide kasutamisele spetsiifilised täiendavad juhised, välja arvatud juhul, kui on lubatud \"Kohandatud viiparežiim\".", + "com_nav_info_custom_prompt_mode": "Kui see on lubatud, siis vaikimisi artefaktide süsteemiviipa ei lisata. Kõik artefakte genereerivad juhised tuleb selles režiimis käsitsi esitada.", + "com_nav_info_enter_to_send": "Kui see on lubatud, saadab `ENTER` sõnumi. Kui see on keelatud, lisab Enter uue rea ja sõnumi saatmiseks peate vajutama `CTRL + ENTER` / `⌘ + ENTER`.", + "com_nav_info_fork_change_default": "`Ainult nähtavad sõnumid` sisaldab ainult otsest teed valitud sõnumini. `Kaasa seotud harud` lisab harud mööda teed. `Kaasa kõik siia/siit` sisaldab kõiki ühendatud sõnumeid ja harusid.", + "com_nav_info_fork_split_target_setting": "Kui see on lubatud, algab hargnemine sihtsõnumist vestluse viimase sõnumini vastavalt valitud käitumisele.", + "com_nav_info_include_shadcnui": "Kui see on lubatud, siis lisatakse juhised shadcn/ui komponentide kasutamiseks. shadcn/ui on Radix UI ja Tailwind CSS-i abil loodud taaskasutatavate komponentide kogu. Märkus: need on pikad juhised, peaksid lubama ainult siis, kui LLM-i õigete importide ja komponentide teavitamine on sinu jaoks oluline. Nende komponentide kohta lisateabe saamiseks külastage: https://ui.shadcn.com/", + "com_nav_info_latex_parsing": "Kui see on lubatud, renderdatakse sõnumites olev LaTeX-kood matemaatiliste võrranditena. Selle keelamine võib jõudlust parandada, kui sa LaTeX-i renderdamist ei vaja.", + "com_nav_info_save_draft": "Kui see on lubatud, salvestatakse vestlusvormi sisestatud tekst ja manused automaatselt kohalikult mustanditena. Need mustandid on saadaval ka siis, kui lehe uuesti laadite või teisele vestlusele lülitute. Mustandid salvestatakse lokaalselt sinu seadmesse ja kustutatakse pärast sõnumi saatmist.", + "com_nav_info_show_thinking": "Kui see on lubatud, kuvatakse vestluses vaikimisi avatud mõtlemise rippmenüüd, võimaldades sul AI põhjendusi reaalajas vaadata. Kui see on keelatud, jäävad mõtlemise rippmenüüd vaikimisi suletuks, et saada puhtam ja sujuvam liides", + "com_nav_info_user_name_display": "Kui see on lubatud, kuvatakse saatja kasutajanimi iga sinu saadetud sõnumi kohal. Kui see on keelatud, näed oma sõnumite kohal ainult \"Sina\".", + "com_nav_lang_arabic": "العربية", + "com_nav_lang_auto": "Tuvasta automaatselt", + "com_nav_lang_brazilian_portuguese": "Português Brasileiro", + "com_nav_lang_chinese": "中文", + "com_nav_lang_dutch": "Nederlands", + "com_nav_lang_english": "English", + "com_nav_lang_finnish": "Suomi", + "com_nav_lang_french": "Français ", + "com_nav_lang_german": "Deutsch", + "com_nav_lang_hebrew": "עברית", + "com_nav_lang_indonesia": "Indonesia", + "com_nav_lang_italian": "Italiano", + "com_nav_lang_japanese": "日本語", + "com_nav_lang_korean": "한국어", + "com_nav_lang_polish": "Polski", + "com_nav_lang_russian": "Русский", + "com_nav_lang_spanish": "Español", + "com_nav_lang_swedish": "Svenska", + "com_nav_lang_traditionalchinese": "繁體中文", + "com_nav_lang_turkish": "Türkçe", + "com_nav_lang_vietnamese": "Tiếng Việt", + "com_nav_language": "Keel", + "com_nav_latex_parsing": "LaTeXi parsimine sõnumites (võib mõjutada jõudlust)", + "com_nav_log_out": "Logi välja", + "com_nav_long_audio_warning": "Pikemate tekstide töötlemine võtab kauem aega.", + "com_nav_maximize_chat_space": "Maksimeeri vestluse ruumi", + "com_nav_modular_chat": "Luba otspunktide vahetamine vestluse keskel", + "com_nav_my_files": "Minu failid", + "com_nav_no_search_results": "Otsingutulemusi ei leitud", + "com_nav_not_supported": "Pole toetatud", + "com_nav_open_sidebar": "Ava külgriba", + "com_nav_playback_rate": "Heli taasesituse kiirus", + "com_nav_plugin_auth_error": "Selle pistikprogrammi autentimisel tekkis viga. Palun proovi uuesti.", + "com_nav_plugin_install": "Paigalda", + "com_nav_plugin_search": "Otsi pistikprogramme", + "com_nav_plugin_store": "Pistikprogrammide pood", + "com_nav_plugin_uninstall": "Eemalda", + "com_nav_plus_command": "+-käsk", + "com_nav_plus_command_description": "Lülita käsk \"+\" sisse/välja mitme vastuse seadistuse lisamiseks", + "com_nav_profile_picture": "Profiilipilt", + "com_nav_save_drafts": "Salvesta mustandid kohalikult", + "com_nav_scroll_button": "Keri nupu lõppu", + "com_nav_search_placeholder": "Otsi sõnumeid", + "com_nav_send_message": "Saada sõnum", + "com_nav_setting_account": "Konto", + "com_nav_setting_beta": "Beeta funktsioonid", + "com_nav_setting_chat": "Vestlus", + "com_nav_setting_data": "Andmekontroll", + "com_nav_setting_general": "Üldine", + "com_nav_setting_speech": "Kõne", + "com_nav_settings": "Seaded", + "com_nav_shared_links": "Jagatud lingid", + "com_nav_shared_links_manage": "Halda", + "com_nav_show_code": "Näita koodi alati, kui kasutatakse koodiinterpreteerijat", + "com_nav_show_thinking": "Ava mõtlemise rippmenüüd vaikimisi", + "com_nav_slash_command": "/-käsk", + "com_nav_slash_command_description": "Lülita käsk \"/\" sisse/välja, et valida klaviatuuri kaudu viipa", + "com_nav_source_buffer_error": "Viga heli taasesituse seadistamisel. Palun värskenda lehte.", + "com_nav_speech_cancel_error": "Heli taasesitust ei saa peatada. Võib-olla pead lehte värskendama.", + "com_nav_speech_to_text": "Kõnest tekstiks", + "com_nav_stop_generating": "Lõpeta genereerimine", + "com_nav_text_to_speech": "Tekst kõneks", + "com_nav_theme": "Teema", + "com_nav_theme_dark": "Tume", + "com_nav_theme_light": "Hele", + "com_nav_theme_system": "Süsteem", + "com_nav_tool_dialog": "Assistendi tööriistad", + "com_nav_tool_dialog_agents": "Agendi tööriistad", + "com_nav_tool_dialog_description": "Tööriistade valikute säilitamiseks tuleb assistent salvestada.", + "com_nav_tool_remove": "Eemalda", + "com_nav_tool_search": "Otsi tööriistu", + "com_nav_tts_init_error": "Teksti kõneks muutmise initsialiseerimine ebaõnnestus: {{0}}", + "com_nav_tts_unsupported_error": "Valitud mootori tekst kõneks muutmise funktsioon ei ole selles brauseris toetatud.", + "com_nav_user": "KASUTAJA", + "com_nav_user_msg_markdown": "Renderda kasutajasõnumid markdownina", + "com_nav_user_name_display": "Kuva kasutajanimi sõnumites", + "com_nav_voice_select": "Hääl", + "com_nav_voices_fetch_error": "Hääle valikuid ei saanud hankida. Palun kontrolli oma internetiühendust.", + "com_nav_welcome_agent": "Palun vali agent", + "com_nav_welcome_assistant": "Palun vali assistent", + "com_nav_welcome_message": "Kuidas ma saan täna sind aidata?", + "com_show_agent_settings": "Näita agendi seadeid", + "com_show_completion_settings": "Näita lõpetamise seadeid", + "com_show_examples": "Näita näiteid", + "com_sidepanel_agent_builder": "Agendi ehitaja", + "com_sidepanel_assistant_builder": "Assistendi ehitaja", + "com_sidepanel_attach_files": "Lisa faile", + "com_sidepanel_conversation_tags": "Järjehoidjad", + "com_sidepanel_hide_panel": "Peida paneel", + "com_sidepanel_manage_files": "Halda faile", + "com_sidepanel_parameters": "Parameetrid", + "com_sidepanel_select_agent": "Vali agent", + "com_sidepanel_select_assistant": "Vali assistent", + "com_ui_accept": "Nõustun", + "com_ui_add": "Lisa", + "com_ui_add_model_preset": "Lisa mudel või eelseadistus täiendava vastuse jaoks", + "com_ui_add_multi_conversation": "Lisa mitmevestlus", + "com_ui_admin": "Administraator", + "com_ui_admin_access_warning": "Administraatori juurdepääsu keelamine sellele funktsioonile võib põhjustada ootamatuid kasutajaliidese probleeme, mis nõuavad värskendamist. Kui see on salvestatud, on ainus viis taastada liideseseade kaudu librechat.yaml konfiguratsioonis, mis mõjutab kõiki rolle.", + "com_ui_admin_settings": "Administraatori seaded", + "com_ui_advanced": "Täpsemad", + "com_ui_agent": "Agent", + "com_ui_agent_delete_error": "Agendi kustutamisel tekkis viga", + "com_ui_agent_deleted": "Agendi kustutamine õnnestus", + "com_ui_agent_duplicate_error": "Agendi dubleerimisel tekkis viga", + "com_ui_agent_duplicated": "Agendi dubleerimine õnnestus", + "com_ui_agent_editing_allowed": "Teised kasutajad saavad seda agenti juba muuta", + "com_ui_agent_shared_to_all": "Mmdagi peaks siia minema. Oli tühi", + "com_ui_agents": "Agendid", + "com_ui_agents_allow_create": "Luba agentide loomine", + "com_ui_agents_allow_share_global": "Luba agentide jagamine kõigile kasutajatele", + "com_ui_agents_allow_use": "Luba agentide kasutamine", + "com_ui_all": "kõik", + "com_ui_all_proper": "Kõik", + "com_ui_analyzing": "Analüüsimine", + "com_ui_analyzing_finished": "Analüüs lõpetatud", + "com_ui_api_key": "API võti", + "com_ui_archive": "Arhiveeri", + "com_ui_archive_error": "Vestluse arhiveerimine ebaõnnestus", + "com_ui_artifact_click": "Klõpsa avamiseks", + "com_ui_artifacts": "Artefaktid", + "com_ui_artifacts_toggle": "Lülita artefaktide kasutajaliides sisse/välja", + "com_ui_artifacts_toggle_agent": "Luba artefaktid", + "com_ui_ascending": "Asc", + "com_ui_assistant": "Assistent", + "com_ui_assistant_delete_error": "Assistendi kustutamisel tekkis viga", + "com_ui_assistant_deleted": "Assistendi kustutamine õnnestus", + "com_ui_assistants": "Assistendid", + "com_ui_assistants_output": "Assistentide väljund", + "com_ui_attach_error": "Faili ei saa lisada. Loo või vali vestlus või proovi lehte värskendada.", + "com_ui_attach_error_openai": "Assistendi faile ei saa teistele otspunktidele lisada", + "com_ui_attach_error_size": "Failisuuruse limiit on otspunkti jaoks ületatud:", + "com_ui_attach_error_type": "Otspunkti jaoks toetamatu failitüüp:", + "com_ui_attach_warn_endpoint": "Sobiva tööriista puudumisel võidakse mitte-assistendi faile ignoreerida", + "com_ui_attachment": "Manus", + "com_ui_auth_type": "Autentimise tüüp", + "com_ui_auth_url": "Autentimise URL", + "com_ui_authentication": "Autentimine", + "com_ui_authentication_type": "Autentimise tüüp", + "com_ui_avatar": "Avatar", + "com_ui_azure": "Azure", + "com_ui_back_to_chat": "Tagasi vestlusesse", + "com_ui_back_to_prompts": "Tagasi sisendite juurde", + "com_ui_basic": "Põhiline", + "com_ui_basic_auth_header": "Põhiline autentimise päis", + "com_ui_bearer": "Bearer", + "com_ui_bookmark_delete_confirm": "Oled sa kindel, et sa soovid selle järjehoidja kustutada?", + "com_ui_bookmarks": "Järjehoidjad", + "com_ui_bookmarks_add": "Lisa järjehoidjaid", + "com_ui_bookmarks_add_to_conversation": "Lisa praegusesse vestlusse", + "com_ui_bookmarks_count": "Hulk", + "com_ui_bookmarks_create_error": "Järjehoidja loomisel tekkis viga", + "com_ui_bookmarks_create_exists": "See järjehoidja on juba olemas", + "com_ui_bookmarks_create_success": "Järjehoidja loomine õnnestus", + "com_ui_bookmarks_delete": "Kustuta järjehoidja", + "com_ui_bookmarks_delete_error": "Järjehoidja kustutamisel tekkis viga", + "com_ui_bookmarks_delete_success": "Järjehoidja kustutamine õnnestus", + "com_ui_bookmarks_description": "Kirjeldus", + "com_ui_bookmarks_edit": "Muuda järjehoidjat", + "com_ui_bookmarks_filter": "Filtreeri järjehoidjaid...", + "com_ui_bookmarks_new": "Uus järjehoidja", + "com_ui_bookmarks_title": "Pealkiri", + "com_ui_bookmarks_update_error": "Järjehoidja uuendamisel tekkis viga", + "com_ui_bookmarks_update_success": "Järjehoidja uuendamine õnnestus", + "com_ui_bulk_delete_error": "Jagatud linkide kustutamine ebaõnnestus", + "com_ui_callback_url": "Tagasikutsumise URL", + "com_ui_cancel": "Tühista", + "com_ui_chat": "Vestlus", + "com_ui_chat_history": "Vestluse ajalugu", + "com_ui_clear": "Tühjenda", + "com_ui_clear_all": "Tühjenda kõik", + "com_ui_client_id": "Kliendi ID", + "com_ui_client_secret": "Kliendi saladus", + "com_ui_close": "Sulge", + "com_ui_close_menu": "Sulge menüü", + "com_ui_code": "Kood", + "com_ui_collapse_chat": "Ahenda vestlus", + "com_ui_command_placeholder": "Valikuline: sisesta sisendi jaoks käsk või kasutatakse nime", + "com_ui_command_usage_placeholder": "Vali sisend käsu või nime järgi", + "com_ui_confirm_action": "Kinnita tegevus", + "com_ui_confirm_admin_use_change": "Selle seadistuse muutmine blokeerib juurdepääsu administraatoritele, sealhulgas sinule endale. Oled sa kindel, et sa soovid jätkata?", + "com_ui_confirm_change": "Kinnita muudatus", + "com_ui_context": "Kontekst", + "com_ui_continue": "Jätka", + "com_ui_controls": "Juhtelemendid", + "com_ui_copied": "Kopeeritud!", + "com_ui_copied_to_clipboard": "Kopeeritud lõikepuhvrisse", + "com_ui_copy_code": "Kopeeri kood", + "com_ui_copy_link": "Kopeeri link", + "com_ui_copy_to_clipboard": "Kopeeri lõikepuhvrisse", + "com_ui_create": "Loo", + "com_ui_create_link": "Loo link", + "com_ui_create_prompt": "Loo sisend", + "com_ui_currently_production": "Praegu tootmises", + "com_ui_custom": "Kohandatud", + "com_ui_custom_header_name": "Kohandatud päise nimi", + "com_ui_custom_prompt_mode": "Kohandatud viiparežiim", + "com_ui_dashboard": "Armatuurlaud", + "com_ui_date": "Kuupäev", + "com_ui_date_april": "Aprill", + "com_ui_date_august": "August", + "com_ui_date_december": "Detsember", + "com_ui_date_february": "Veebruar", + "com_ui_date_january": "Jaanuar", + "com_ui_date_july": "Juuli", + "com_ui_date_june": "Juuni", + "com_ui_date_march": "Märts", + "com_ui_date_may": "Mai", + "com_ui_date_november": "November", + "com_ui_date_october": "Oktoober", + "com_ui_date_previous_30_days": "Eelmised 30 päeva", + "com_ui_date_previous_7_days": "Eelmised 7 päeva", + "com_ui_date_september": "September", + "com_ui_date_today": "Täna", + "com_ui_date_yesterday": "Eile", + "com_ui_decline": "Ma ei nõustu", + "com_ui_default_post_request": "Vaikimisi (POST päring)", + "com_ui_delete": "Kustuta", + "com_ui_delete_action": "Kustuta tegevus", + "com_ui_delete_action_confirm": "Oled sa kindel, et sa soovid selle tegevuse kustutada?", + "com_ui_delete_agent_confirm": "Oled sa kindel, et sa soovid selle agendi kustutada?", + "com_ui_delete_assistant_confirm": "Oled sa kindel, et sa soovid selle assistendi kustutada? Seda ei saa tagasi võtta.", + "com_ui_delete_confirm": "See kustutab", + "com_ui_delete_confirm_prompt_version_var": "See kustutab valitud versiooni \"{{0}}\" jaoks. Kui muid versioone ei eksisteeri, kustutatakse sisend.", + "com_ui_delete_conversation": "Kustuta vestlus?", + "com_ui_delete_prompt": "Kustuta sisend?", + "com_ui_delete_shared_link": "Kustuta jagatud link?", + "com_ui_delete_tool": "Kustuta tööriist", + "com_ui_delete_tool_confirm": "Oled sa kindel, et sa soovid selle tööriista kustutada?", + "com_ui_descending": "Desc", + "com_ui_description": "Kirjeldus", + "com_ui_description_placeholder": "Valikuline: sisesta sisendi jaoks kuvatav kirjeldus", + "com_ui_download": "Laadi alla", + "com_ui_download_artifact": "Laadi artefakt alla", + "com_ui_download_error": "Viga faili allalaadimisel. Fail võib olla kustutatud.", + "com_ui_drag_drop": "Midagi peaks siia minema. Oli tühi", + "com_ui_dropdown_variables": "Rippmenüü muutujad:", + "com_ui_dropdown_variables_info": "Loo sisendite jaoks kohandatud rippmenüüd: `{{muutuja_nimi:valik1|valik2|valik3}}`", + "com_ui_duplicate": "Dubleeri", + "com_ui_duplication_error": "Vestluse dubleerimisel tekkis viga", + "com_ui_duplication_processing": "Vestlust dubleeritakse...", + "com_ui_duplication_success": "Vestluse dubleerimine õnnestus", + "com_ui_edit": "Muuda", + "com_ui_endpoint": "Otspunkt", + "com_ui_endpoint_menu": "LLM otspunkti menüü", + "com_ui_endpoints_available": "Saadaolevad otspunktid", + "com_ui_enter": "Sisesta", + "com_ui_enter_api_key": "Sisesta API võti", + "com_ui_enter_openapi_schema": "Sisesta siia oma OpenAPI skeem", + "com_ui_enter_var": "Sisesta {{0}}", + "com_ui_error": "Viga", + "com_ui_error_connection": "Viga serveriga ühendamisel, proovi lehte värskendada.", + "com_ui_error_save_admin_settings": "Administraatori seadete salvestamisel tekkis viga.", + "com_ui_examples": "Näited", + "com_ui_export_convo_modal": "Ekspordi vestluse modaal", + "com_ui_field_required": "See väli on kohustuslik", + "com_ui_filter_prompts": "Filtreeri sisendid", + "com_ui_filter_prompts_name": "Filtreeri sisendeid nime järgi", + "com_ui_fork": "Hargne", + "com_ui_fork_all_target": "Kaasa kõik siia/siit", + "com_ui_fork_branches": "Kaasa seotud harud", + "com_ui_fork_change_default": "Vaikimisi hargnemise valik", + "com_ui_fork_default": "Kasuta vaikimisi hargnemise valikut", + "com_ui_fork_error": "Vestluse hargnemisel tekkis viga", + "com_ui_fork_from_message": "Vali hargnemise valik", + "com_ui_fork_info_1": "Kasuta seda seadistust sõnumite hargnemiseks soovitud käitumisega.", + "com_ui_fork_info_2": "\"Hargnemine\" viitab uue vestluse loomisele, mis algab/lõpeb praeguse vestluse konkreetsetest sõnumitest, luues koopia vastavalt valitud valikutele.", + "com_ui_fork_info_3": "\"Sihtsõnum\" viitab kas sõnumile, millest see hüpikaken avati, või, kui märgid \"{{0}}\", vestluse viimasele sõnumile.", + "com_ui_fork_info_branches": "See valik hargneb nähtavad sõnumid koos seotud harudega; teisisõnu, otsene tee sihtsõnumini, sealhulgas harud mööda teed.", + "com_ui_fork_info_remember": "Märgi see, et jätta meelde valitud valikud edaspidiseks kasutamiseks, muutes vestluste hargnemise eelistatud viisil kiiremaks.", + "com_ui_fork_info_start": "Kui see on märgitud, algab hargnemine sellest sõnumist vestluse viimase sõnumini vastavalt ülalvalitud käitumisele.", + "com_ui_fork_info_target": "See valik hargneb kõik sõnumid, mis viivad sihtsõnumini, kaasa arvatud selle naabrid; teisisõnu, kõik sõnumiharud, olenemata sellest, kas need on nähtavad või samal teel, on kaasatud.", + "com_ui_fork_info_visible": "See valik hargneb ainult nähtavad sõnumid; teisisõnu, otsene tee sihtsõnumini, ilma harudeta.", + "com_ui_fork_processing": "Vestlust hargnetakse...", + "com_ui_fork_remember": "Jäta meelde", + "com_ui_fork_remember_checked": "Sinu valik jäetakse pärast kasutamist meelde. Muuda seda igal ajal seadetes.", + "com_ui_fork_split_target": "Alusta hargnemist siit", + "com_ui_fork_split_target_setting": "Alusta vaikimisi sihtsõnumist hargnemist", + "com_ui_fork_success": "Vestluse hargnemine õnnestus", + "com_ui_fork_visible": "Ainult nähtavad sõnumid", + "com_ui_global_group": "Midagi peaks siia minema. Oli tühi", + "com_ui_go_back": "Mine tagasi", + "com_ui_go_to_conversation": "Mine vestlusesse", + "com_ui_happy_birthday": "Mul on 1. sünnipäev!", + "com_ui_hide_qr": "Peida QR-kood", + "com_ui_host": "Host", + "com_ui_image_gen": "Pildi genereerimine", + "com_ui_import_conversation": "Impordi", + "com_ui_import_conversation_error": "Vestluste importimisel tekkis viga", + "com_ui_import_conversation_file_type_error": "Toetamatu imporditüüp", + "com_ui_import_conversation_info": "Impordi vestlused JSON-failist", + "com_ui_import_conversation_success": "Vestluste importimine õnnestus", + "com_ui_include_shadcnui": "Kaasa shadcn/ui komponentide juhised", + "com_ui_include_shadcnui_agent": "Kaasa shadcn/ui juhised", + "com_ui_input": "Sisend", + "com_ui_instructions": "Juhised", + "com_ui_latest_footer": "Igaühele oma AI.", + "com_ui_latest_production_version": "Viimane tootmisversioon", + "com_ui_latest_version": "Viimane versioon", + "com_ui_librechat_code_api_key": "Hangi oma LibreChati koodiinterpreteerimise API võti", + "com_ui_librechat_code_api_subtitle": "Turvaline. Mitmekeelne. Sisend-/väljundfailid.", + "com_ui_librechat_code_api_title": "Käivita AI koodi", + "com_ui_llm_menu": "LLM menüü", + "com_ui_llms_available": "Saadaolevad LLM-id", + "com_ui_locked": "Lukus", + "com_ui_logo": "{{0}} logo", + "com_ui_manage": "Halda", + "com_ui_max_tags": "Maksimaalne lubatud arv on {{0}}, kasutades viimaseid väärtusi.", + "com_ui_mention": "Maini otspunkti, assistenti või eelseadistust, et sellele kiiresti üle minna", + "com_ui_min_tags": "Rohkem väärtusi ei saa eemaldada, vaja on vähemalt {{0}}.", + "com_ui_model": "Mudel", + "com_ui_model_parameters": "Mudeli parameetrid", + "com_ui_more_info": "Rohkem infot", + "com_ui_my_prompts": "Minu sisendid", + "com_ui_name": "Nimi", + "com_ui_new_chat": "Uus vestlus", + "com_ui_next": "Järgmine", + "com_ui_no": "Ei", + "com_ui_no_bookmarks": "Tundub, et sul pole veel järjehoidjaid. Klõpsa vestlusele ja lisa uus", + "com_ui_no_category": "Kategooriat pole", + "com_ui_no_changes": "Uuendamiseks pole muudatusi", + "com_ui_no_data": "midagi peaks siia minema. Oli tühi", + "com_ui_no_terms_content": "Kuvamiseks puudub kasutustingimuste sisu", + "com_ui_no_valid_items": "midagi peaks siia minema. Oli tühi", + "com_ui_none": "Puudub", + "com_ui_none_selected": "Ühtegi pole valitud", + "com_ui_nothing_found": "Midagi ei leitud", + "com_ui_oauth": "OAuth", + "com_ui_of": "kohta", + "com_ui_off": "Väljas", + "com_ui_on": "Sees", + "com_ui_openai": "OpenAI", + "com_ui_page": "Leht", + "com_ui_prev": "Eelmine", + "com_ui_preview": "Eelvaade", + "com_ui_privacy_policy": "Privaatsuspoliitika", + "com_ui_privacy_policy_url": "Privaatsuspoliitika URL", + "com_ui_prompt": "Sisend", + "com_ui_prompt_already_shared_to_all": "See sisend on juba kõigile kasutajatele jagatud", + "com_ui_prompt_name": "Sisendi nimi", + "com_ui_prompt_name_required": "Sisendi nimi on kohustuslik", + "com_ui_prompt_preview_not_shared": "Autor ei ole selle sisendi jaoks koostööd lubanud.", + "com_ui_prompt_text": "Tekst", + "com_ui_prompt_text_required": "Tekst on kohustuslik", + "com_ui_prompt_update_error": "Sisendi uuendamisel tekkis viga", + "com_ui_prompts": "Sisendid", + "com_ui_prompts_allow_create": "Luba sisendite loomine", + "com_ui_prompts_allow_share_global": "Luba sisendite jagamine kõigile kasutajatele", + "com_ui_prompts_allow_use": "Luba sisendite kasutamine", + "com_ui_provider": "Teenusepakkuja", + "com_ui_read_aloud": "Loe valjusti", + "com_ui_refresh_link": "Värskenda linki", + "com_ui_regenerate": "Genereeri uuesti", + "com_ui_region": "Piirkond", + "com_ui_rename": "Nimeta ümber", + "com_ui_rename_prompt": "Nimeta sisend ümber", + "com_ui_requires_auth": "Vajab autentimist", + "com_ui_reset_var": "Lähtesta {{0}}", + "com_ui_result": "Tulemus", + "com_ui_revoke": "Tühista", + "com_ui_revoke_info": "Tühista kõik kasutaja esitatud mandaadid", + "com_ui_revoke_key_confirm": "Oled sa kindel, et sa soovid selle võtme tühistada?", + "com_ui_revoke_key_endpoint": "Tühista võti {{0}} jaoks", + "com_ui_revoke_keys": "Tühista võtmed", + "com_ui_revoke_keys_confirm": "Oled sa kindel, et sa soovid kõik võtmed tühistada?", + "com_ui_role_select": "Roll", + "com_ui_run_code": "Käivita kood", + "com_ui_run_code_error": "Koodi käivitamisel tekkis viga", + "com_ui_save": "Salvesta", + "com_ui_save_submit": "Salvesta ja esita", + "com_ui_saved": "Salvestatud!", + "com_ui_schema": "Skeem", + "com_ui_scope": "Ulatus", + "com_ui_search": "Otsi", + "com_ui_select": "Vali", + "com_ui_select_file": "Vali fail", + "com_ui_select_model": "Vali mudel", + "com_ui_select_provider": "Vali teenusepakkuja", + "com_ui_select_provider_first": "Vali esmalt teenusepakkuja", + "com_ui_select_region": "Vali piirkond", + "com_ui_select_search_model": "Otsi mudelit nime järgi", + "com_ui_select_search_plugin": "Otsi pistikprogrammi nime järgi", + "com_ui_select_search_provider": "Otsi teenusepakkujat nime järgi", + "com_ui_select_search_region": "Otsi piirkonda nime järgi", + "com_ui_share": "Jaga", + "com_ui_share_create_message": "Sinu nimi ja kõik sõnumid, mille sa pärast jagamist lisad, jäävad privaatseks.", + "com_ui_share_delete_error": "Jagatud lingi kustutamisel tekkis viga", + "com_ui_share_error": "Vestluslingi jagamisel tekkis viga", + "com_ui_share_form_description": "Midagi peaks siia minema. Oli tühi", + "com_ui_share_link_to_chat": "Jaga linki vestlusele", + "com_ui_share_to_all_users": "Jaga kõigile kasutajatele", + "com_ui_share_update_message": "Sinu nimi, kohandatud juhised ja kõik sõnumid, mille sa pärast jagamist lisad, jäävad privaatseks.", + "com_ui_share_var": "Jaga {{0}}", + "com_ui_shared_link_bulk_delete_success": "Jagatud linkide kustutamine õnnestus", + "com_ui_shared_link_delete_success": "Jagatud lingi kustutamine õnnestus", + "com_ui_shared_link_not_found": "Jagatud linki ei leitud", + "com_ui_shared_prompts": "Jagatud sisendid", + "com_ui_show_all": "Näita kõiki", + "com_ui_show_qr": "Näita QR-koodi", + "com_ui_sign_in_to_domain": "Logi sisse {{0}}", + "com_ui_simple": "Lihtne", + "com_ui_size": "Suurus", + "com_ui_special_variables": "Erilised muutujad:", + "com_ui_special_variables_info": "Kasuta `{{praegune_kuupäev}}` praeguse kuupäeva jaoks ja `{{praegune_kasutaja}}` oma konto nime jaoks.", + "com_ui_speech_while_submitting": "Kõnet ei saa esitada, kui vastust genereeritakse", + "com_ui_stop": "Peata", + "com_ui_storage": "Salvestusruum", + "com_ui_submit": "Esita", + "com_ui_temporary_chat": "Ajutine vestlus", + "com_ui_terms_and_conditions": "Kasutustingimused", + "com_ui_terms_of_service": "Teenuse tingimused", + "com_ui_thinking": "Mõtleb...", + "com_ui_thoughts": "Mõtted", + "com_ui_token_exchange_method": "Märgi vahetamise meetod", + "com_ui_token_url": "Märgi URL", + "com_ui_tools": "Tööriistad", + "com_ui_unarchive": "Arhiveeri lahti", + "com_ui_unarchive_error": "Vestluse arhiveerimine lahti ebaõnnestus", + "com_ui_unknown": "Tundmatu", + "com_ui_update": "Uuenda", + "com_ui_upload": "Laadi üles", + "com_ui_upload_code_files": "Laadi üles koodiinterpreteerija jaoks", + "com_ui_upload_delay": "Faili \"{{0}}\" üleslaadimine võtab oodatust kauem aega. Palun oota, kuni faili indekseerimine hankimiseks lõpeb.", + "com_ui_upload_error": "Faili üleslaadimisel tekkis viga", + "com_ui_upload_file_search": "Laadi üles failiotsingu jaoks", + "com_ui_upload_files": "Laadi faile üles", + "com_ui_upload_image": "Laadi pilt üles", + "com_ui_upload_image_input": "Laadi pilt üles", + "com_ui_upload_invalid": "Fail on üleslaadimiseks vigane. Peab olema pilt, mis ei ületa piirangut", + "com_ui_upload_invalid_var": "Fail on üleslaadimiseks vigane. Peab olema pilt, mis ei ületa {{0}} MB", + "com_ui_upload_success": "Faili üleslaadimine õnnestus", + "com_ui_upload_type": "Vali üleslaadimise tüüp", + "com_ui_use_micrphone": "Kasuta mikrofoni", + "com_ui_use_prompt": "Kasuta sisendit", + "com_ui_variables": "Muutujad", + "com_ui_variables_info": "Kasuta oma tekstis topelt sulgusid, et luua muutujaid, nt `{{näidismuutuja}}`, et hiljem sisendi kasutamisel täita.", + "com_ui_version_var": "Versioon {{0}}", + "com_ui_versions": "Versioonid", + "com_ui_view_source": "Vaata algset vestlust", + "com_ui_yes": "Jah", + "com_ui_zoom": "Suumi", + "com_user_message": "Sina", + "com_warning_resubmit_unsupported": "AI sõnumi uuesti esitamine pole selle otspunkti jaoks toetatud." +} \ No newline at end of file diff --git a/client/src/locales/zh/translation.json b/client/src/locales/zh/translation.json index a55ec579ea..36f59cb4cd 100644 --- a/client/src/locales/zh/translation.json +++ b/client/src/locales/zh/translation.json @@ -18,13 +18,16 @@ "com_agents_not_available": "助手不可用", "com_agents_search_name": "根据名称搜索代理", "com_agents_update_error": "更新代理时出现错误。", + "com_assistants_action_attempt": "助理想要和 {{0}} 对话", "com_assistants_actions": "操作", "com_assistants_actions_disabled": "您需要先创建助手,然后才能添加操作。", "com_assistants_actions_info": "让您的助手通过 API 检索信息或执行操作", "com_assistants_add_actions": "添加操作", "com_assistants_add_tools": "添加工具", + "com_assistants_allow_sites_you_trust": "只允许您信任的网站", "com_assistants_append_date": "添加当前日期和时间", "com_assistants_append_date_tooltip": "启用后,当前客户的日期和时间将附加到助手的系统指令中。", + "com_assistants_attempt_info": "助理想要发送以下内容:", "com_assistants_available_actions": "可用操作", "com_assistants_capabilities": "功能", "com_assistants_code_interpreter": "代码解释器", @@ -46,7 +49,7 @@ "com_assistants_image_vision": "识图", "com_assistants_instructions_placeholder": "助手使用的系统指令", "com_assistants_knowledge": "知识", - "com_assistants_knowledge_disabled": "必须创建助手,且启用并保存代码解释器或检索,才能将文件作为知识上传。", + "com_assistants_knowledge_disabled": "必须先创建助手,且启用并保存代码解释器或检索,才能将文件作为知识上传。", "com_assistants_knowledge_info": "如果您在 “知识” 中上传文件,与助手的对话可能包括文件内容。", "com_assistants_max_starters_reached": "已达到对话启动器的最大数量", "com_assistants_name_placeholder": "(选填)助手的名称", @@ -59,6 +62,7 @@ "com_assistants_update_error": "更新助手时出现错误。", "com_assistants_update_success": "更新成功", "com_auth_already_have_account": "已有账户?", + "com_auth_apple_login": "使用Apple账户登录", "com_auth_back_to_login": "返回登录", "com_auth_click": "点击", "com_auth_click_here": "点击这里", @@ -126,11 +130,11 @@ "com_download_expires": "(点击此处下载 - {{0}}后过期)", "com_endpoint": "渠道", "com_endpoint_agent": "代理", - "com_endpoint_agent_model": "代理模型(推荐: GPT-3.5)", + "com_endpoint_agent_model": "Agent 模型(推荐: GPT-3.5)", "com_endpoint_agent_placeholder": "请选择代理", "com_endpoint_ai": "人工智能", "com_endpoint_anthropic_maxoutputtokens": "响应中可以生成的最大令牌数。指定较低的值以获得较短的响应,指定较高的值以获得较长的响应。注意:模型可能会在达到此最大值之前停止。", - "com_endpoint_anthropic_prompt_cache": "提示词缓存允许在 API 调用中重用大型上下文或指令,从而降低成本和延迟", + "com_endpoint_anthropic_prompt_cache": "提示词缓存允许在 API 调用中复用大型上下文或指令,从而降低成本和延迟", "com_endpoint_anthropic_temp": "值介于 0 到 1 之间。对于分析性/选择性任务,值应更接近 0;对于创造性和生成性任务,值应更接近 1。我们建议更改该参数或 Top P,但不要同时更改这两个参数。", "com_endpoint_anthropic_topk": "top-k 会改变模型选择输出词元的方式。top-k 为 1 意味着所选词是模型词汇中概率最大的(也称为贪心解码),而 top-k 为 3 意味着下一个词是从 3 个概率最大的词中选出的(使用随机性)。", "com_endpoint_anthropic_topp": "top-p(核采样)会改变模型选择输出词元的方式。从概率最大的 K(参见topK参数)向最小的 K 选择,直到它们的概率之和等于 top-p 值。", @@ -228,6 +232,7 @@ "com_endpoint_prompt_prefix_assistants": "额外指令", "com_endpoint_prompt_prefix_assistants_placeholder": "在助手的主要指令之上设置额外的指令或上下文。如果为空,则忽略。", "com_endpoint_prompt_prefix_placeholder": "自定义指令和上下文,默认为空。", + "com_endpoint_reasoning_effort": "推理强度", "com_endpoint_save_as_preset": "保存为预设", "com_endpoint_search": "按名称搜索渠道", "com_endpoint_set_custom_name": "设置一个自定义名称,以便您检索此预设", @@ -329,6 +334,7 @@ "com_nav_info_include_shadcnui": "启用后,将包含使用 shadcn/ui 组件的指令。shadcn/ui 是一组使用 Radix UI 和 Tailwind CSS 构建的可重复使用的组件。注意:这些指令较长,仅在您需要向 LLM 提供正确的导入和组件信息时才应启用。有关这些组件的更多信息,请访问:https://ui.shadcn.com/", "com_nav_info_latex_parsing": "启用后,消息中的 LaTeX 代码将呈现为数学公式。如果您不需要 LaTeX 渲染,禁用此功能可能会提高性能。", "com_nav_info_save_draft": "启用后,您在聊天表单中输入的文本和附件将自动本地保存为草稿。即使您重新加载页面或切换到不同的对话,这些草稿也将可用。草稿存储在您设备的本地,并在消息发送后删除。", + "com_nav_info_show_thinking": "启用后,聊天界面将默认展开“深度思考下拉框”,让您能够实时查看人工智能的推理过程。禁用后,深度思考下拉框将默认保持关闭状态,以提供更简洁、更流畅的界面。", "com_nav_info_user_name_display": "启用后,发送者的用户名将显示在您发送的每条消息上方。禁用后,您只会在自己的消息上方看到 “您”。", "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "自动检测语言", @@ -371,6 +377,7 @@ "com_nav_plus_command_description": "切换至命令 “+” 以添加多响应设置", "com_nav_profile_picture": "个人资料头像", "com_nav_save_drafts": "在本地保存草稿", + "com_nav_scroll_button": "滚动到末尾按钮", "com_nav_search_placeholder": "搜索消息", "com_nav_send_message": "发送消息", "com_nav_setting_account": "账户", @@ -458,10 +465,14 @@ "com_ui_attach_error_type": "渠道不支持的文件类型:", "com_ui_attach_warn_endpoint": "不兼容的工具可能会忽略非助手文件", "com_ui_attachment": "附件", + "com_ui_auth_type": "认证类型", + "com_ui_auth_url": "认证 URL", "com_ui_authentication": "认证", + "com_ui_authentication_type": "认证类型", "com_ui_avatar": "头像", "com_ui_back_to_chat": "返回对话", "com_ui_back_to_prompts": "返回提示词", + "com_ui_basic": "基本", "com_ui_bookmark_delete_confirm": "您确定要删除此书签吗?", "com_ui_bookmarks": "书签", "com_ui_bookmarks_add": "添加书签", @@ -480,17 +491,22 @@ "com_ui_bookmarks_title": "标题", "com_ui_bookmarks_update_error": "更新书签时出现错误", "com_ui_bookmarks_update_success": "书签更新成功", + "com_ui_bulk_delete_error": "删除分享链接时失败", + "com_ui_callback_url": "回调 URL", "com_ui_cancel": "取消", "com_ui_chat": "对话", "com_ui_chat_history": "对话历史", "com_ui_clear": "清除", "com_ui_clear_all": "全部清除", "com_ui_close": "关闭", + "com_ui_close_menu": "关闭菜单", "com_ui_code": "代码", "com_ui_collapse_chat": "收起聊天", "com_ui_command_placeholder": "可选:输入提示词的命令,否则将使用名称", "com_ui_command_usage_placeholder": "通过命令或名称选择提示词", "com_ui_confirm_action": "确认执行", + "com_ui_confirm_admin_use_change": "更改此设置将阻止包括您的在内的所有管理员的权限。您确定要继续吗?", + "com_ui_confirm_change": "确认更改", "com_ui_context": "上下文", "com_ui_continue": "继续", "com_ui_controls": "管理", @@ -502,6 +518,8 @@ "com_ui_create": "创建", "com_ui_create_link": "创建链接", "com_ui_create_prompt": "创建提示词", + "com_ui_custom": "自定义", + "com_ui_custom_header_name": "自定义 Header 名称", "com_ui_custom_prompt_mode": "自定义提示词模式", "com_ui_dashboard": "仪表板", "com_ui_date": "日期", @@ -522,6 +540,7 @@ "com_ui_date_today": "今天", "com_ui_date_yesterday": "昨天", "com_ui_decline": "我不接受", + "com_ui_default_post_request": "默认 (POST 请求)", "com_ui_delete": "删除", "com_ui_delete_action": "删除操作", "com_ui_delete_action_confirm": "您确定要删除此操作吗?", @@ -537,6 +556,8 @@ "com_ui_descending": "降序", "com_ui_description": "描述", "com_ui_description_placeholder": "可选:输入要显示的提示词描述", + "com_ui_download": "下载", + "com_ui_download_artifact": "下载 Artifact", "com_ui_download_error": "下载文件时出现错误,该文件可能已被删除。", "com_ui_dropdown_variables": "下拉变量:", "com_ui_dropdown_variables_info": "为您的提示词创建自定义下拉菜单:`{{variable_name:option1|option2|option3}}`", @@ -558,6 +579,7 @@ "com_ui_examples": "示例", "com_ui_export_convo_modal": "导出对话窗口", "com_ui_field_required": "此字段为必填项", + "com_ui_filter_prompts": "过滤 Prompts", "com_ui_filter_prompts_name": "根据名称筛选提示词", "com_ui_fork": "分叉", "com_ui_fork_all_target": "包含所有目标", @@ -581,8 +603,10 @@ "com_ui_fork_split_target_setting": "默认以目标消息开始分叉", "com_ui_fork_success": "对话分叉成功", "com_ui_fork_visible": "仅可见消息", + "com_ui_go_back": "返回", "com_ui_go_to_conversation": "转到对话", "com_ui_happy_birthday": "这是我的第一个生日!", + "com_ui_hide_qr": "隐藏二维码", "com_ui_host": "主机", "com_ui_image_gen": "图片生成", "com_ui_import_conversation": "导入", @@ -594,6 +618,8 @@ "com_ui_input": "输入", "com_ui_instructions": "指令", "com_ui_latest_footer": "Every AI for Everyone.", + "com_ui_latest_production_version": "最新的生产版本", + "com_ui_latest_version": "最新版本", "com_ui_librechat_code_api_key": "获取您的 LibreChat 代码解释器 API 密钥", "com_ui_librechat_code_api_subtitle": "安全可靠。多语言支持。文件输入/输出。", "com_ui_librechat_code_api_title": "运行AI代码", @@ -644,6 +670,8 @@ "com_ui_regenerate": "重新生成", "com_ui_region": "区域", "com_ui_rename": "重命名", + "com_ui_rename_prompt": "重命名 Prompt", + "com_ui_requires_auth": "需要认证", "com_ui_reset_var": "重置{{0}}", "com_ui_result": "结果", "com_ui_revoke": "撤销", @@ -659,6 +687,7 @@ "com_ui_save_submit": "保存并提交", "com_ui_saved": "保存成功!", "com_ui_schema": "架构", + "com_ui_search": "搜索", "com_ui_select": "选择", "com_ui_select_file": "选择文件", "com_ui_select_model": "模型选择", @@ -673,13 +702,18 @@ "com_ui_share_create_message": "您的姓名以及您在共享后添加的任何消息将保持私密。", "com_ui_share_delete_error": "删除共享链接时出现错误", "com_ui_share_error": "共享聊天链接时发生错误", + "com_ui_share_form_description": "未找到描述。", "com_ui_share_link_to_chat": "共享链接到聊天", "com_ui_share_to_all_users": "共享给所有用户", "com_ui_share_update_message": "您的姓名、自定义指令以及您在共享后添加的任何消息将保持私密。", "com_ui_share_var": "共享 {{0}}", + "com_ui_shared_link_bulk_delete_success": "成功删除分享链接", + "com_ui_shared_link_delete_success": "成功删除分享链接", "com_ui_shared_link_not_found": "未找到共享链接", "com_ui_shared_prompts": "共享提示词", "com_ui_show_all": "展开全部", + "com_ui_show_qr": "显示二维码", + "com_ui_sign_in_to_domain": "登录到 {{0}}", "com_ui_simple": "基本", "com_ui_size": "大小", "com_ui_special_variables": "特殊变量:", @@ -688,8 +722,11 @@ "com_ui_stop": "停止", "com_ui_storage": "存储", "com_ui_submit": "提交", + "com_ui_temporary_chat": "临时对话", "com_ui_terms_and_conditions": "条款和条件", "com_ui_terms_of_service": "服务政策", + "com_ui_thinking": "思考中", + "com_ui_thoughts": "思考内容", "com_ui_tools": "工具", "com_ui_unarchive": "取消归档", "com_ui_unarchive_error": "取消归档对话失败", @@ -713,6 +750,7 @@ "com_ui_variables_info": "在您的文本中使用双大括号创建变量,例如 `{{example variable}}`,以便在使用提示词时填充。", "com_ui_version_var": "版本 {{0}}", "com_ui_versions": "版本", + "com_ui_view_source": "查看来源对话", "com_ui_yes": "是的", "com_ui_zoom": "缩放", "com_user_message": "您", From 28fe1218c55ff65bffeb215642e9d6b07e802395 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Thu, 13 Feb 2025 10:07:40 -0500 Subject: [PATCH 03/23] =?UTF-8?q?=F0=9F=94=A7=20fix:=20Ariakit=20Combobox?= =?UTF-8?q?=20Virtualization=20(#5851)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ariakit Combobox was not working well with several virtualization libraries as automated focus management was conflicting with scrolling/styling required of other virtualization methods. The entire strategy was replaced using experimental ariakit virtualization component `SelectRenderer` Performance of component was also improved as a result of latest ariakit lib changes --- client/package.json | 3 +- client/src/components/ui/ControlCombobox.tsx | 192 +++++++++---------- package-lock.json | 52 +++-- 3 files changed, 119 insertions(+), 128 deletions(-) diff --git a/client/package.json b/client/package.json index 993cf30071..0a0379b6f1 100644 --- a/client/package.json +++ b/client/package.json @@ -28,7 +28,8 @@ }, "homepage": "https://librechat.ai", "dependencies": { - "@ariakit/react": "^0.4.11", + "@ariakit/react": "^0.4.15", + "@ariakit/react-core": "^0.4.15", "@codesandbox/sandpack-react": "^2.19.10", "@dicebear/collection": "^7.0.4", "@dicebear/core": "^7.0.4", diff --git a/client/src/components/ui/ControlCombobox.tsx b/client/src/components/ui/ControlCombobox.tsx index d4a95f25d7..9c4fd16c30 100644 --- a/client/src/components/ui/ControlCombobox.tsx +++ b/client/src/components/ui/ControlCombobox.tsx @@ -1,10 +1,10 @@ +import { Search } from 'lucide-react'; import * as Ariakit from '@ariakit/react'; import { matchSorter } from 'match-sorter'; -import { AutoSizer, List } from 'react-virtualized'; -import { startTransition, useMemo, useState, useEffect, useRef, memo } from 'react'; -import { cn } from '~/utils'; +import { useMemo, useState, useRef, memo, useEffect } from 'react'; +import { SelectRenderer } from '@ariakit/react-core/select/select-renderer'; import type { OptionWithIcon } from '~/common'; -import { Search } from 'lucide-react'; +import { cn } from '~/utils'; interface ControlComboboxProps { selectedValue: string; @@ -35,11 +35,33 @@ function ControlCombobox({ const buttonRef = useRef(null); const [buttonWidth, setButtonWidth] = useState(null); + const getItem = (option: OptionWithIcon) => ({ + id: `item-${option.value}`, + value: option.value as string | undefined, + label: option.label, + icon: option.icon, + }); + + const combobox = Ariakit.useComboboxStore({ + defaultItems: items.map(getItem), + resetValueOnHide: true, + value: searchValue, + setValue: setSearchValue, + }); + + const select = Ariakit.useSelectStore({ + combobox, + defaultItems: items.map(getItem), + value: selectedValue, + setValue, + }); + const matches = useMemo(() => { - return matchSorter(items, searchValue, { + const filteredItems = matchSorter(items, searchValue, { keys: ['value', 'label'], baseSort: (a, b) => (a.index < b.index ? -1 : 1), }); + return filteredItems.map(getItem); }, [searchValue, items]); useEffect(() => { @@ -48,104 +70,74 @@ function ControlCombobox({ } }, [isCollapsed]); - const rowRenderer = ({ - index, - key, - style, - }: { - index: number; - key: string; - style: React.CSSProperties; - }) => { - const item = matches[index]; - return ( - } - style={style} - > - {item.icon != null && ( -
- {item.icon} -
- )} - {item.label} -
- ); - }; - return (
- { - startTransition(() => { - setSearchValue(value); - }); - }} + + {ariaLabel} + + - - {ariaLabel} - - {SelectIcon != null && ( -
- {SelectIcon} -
- )} - {!isCollapsed && ( - - {displayValue ?? selectPlaceholder} - - )} -
- -
-
- - -
-
-
- - {({ width }) => ( - - )} - -
-
-
-
+ {SelectIcon != null && ( +
+ {SelectIcon} +
+ )} + {!isCollapsed && ( + {displayValue ?? selectPlaceholder} + )} + + +
+
+ + +
+
+
+ + + {({ value, icon, label, ...item }) => ( + } + > + {icon != null && ( +
+ {icon} +
+ )} + {label} +
+ )} +
+
+
+
); } diff --git a/package-lock.json b/package-lock.json index 77a282d807..3b72a2f66f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1598,7 +1598,8 @@ "version": "v0.7.7-rc1", "license": "ISC", "dependencies": { - "@ariakit/react": "^0.4.11", + "@ariakit/react": "^0.4.15", + "@ariakit/react-core": "^0.4.15", "@codesandbox/sandpack-react": "^2.19.10", "@dicebear/collection": "^7.0.4", "@dicebear/core": "^7.0.4", @@ -1711,6 +1712,22 @@ "vite-plugin-pwa": "^0.21.1" } }, + "client/node_modules/@ariakit/react": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.4.15.tgz", + "integrity": "sha512-0V2LkNPFrGRT+SEIiObx/LQjR6v3rR+mKEDUu/3tq7jfCZ+7+6Q6EMR1rFaK+XMkaRY1RWUcj/rRDWAUWnsDww==", + "dependencies": { + "@ariakit/react-core": "0.4.15" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ariakit" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "client/node_modules/@babel/code-frame": { "version": "7.26.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", @@ -2818,35 +2835,16 @@ } }, "node_modules/@ariakit/core": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.4.10.tgz", - "integrity": "sha512-mX3EabQbfVh5uTjsTJ3+gjj7KGdTNhIN0qZHJd5Z2iPUnKl9NBy23Lgu6PEskpVsKAZ3proirjguD7U9fKMs/A==", - "license": "MIT" - }, - "node_modules/@ariakit/react": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.4.11.tgz", - "integrity": "sha512-nLpPrmNcspqNhk4o+epsgeZfP1+Fkh4uIzNe5yrFkXolRkqHGKAxl4Hi82e0yxIBUbYbZIEwsZQQVceF1L6xrw==", - "license": "MIT", - "dependencies": { - "@ariakit/react-core": "0.4.11" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ariakit" - }, - "peerDependencies": { - "react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" - } + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.4.14.tgz", + "integrity": "sha512-hpzZvyYzGhP09S9jW1XGsU/FD5K3BKsH1eG/QJ8rfgEeUdPS7BvHPt5lHbOeJ2cMrRzBEvsEzLi1ivfDifHsVA==" }, "node_modules/@ariakit/react-core": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.4.11.tgz", - "integrity": "sha512-i6KedWhjZkNC7tMEKO0eNjjq2HRPiHyGaBS2x2VaWwzBepoYtjyvxRXyqLJ3gaiNdlwckN1TZsRDfD+viy13IQ==", - "license": "MIT", + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.4.15.tgz", + "integrity": "sha512-Up8+U97nAPJdyUh9E8BCEhJYTA+eVztWpHoo1R9zZfHd4cnBWAg5RHxEmMH+MamlvuRxBQA71hFKY/735fDg+A==", "dependencies": { - "@ariakit/core": "0.4.10", + "@ariakit/core": "0.4.14", "@floating-ui/dom": "^1.0.0", "use-sync-external-store": "^1.2.0" }, From 750b22d5f41259a9f8bb43df6f4baa3c98fb841e Mon Sep 17 00:00:00 2001 From: Ruben Talstra Date: Thu, 13 Feb 2025 20:20:30 +0100 Subject: [PATCH 04/23] =?UTF-8?q?=F0=9F=8C=8F=20i18n:=20fix=20Traditional?= =?UTF-8?q?=20Chinese=20Language=20Option=20(#5854)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/Nav/SettingsTabs/General/General.tsx | 2 +- client/src/locales/en/translation.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/Nav/SettingsTabs/General/General.tsx b/client/src/components/Nav/SettingsTabs/General/General.tsx index bf9ff3d8fb..5bbe0a6d61 100644 --- a/client/src/components/Nav/SettingsTabs/General/General.tsx +++ b/client/src/components/Nav/SettingsTabs/General/General.tsx @@ -52,7 +52,7 @@ export const LangSelector = ({ { value: 'auto', label: localize('com_nav_lang_auto') }, { value: 'en-US', label: localize('com_nav_lang_english') }, { value: 'zh-CN', label: localize('com_nav_lang_chinese') }, - { value: 'zh-TW', label: localize('com_nav_lang_traditionalchinese') }, + { value: 'zh-Hant', label: localize('com_nav_lang_traditional_chinese') }, { value: 'ar-EG', label: localize('com_nav_lang_arabic') }, { value: 'de-DE', label: localize('com_nav_lang_german') }, { value: 'es-ES', label: localize('com_nav_lang_spanish') }, diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index f17a596b5b..fc6b1800d9 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -360,7 +360,7 @@ "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", - "com_nav_lang_traditionalchinese": "繁體中文", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", "com_nav_language": "Language", From 52a6de2aa756564ffe12114ebfce0e7f93ea125c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 13 Feb 2025 16:06:25 -0500 Subject: [PATCH 05/23] =?UTF-8?q?=F0=9F=8C=8D=20i18n:=20Update=20translati?= =?UTF-8?q?on.json=20with=20latest=20translations=20(#5855)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- client/src/locales/ar/translation.json | 1 - client/src/locales/de/translation.json | 1 - client/src/locales/es/translation.json | 1 - client/src/locales/et/translation.json | 1 - client/src/locales/fi/translation.json | 1 - client/src/locales/fr/translation.json | 1 - client/src/locales/he/translation.json | 1 - client/src/locales/id/translation.json | 1 - client/src/locales/it/translation.json | 1 - client/src/locales/ja/translation.json | 1 - client/src/locales/ko/translation.json | 1 - client/src/locales/nl/translation.json | 1 - client/src/locales/pl/translation.json | 1 - client/src/locales/pt/translation.json | 1 - client/src/locales/ru/translation.json | 1 - client/src/locales/sv/translation.json | 1 - client/src/locales/tr/translation.json | 1 - client/src/locales/vi/translation.json | 1 - client/src/locales/zh-Hant/translation.json | 1 - client/src/locales/zh/translation.json | 1 - 20 files changed, 20 deletions(-) diff --git a/client/src/locales/ar/translation.json b/client/src/locales/ar/translation.json index e9b907c62e..fe929928d8 100644 --- a/client/src/locales/ar/translation.json +++ b/client/src/locales/ar/translation.json @@ -348,7 +348,6 @@ "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", - "com_nav_lang_traditionalchinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", "com_nav_language": "اللغة", diff --git a/client/src/locales/de/translation.json b/client/src/locales/de/translation.json index ac5bc4fc27..88f3b3920e 100644 --- a/client/src/locales/de/translation.json +++ b/client/src/locales/de/translation.json @@ -355,7 +355,6 @@ "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", - "com_nav_lang_traditionalchinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", "com_nav_language": "Sprache", diff --git a/client/src/locales/es/translation.json b/client/src/locales/es/translation.json index d99fea763d..662e51393c 100644 --- a/client/src/locales/es/translation.json +++ b/client/src/locales/es/translation.json @@ -348,7 +348,6 @@ "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", - "com_nav_lang_traditionalchinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", "com_nav_language": "Idioma", diff --git a/client/src/locales/et/translation.json b/client/src/locales/et/translation.json index 96aa423ba6..62cf29113c 100644 --- a/client/src/locales/et/translation.json +++ b/client/src/locales/et/translation.json @@ -360,7 +360,6 @@ "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", - "com_nav_lang_traditionalchinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", "com_nav_language": "Keel", diff --git a/client/src/locales/fi/translation.json b/client/src/locales/fi/translation.json index d11b8955e9..a19903107f 100644 --- a/client/src/locales/fi/translation.json +++ b/client/src/locales/fi/translation.json @@ -285,7 +285,6 @@ "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", - "com_nav_lang_traditionalchinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", "com_nav_language": "Kieli", diff --git a/client/src/locales/fr/translation.json b/client/src/locales/fr/translation.json index 51ec313ba7..e5dfaaed6d 100644 --- a/client/src/locales/fr/translation.json +++ b/client/src/locales/fr/translation.json @@ -352,7 +352,6 @@ "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", - "com_nav_lang_traditionalchinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", "com_nav_language": "Langue", diff --git a/client/src/locales/he/translation.json b/client/src/locales/he/translation.json index 51677209c2..d5768dedc8 100644 --- a/client/src/locales/he/translation.json +++ b/client/src/locales/he/translation.json @@ -206,7 +206,6 @@ "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", - "com_nav_lang_traditionalchinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", "com_nav_latex_parsing": "ניתוח LaTeX בהודעות (עשוי להשפיע על הביצועים)", diff --git a/client/src/locales/id/translation.json b/client/src/locales/id/translation.json index 9b9ee38fd5..f5cfb88e0d 100644 --- a/client/src/locales/id/translation.json +++ b/client/src/locales/id/translation.json @@ -181,7 +181,6 @@ "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", - "com_nav_lang_traditionalchinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", "com_nav_language": "Bahasa", diff --git a/client/src/locales/it/translation.json b/client/src/locales/it/translation.json index b9d90c32c9..f0c1cb5273 100644 --- a/client/src/locales/it/translation.json +++ b/client/src/locales/it/translation.json @@ -352,7 +352,6 @@ "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", - "com_nav_lang_traditionalchinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", "com_nav_language": "Lingua", diff --git a/client/src/locales/ja/translation.json b/client/src/locales/ja/translation.json index 0aaaef90f0..b8a2a56b5e 100644 --- a/client/src/locales/ja/translation.json +++ b/client/src/locales/ja/translation.json @@ -348,7 +348,6 @@ "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", - "com_nav_lang_traditionalchinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", "com_nav_language": "言語", diff --git a/client/src/locales/ko/translation.json b/client/src/locales/ko/translation.json index 63bfd74e55..641b2763bf 100644 --- a/client/src/locales/ko/translation.json +++ b/client/src/locales/ko/translation.json @@ -348,7 +348,6 @@ "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", - "com_nav_lang_traditionalchinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", "com_nav_language": "언어", diff --git a/client/src/locales/nl/translation.json b/client/src/locales/nl/translation.json index 61e9d07103..a07206ef1d 100644 --- a/client/src/locales/nl/translation.json +++ b/client/src/locales/nl/translation.json @@ -164,7 +164,6 @@ "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", - "com_nav_lang_traditionalchinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", "com_nav_log_out": "Uitloggen", diff --git a/client/src/locales/pl/translation.json b/client/src/locales/pl/translation.json index 9de0c5861f..b56c7acf14 100644 --- a/client/src/locales/pl/translation.json +++ b/client/src/locales/pl/translation.json @@ -320,7 +320,6 @@ "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", - "com_nav_lang_traditionalchinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", "com_nav_language": "Język", diff --git a/client/src/locales/pt/translation.json b/client/src/locales/pt/translation.json index 5c205566a0..158beec77a 100644 --- a/client/src/locales/pt/translation.json +++ b/client/src/locales/pt/translation.json @@ -320,7 +320,6 @@ "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", - "com_nav_lang_traditionalchinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", "com_nav_language": "Idioma", diff --git a/client/src/locales/ru/translation.json b/client/src/locales/ru/translation.json index 819d57f783..bfad6b16c2 100644 --- a/client/src/locales/ru/translation.json +++ b/client/src/locales/ru/translation.json @@ -348,7 +348,6 @@ "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", - "com_nav_lang_traditionalchinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", "com_nav_language": "Локализация", diff --git a/client/src/locales/sv/translation.json b/client/src/locales/sv/translation.json index d3cb414b4a..7463a90de2 100644 --- a/client/src/locales/sv/translation.json +++ b/client/src/locales/sv/translation.json @@ -151,7 +151,6 @@ "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", - "com_nav_lang_traditionalchinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", "com_nav_log_out": "Logga ut", diff --git a/client/src/locales/tr/translation.json b/client/src/locales/tr/translation.json index 97eef6ef58..daf714ba93 100644 --- a/client/src/locales/tr/translation.json +++ b/client/src/locales/tr/translation.json @@ -352,7 +352,6 @@ "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", - "com_nav_lang_traditionalchinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", "com_nav_language": "Dil", diff --git a/client/src/locales/vi/translation.json b/client/src/locales/vi/translation.json index f57aea31e9..40a65d7f96 100644 --- a/client/src/locales/vi/translation.json +++ b/client/src/locales/vi/translation.json @@ -149,7 +149,6 @@ "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", - "com_nav_lang_traditionalchinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", "com_nav_log_out": "Đăng xuất", diff --git a/client/src/locales/zh-Hant/translation.json b/client/src/locales/zh-Hant/translation.json index e899cfbd73..835ccbffe7 100644 --- a/client/src/locales/zh-Hant/translation.json +++ b/client/src/locales/zh-Hant/translation.json @@ -348,7 +348,6 @@ "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", - "com_nav_lang_traditionalchinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", "com_nav_language": "語言", diff --git a/client/src/locales/zh/translation.json b/client/src/locales/zh/translation.json index 36f59cb4cd..03ee06a8bf 100644 --- a/client/src/locales/zh/translation.json +++ b/client/src/locales/zh/translation.json @@ -354,7 +354,6 @@ "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", - "com_nav_lang_traditionalchinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", "com_nav_language": "语言", From 04c2a5abe75461bb651519552f3b7cce691bb6ea Mon Sep 17 00:00:00 2001 From: Ruben Talstra Date: Fri, 14 Feb 2025 14:30:27 +0100 Subject: [PATCH 06/23] =?UTF-8?q?=F0=9F=8C=8D=20fix:=20Enhance=20i18n=20Su?= =?UTF-8?q?pport=20&=20Optimize=20Category=20Handling=20(#5866)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: Missing Translations in Prompt Filters in Prompt Library * fix: fixed issue with `zh` feat: added `Estonian` language option * fix: test for `i18n.ts` * refactor: `pt` --> `pt-BR` and `pt-PT` * feat: request access to another language. default is only one language during invite. --- .../LOCIZE_TRANSLATION_ACCESS_REQUEST.yml | 42 ++ .github/workflows/i18n-unused-keys.yml | 13 +- api/models/Categories.js | 19 +- client/package.json | 1 + .../Nav/SettingsTabs/General/General.tsx | 4 +- client/src/hooks/Prompts/useCategories.tsx | 14 +- client/src/hooks/useLocalize.ts | 15 +- client/src/locales/Translation.spec.ts | 5 +- client/src/locales/ar/translation.json | 3 + client/src/locales/de/translation.json | 3 + client/src/locales/en/translation.json | 12 + client/src/locales/es/translation.json | 3 + client/src/locales/et/translation.json | 3 + client/src/locales/fi/translation.json | 3 + client/src/locales/fr/translation.json | 3 + client/src/locales/he/translation.json | 3 + client/src/locales/i18n.ts | 29 +- client/src/locales/id/translation.json | 3 + client/src/locales/it/translation.json | 3 + client/src/locales/ja/translation.json | 3 + client/src/locales/ko/translation.json | 3 + client/src/locales/nl/translation.json | 3 + client/src/locales/pl/translation.json | 3 + .../locales/{pt => pt-BR}/translation.json | 3 + client/src/locales/pt-PT/translation.json | 635 ++++++++++++++++++ client/src/locales/ru/translation.json | 3 + client/src/locales/sv/translation.json | 3 + client/src/locales/tr/translation.json | 3 + client/src/locales/vi/translation.json | 3 + .../locales/{zh => zh-Hans}/translation.json | 3 + client/src/locales/zh-Hant/translation.json | 3 + package-lock.json | 10 + packages/data-provider/src/types.ts | 1 + 33 files changed, 819 insertions(+), 41 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/LOCIZE_TRANSLATION_ACCESS_REQUEST.yml rename client/src/locales/{pt => pt-BR}/translation.json (99%) create mode 100644 client/src/locales/pt-PT/translation.json rename client/src/locales/{zh => zh-Hans}/translation.json (99%) diff --git a/.github/ISSUE_TEMPLATE/LOCIZE_TRANSLATION_ACCESS_REQUEST.yml b/.github/ISSUE_TEMPLATE/LOCIZE_TRANSLATION_ACCESS_REQUEST.yml new file mode 100644 index 0000000000..49b01a814d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/LOCIZE_TRANSLATION_ACCESS_REQUEST.yml @@ -0,0 +1,42 @@ +name: Locize Translation Access Request +description: Request access to an additional language in Locize for LibreChat translations. +title: "Locize Access Request: " +labels: ["🌍 i18n", "🔑 access request"] +body: + - type: markdown + attributes: + value: | + Thank you for your interest in contributing to LibreChat translations! + Please fill out the form below to request access to an additional language in **Locize**. + + **🔗 Available Languages:** [View the list here](https://www.librechat.ai/docs/translation) + + **📌 Note:** Ensure that the requested language is supported before submitting your request. + - type: input + id: account_name + attributes: + label: Locize Account Name + description: Please provide your Locize account name (e.g., John Doe). + placeholder: e.g., John Doe + validations: + required: true + - type: input + id: language_requested + attributes: + label: Language Code (ISO 639-1) + description: | + Enter the **ISO 639-1** language code for the language you want to translate into. + Example: `es` for Spanish, `zh-Hant` for Traditional Chinese. + + **🔗 Reference:** [Available Languages](https://www.librechat.ai/docs/translation) + placeholder: e.g., es + validations: + required: true + - type: checkboxes + id: agreement + attributes: + label: Agreement + description: By submitting this request, you confirm that you will contribute responsibly and adhere to the project guidelines. + options: + - label: I agree to use my access solely for contributing to LibreChat translations. + required: true \ No newline at end of file diff --git a/.github/workflows/i18n-unused-keys.yml b/.github/workflows/i18n-unused-keys.yml index 2e66dfa987..5e29a8a8bd 100644 --- a/.github/workflows/i18n-unused-keys.yml +++ b/.github/workflows/i18n-unused-keys.yml @@ -4,6 +4,7 @@ on: pull_request: paths: - "client/src/**" + - "api/**" jobs: detect-unused-i18n-keys: @@ -21,7 +22,7 @@ jobs: # Define paths I18N_FILE="client/src/locales/en/translation.json" - SOURCE_DIR="client/src" + SOURCE_DIRS=("client/src" "api") # Check if translation file exists if [[ ! -f "$I18N_FILE" ]]; then @@ -37,7 +38,15 @@ jobs: # Check if each key is used in the source code for KEY in $KEYS; do - if ! grep -r --include=\*.{js,jsx,ts,tsx} -q "$KEY" "$SOURCE_DIR"; then + FOUND=false + for DIR in "${SOURCE_DIRS[@]}"; do + if grep -r --include=\*.{js,jsx,ts,tsx} -q "$KEY" "$DIR"; then + FOUND=true + break + fi + done + + if [[ "$FOUND" == false ]]; then UNUSED_KEYS+=("$KEY") fi done diff --git a/api/models/Categories.js b/api/models/Categories.js index 0f7f29703f..605b68d176 100644 --- a/api/models/Categories.js +++ b/api/models/Categories.js @@ -1,41 +1,42 @@ const { logger } = require('~/config'); // const { Categories } = require('./schema/categories'); + const options = [ { label: 'idea', - value: 'idea', + value: 'com_ui_idea', }, { label: 'travel', - value: 'travel', + value: 'com_ui_travel', }, { label: 'teach_or_explain', - value: 'teach_or_explain', + value: 'com_ui_teach_or_explain', }, { label: 'write', - value: 'write', + value: 'com_ui_write', }, { label: 'shop', - value: 'shop', + value: 'com_ui_shop', }, { label: 'code', - value: 'code', + value: 'com_ui_code', }, { label: 'misc', - value: 'misc', + value: 'com_ui_misc', }, { label: 'roleplay', - value: 'roleplay', + value: 'com_ui_roleplay', }, { label: 'finance', - value: 'finance', + value: 'com_ui_finance', }, ]; diff --git a/client/package.json b/client/package.json index 0a0379b6f1..22e9b1dd03 100644 --- a/client/package.json +++ b/client/package.json @@ -64,6 +64,7 @@ "framer-motion": "^11.5.4", "html-to-image": "^1.11.11", "i18next": "^24.2.2", + "i18next-browser-languagedetector": "^8.0.3", "js-cookie": "^3.0.5", "librechat-data-provider": "*", "lodash": "^4.17.21", diff --git a/client/src/components/Nav/SettingsTabs/General/General.tsx b/client/src/components/Nav/SettingsTabs/General/General.tsx index 5bbe0a6d61..d1ad3d8a93 100644 --- a/client/src/components/Nav/SettingsTabs/General/General.tsx +++ b/client/src/components/Nav/SettingsTabs/General/General.tsx @@ -51,15 +51,17 @@ export const LangSelector = ({ const languageOptions = [ { value: 'auto', label: localize('com_nav_lang_auto') }, { value: 'en-US', label: localize('com_nav_lang_english') }, - { value: 'zh-CN', label: localize('com_nav_lang_chinese') }, + { value: 'zh-Hans', label: localize('com_nav_lang_chinese') }, { value: 'zh-Hant', label: localize('com_nav_lang_traditional_chinese') }, { value: 'ar-EG', label: localize('com_nav_lang_arabic') }, { value: 'de-DE', label: localize('com_nav_lang_german') }, { value: 'es-ES', label: localize('com_nav_lang_spanish') }, + { value: 'et-EE', label: localize('com_nav_lang_estonian') }, { value: 'fr-FR', label: localize('com_nav_lang_french') }, { value: 'it-IT', label: localize('com_nav_lang_italian') }, { value: 'pl-PL', label: localize('com_nav_lang_polish') }, { value: 'pt-BR', label: localize('com_nav_lang_brazilian_portuguese') }, + { value: 'pt-PT', label: localize('com_nav_lang_portuguese') }, { value: 'ru-RU', label: localize('com_nav_lang_russian') }, { value: 'ja-JP', label: localize('com_nav_lang_japanese') }, { value: 'sv-SE', label: localize('com_nav_lang_swedish') }, diff --git a/client/src/hooks/Prompts/useCategories.tsx b/client/src/hooks/Prompts/useCategories.tsx index 6bdaa5272f..a5083f8ed0 100644 --- a/client/src/hooks/Prompts/useCategories.tsx +++ b/client/src/hooks/Prompts/useCategories.tsx @@ -1,16 +1,16 @@ import { useGetCategories } from '~/data-provider'; import CategoryIcon from '~/components/Prompts/Groups/CategoryIcon'; -import useLocalize from '~/hooks/useLocalize'; +import useLocalize, { TranslationKeys } from '~/hooks/useLocalize'; -const loadingCategories = [ +const loadingCategories: { label: TranslationKeys; value: string }[] = [ { - label: 'Loading...', + label: 'com_ui_loading', value: '', }, -] as undefined | { label: string; value: string }[]; +]; -const emptyCategory = { - label: '-', +const emptyCategory: { label: TranslationKeys; value: string } = { + label: 'com_ui_empty_category', value: '', }; @@ -19,7 +19,7 @@ const useCategories = (className = '') => { const { data: categories = loadingCategories } = useGetCategories({ select: (data) => data.map((category) => ({ - label: localize(`com_ui_${category.label}`) || category.label, + label: localize(category.label as TranslationKeys), value: category.value, icon: category.value ? ( diff --git a/client/src/hooks/useLocalize.ts b/client/src/hooks/useLocalize.ts index 4c7a385988..bae7480e7c 100644 --- a/client/src/hooks/useLocalize.ts +++ b/client/src/hooks/useLocalize.ts @@ -1,9 +1,9 @@ -import { useEffect, useCallback } from 'react'; +import { useEffect } from 'react'; +import { TOptions } from 'i18next'; import { useRecoilValue } from 'recoil'; import { useTranslation } from 'react-i18next'; -import { TOptions } from 'i18next'; -import store from '~/store'; import { resources } from '~/locales/i18n'; +import store from '~/store'; export type TranslationKeys = keyof typeof resources.en.translation; @@ -17,10 +17,5 @@ export default function useLocalize() { } }, [lang, i18n]); - const memoizedLocalize = useCallback( - (phraseKey: TranslationKeys, options?: TOptions) => t(phraseKey, options), - [t], - ); - - return memoizedLocalize; -} + return (phraseKey: TranslationKeys, options?: TOptions) => t(phraseKey, options); +} \ No newline at end of file diff --git a/client/src/locales/Translation.spec.ts b/client/src/locales/Translation.spec.ts index 763348a731..89c7b1d7b7 100644 --- a/client/src/locales/Translation.spec.ts +++ b/client/src/locales/Translation.spec.ts @@ -34,10 +34,9 @@ describe('i18next translation tests', () => { expect(i18n.t('com_ui_examples')).toBe(English.com_ui_examples); }); - it('should return an empty string for an invalid key', () => { + it('should return the key itself for an invalid key', () => { i18n.changeLanguage('en'); - // @ts-ignore - expect(i18n.t('invalid-key')).toBe(''); + expect(i18n.t('invalid-key')).toBe('invalid-key'); // Returns the key itself }); it('should correctly format placeholders in the translation', () => { diff --git a/client/src/locales/ar/translation.json b/client/src/locales/ar/translation.json index fe929928d8..f3add85df2 100644 --- a/client/src/locales/ar/translation.json +++ b/client/src/locales/ar/translation.json @@ -333,7 +333,9 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "اكتشاف تلقائي", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", + "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", "com_nav_lang_finnish": "Suomi", @@ -350,6 +352,7 @@ "com_nav_lang_swedish": "Svenska", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_language": "اللغة", "com_nav_latex_parsing": "تحليل LaTeX في الرسائل (قد يؤثر على الأداء)", "com_nav_log_out": "تسجيل الخروج", diff --git a/client/src/locales/de/translation.json b/client/src/locales/de/translation.json index 88f3b3920e..a38e05a468 100644 --- a/client/src/locales/de/translation.json +++ b/client/src/locales/de/translation.json @@ -340,7 +340,9 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "Automatisch erkennen", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", + "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", "com_nav_lang_finnish": "Suomi", @@ -357,6 +359,7 @@ "com_nav_lang_swedish": "Svenska", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_language": "Sprache", "com_nav_latex_parsing": "LaTeX in Nachrichten parsen (kann die Leistung beeinflussen)", "com_nav_log_out": "Abmelden", diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index fc6b1800d9..c123d67f17 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -345,7 +345,9 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "Auto detect", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", + "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", "com_nav_lang_finnish": "Suomi", @@ -784,5 +786,15 @@ "com_ui_yes": "Yes", "com_ui_zoom": "Zoom", "com_user_message": "You", + "com_ui_loading": "Loading...", + "com_ui_finance": "Finance", + "com_ui_idea": "Ideas", + "com_ui_misc": "Misc.", + "com_ui_roleplay": "Roleplay", + "com_ui_shop": "Shopping", + "com_ui_teach_or_explain": "Learning", + "com_ui_travel": "Travel", + "com_ui_write": "Writing", + "com_ui_empty_category": "-", "com_warning_resubmit_unsupported": "Resubmitting the AI message is not supported for this endpoint." } \ No newline at end of file diff --git a/client/src/locales/es/translation.json b/client/src/locales/es/translation.json index 662e51393c..299bbea3b8 100644 --- a/client/src/locales/es/translation.json +++ b/client/src/locales/es/translation.json @@ -333,7 +333,9 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "Detección automática", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", + "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", "com_nav_lang_finnish": "Suomi", @@ -350,6 +352,7 @@ "com_nav_lang_swedish": "Svenska", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_language": "Idioma", "com_nav_latex_parsing": "Analizar LaTeX en los mensajes (puede afectar el rendimiento)", "com_nav_log_out": "Cerrar sesión", diff --git a/client/src/locales/et/translation.json b/client/src/locales/et/translation.json index 62cf29113c..5d2c40d96a 100644 --- a/client/src/locales/et/translation.json +++ b/client/src/locales/et/translation.json @@ -345,7 +345,9 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "Tuvasta automaatselt", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", + "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", "com_nav_lang_finnish": "Suomi", @@ -362,6 +364,7 @@ "com_nav_lang_swedish": "Svenska", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_language": "Keel", "com_nav_latex_parsing": "LaTeXi parsimine sõnumites (võib mõjutada jõudlust)", "com_nav_log_out": "Logi välja", diff --git a/client/src/locales/fi/translation.json b/client/src/locales/fi/translation.json index a19903107f..709a77e762 100644 --- a/client/src/locales/fi/translation.json +++ b/client/src/locales/fi/translation.json @@ -270,7 +270,9 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "Tunnista automaattisesti", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", + "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", "com_nav_lang_finnish": "Suomi", @@ -287,6 +289,7 @@ "com_nav_lang_swedish": "Svenska", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_language": "Kieli", "com_nav_latex_parsing": "Tulkitse LaTeX:ia viesteissä (saattaa vaikuttaa suoritustehoon)", "com_nav_log_out": "Kirjaudu ulos", diff --git a/client/src/locales/fr/translation.json b/client/src/locales/fr/translation.json index e5dfaaed6d..2a55e52a8b 100644 --- a/client/src/locales/fr/translation.json +++ b/client/src/locales/fr/translation.json @@ -337,7 +337,9 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "Détection automatique", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", + "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", "com_nav_lang_finnish": "Suomi", @@ -354,6 +356,7 @@ "com_nav_lang_swedish": "Svenska", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_language": "Langue", "com_nav_latex_parsing": "Analyse LaTeX dans les messages (peut affecter les performances)", "com_nav_log_out": "Se déconnecter", diff --git a/client/src/locales/he/translation.json b/client/src/locales/he/translation.json index d5768dedc8..bf58f26929 100644 --- a/client/src/locales/he/translation.json +++ b/client/src/locales/he/translation.json @@ -191,7 +191,9 @@ "com_nav_hide_panel": "הסתר לוח הצד הימני ביותר", "com_nav_lang_arabic": "العربية", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", + "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", "com_nav_lang_finnish": "Suomi", @@ -208,6 +210,7 @@ "com_nav_lang_swedish": "Svenska", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_latex_parsing": "ניתוח LaTeX בהודעות (עשוי להשפיע על הביצועים)", "com_nav_log_out": "צא", "com_nav_modular_chat": "אפשר החלפת נקודות קצה באמצע שיחה", diff --git a/client/src/locales/i18n.ts b/client/src/locales/i18n.ts index c9d9d48efe..03d7605d32 100644 --- a/client/src/locales/i18n.ts +++ b/client/src/locales/i18n.ts @@ -1,45 +1,50 @@ import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; +import LanguageDetector from 'i18next-browser-languagedetector'; // Import your JSON translations import translationEn from './en/translation.json'; import translationAr from './ar/translation.json'; -import translationZh from './zh/translation.json'; import translationDe from './de/translation.json'; import translationEs from './es/translation.json'; +import translationEt from './et/translation.json'; import translationFr from './fr/translation.json'; import translationIt from './it/translation.json'; import translationPl from './pl/translation.json'; -import translationPt from './pt/translation.json'; +import translationPt_BR from './pt-BR/translation.json'; +import translationPt_PT from './pt-PT/translation.json'; import translationRu from './ru/translation.json'; import translationJa from './ja/translation.json'; import translationSv from './sv/translation.json'; import translationKo from './ko/translation.json'; -import translationZh_Hant from './zh-Hant/translation.json'; import translationVi from './vi/translation.json'; import translationTr from './tr/translation.json'; import translationNl from './nl/translation.json'; import translationId from './id/translation.json'; import translationHe from './he/translation.json'; import translationFi from './fi/translation.json'; +import translationZh_Hans from './zh-Hans/translation.json'; +import translationZh_Hant from './zh-Hant/translation.json'; export const defaultNS = 'translation'; export const resources = { en: { translation: translationEn }, ar: { translation: translationAr }, - zh: { translation: translationZh }, + 'zh-Hans': { translation: translationZh_Hans }, + 'zh-Hant': { translation: translationZh_Hant }, de: { translation: translationDe }, es: { translation: translationEs }, + et: { translation: translationEt }, fr: { translation: translationFr }, it: { translation: translationIt }, pl: { translation: translationPl }, - pt: { translation: translationPt }, + 'pt-BR': { translation: translationPt_BR }, + 'pt-PT': { translation: translationPt_PT }, ru: { translation: translationRu }, ja: { translation: translationJa }, sv: { translation: translationSv }, ko: { translation: translationKo }, - 'zh-Hant': { translation: translationZh_Hant }, vi: { translation: translationVi }, tr: { translation: translationTr }, nl: { translation: translationNl }, @@ -49,15 +54,21 @@ export const resources = { } as const; i18n + .use(LanguageDetector) .use(initReactI18next) .init({ - fallbackLng: 'en', + fallbackLng: { + 'zh-TW': ['zh-Hant'], + 'zh-HK': ['zh-Hant'], + 'zh': ['zh-Hans'], + default: ['en'], + }, + fallbackNS: 'translation', ns: ['translation'], + debug: true, defaultNS, resources, interpolation: { escapeValue: false }, - // Return an empty string for missing keys rather than the key itself - parseMissingKeyHandler: () => '', }); export default i18n; \ No newline at end of file diff --git a/client/src/locales/id/translation.json b/client/src/locales/id/translation.json index f5cfb88e0d..9d90275f95 100644 --- a/client/src/locales/id/translation.json +++ b/client/src/locales/id/translation.json @@ -166,7 +166,9 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "Deteksi otomatis", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", + "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", "com_nav_lang_finnish": "Suomi", @@ -183,6 +185,7 @@ "com_nav_lang_swedish": "Svenska", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_language": "Bahasa", "com_nav_latex_parsing": "Parsing LaTeX dalam pesan (dapat memengaruhi kinerja)", "com_nav_log_out": "Keluar", diff --git a/client/src/locales/it/translation.json b/client/src/locales/it/translation.json index f0c1cb5273..399e7cadd9 100644 --- a/client/src/locales/it/translation.json +++ b/client/src/locales/it/translation.json @@ -337,7 +337,9 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "Rileva automaticamente", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", + "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", "com_nav_lang_finnish": "Suomi", @@ -354,6 +356,7 @@ "com_nav_lang_swedish": "Svenska", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_language": "Lingua", "com_nav_latex_parsing": "Analizza LaTeX nei messaggi (potrebbe influire sulle prestazioni)", "com_nav_log_out": "Disconnetti", diff --git a/client/src/locales/ja/translation.json b/client/src/locales/ja/translation.json index b8a2a56b5e..cfad406bef 100644 --- a/client/src/locales/ja/translation.json +++ b/client/src/locales/ja/translation.json @@ -333,7 +333,9 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "自動検出", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", + "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", "com_nav_lang_finnish": "Suomi", @@ -350,6 +352,7 @@ "com_nav_lang_swedish": "Svenska", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_language": "言語", "com_nav_latex_parsing": "メッセージ内の LaTeX の構文解析 (パフォーマンスに影響する可能性があります)", "com_nav_log_out": "ログアウト", diff --git a/client/src/locales/ko/translation.json b/client/src/locales/ko/translation.json index 641b2763bf..994cf386e0 100644 --- a/client/src/locales/ko/translation.json +++ b/client/src/locales/ko/translation.json @@ -333,7 +333,9 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "자동 감지", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", + "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", "com_nav_lang_finnish": "Suomi", @@ -350,6 +352,7 @@ "com_nav_lang_swedish": "Svenska", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_language": "언어", "com_nav_latex_parsing": "메시지에서 LaTeX 구문 분석(성능에 영향을 줄 수 있음)", "com_nav_log_out": "로그아웃", diff --git a/client/src/locales/nl/translation.json b/client/src/locales/nl/translation.json index a07206ef1d..d0f881e3bd 100644 --- a/client/src/locales/nl/translation.json +++ b/client/src/locales/nl/translation.json @@ -149,7 +149,9 @@ "com_nav_help_faq": "Help & FAQ", "com_nav_lang_arabic": "العربية", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", + "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", "com_nav_lang_finnish": "Suomi", @@ -166,6 +168,7 @@ "com_nav_lang_swedish": "Svenska", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_log_out": "Uitloggen", "com_nav_not_supported": "Niet ondersteund", "com_nav_open_sidebar": "Zijbalk openen", diff --git a/client/src/locales/pl/translation.json b/client/src/locales/pl/translation.json index b56c7acf14..f4b82ec13b 100644 --- a/client/src/locales/pl/translation.json +++ b/client/src/locales/pl/translation.json @@ -305,7 +305,9 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "Automatyczne wykrywanie", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", + "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", "com_nav_lang_finnish": "Suomi", @@ -322,6 +324,7 @@ "com_nav_lang_swedish": "Svenska", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_language": "Język", "com_nav_latex_parsing": "Parsowanie LaTeX w wiadomościach (może wpływać na wydajność)", "com_nav_log_out": "Wyloguj", diff --git a/client/src/locales/pt/translation.json b/client/src/locales/pt-BR/translation.json similarity index 99% rename from client/src/locales/pt/translation.json rename to client/src/locales/pt-BR/translation.json index 158beec77a..bdebde7057 100644 --- a/client/src/locales/pt/translation.json +++ b/client/src/locales/pt-BR/translation.json @@ -305,7 +305,9 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "Detecção automática", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", + "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", "com_nav_lang_finnish": "Suomi", @@ -322,6 +324,7 @@ "com_nav_lang_swedish": "Svenska", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_language": "Idioma", "com_nav_latex_parsing": "Análise de LaTeX em mensagens (pode afetar o desempenho)", "com_nav_log_out": "Sair", diff --git a/client/src/locales/pt-PT/translation.json b/client/src/locales/pt-PT/translation.json new file mode 100644 index 0000000000..bdebde7057 --- /dev/null +++ b/client/src/locales/pt-PT/translation.json @@ -0,0 +1,635 @@ +{ + "com_a11y_ai_composing": "A IA ainda está compondo.", + "com_a11y_end": "A IA terminou de responder.", + "com_a11y_start": "A IA começou a responder.", + "com_agents_create_error": "Houve um erro ao criar seu agente.", + "com_agents_description_placeholder": "Opcional: Descreva seu Agente aqui", + "com_agents_instructions_placeholder": "As instruções do sistema que o agente usa", + "com_agents_name_placeholder": "Opcional: O nome do agente", + "com_agents_search_name": "Pesquisar agentes por nome", + "com_agents_update_error": "Houve um erro ao atualizar seu agente.", + "com_assistants_actions": "Ações", + "com_assistants_actions_disabled": "Você precisa criar um assistente antes de adicionar ações.", + "com_assistants_actions_info": "Permita que seu Assistente recupere informações ou execute ações via API's", + "com_assistants_add_actions": "Adicionar Ações", + "com_assistants_add_tools": "Adicionar Ferramentas", + "com_assistants_append_date": "Anexar Data e Hora Atual", + "com_assistants_append_date_tooltip": "Quando ativado, a data e hora atual do cliente serão anexadas às instruções do sistema do assistente.", + "com_assistants_available_actions": "Ações Disponíveis", + "com_assistants_capabilities": "Capacidades", + "com_assistants_code_interpreter": "Interpretador de Código", + "com_assistants_code_interpreter_files": "Os arquivos abaixo são apenas para o Interpretador de Código:", + "com_assistants_code_interpreter_info": "O Interpretador de Código permite que o assistente escreva e execute código. Esta ferramenta pode processar arquivos com dados e formatações diversas, e gerar arquivos como gráficos.", + "com_assistants_completed_action": "Conversou com {{0}}", + "com_assistants_completed_function": "Executou {{0}}", + "com_assistants_conversation_starters": "Iniciadores de Conversa", + "com_assistants_conversation_starters_placeholder": "Digite um iniciador de conversa", + "com_assistants_create_error": "Houve um erro ao criar seu assistente.", + "com_assistants_create_success": "Criado com sucesso", + "com_assistants_delete_actions_error": "Houve um erro ao excluir a ação.", + "com_assistants_delete_actions_success": "Ação excluída com sucesso do Assistente", + "com_assistants_description_placeholder": "Opcional: Descreva seu Assistente aqui", + "com_assistants_domain_info": "Assistente enviou esta informação para {{0}}", + "com_assistants_file_search": "Pesquisa de Arquivos", + "com_assistants_file_search_info": "A pesquisa de arquivos permite que o assistente tenha conhecimento dos arquivos que você ou seus usuários carregam. Uma vez que um arquivo é carregado, o assistente decide automaticamente quando recuperar o conteúdo com base nas solicitações do usuário. Anexar armazenamentos vetoriais para Pesquisa de Arquivos ainda não é suportado. Você pode anexá-los no Playground do Provedor ou anexar arquivos às mensagens para pesquisa de arquivos em uma base de thread.", + "com_assistants_function_use": "Assistente usou {{0}}", + "com_assistants_image_vision": "Visão de Imagem", + "com_assistants_instructions_placeholder": "As instruções do sistema que o assistente usa", + "com_assistants_knowledge": "Conhecimento", + "com_assistants_knowledge_disabled": "O assistente deve ser criado, e o Interpretador de Código ou Recuperação deve ser habilitado e salvo antes de carregar arquivos como Conhecimento.", + "com_assistants_knowledge_info": "Se você carregar arquivos em Conhecimento, as conversas com seu Assistente podem incluir o conteúdo dos arquivos.", + "com_assistants_max_starters_reached": "Número máximo de iniciadores de conversa atingido", + "com_assistants_name_placeholder": "Opcional: O nome do assistente", + "com_assistants_non_retrieval_model": "A pesquisa de arquivos não está habilitada neste modelo. Por favor, selecione outro modelo.", + "com_assistants_retrieval": "Recuperação", + "com_assistants_running_action": "Executando ação", + "com_assistants_search_name": "Pesquisar assistentes por nome", + "com_assistants_update_actions_error": "Houve um erro ao criar ou atualizar a ação.", + "com_assistants_update_actions_success": "Ação criada ou atualizada com sucesso", + "com_assistants_update_error": "Houve um erro ao atualizar seu assistente.", + "com_assistants_update_success": "Atualizado com sucesso", + "com_auth_already_have_account": "Já tem uma conta?", + "com_auth_back_to_login": "Voltar para Login", + "com_auth_click": "Clique", + "com_auth_click_here": "Clique aqui", + "com_auth_continue": "Continuar", + "com_auth_create_account": "Criar sua conta", + "com_auth_discord_login": "Continuar com Discord", + "com_auth_email": "E-mail", + "com_auth_email_address": "Endereço de e-mail", + "com_auth_email_max_length": "O e-mail não deve ter mais de 120 caracteres", + "com_auth_email_min_length": "O e-mail deve ter pelo menos 6 caracteres", + "com_auth_email_pattern": "Você deve inserir um endereço de e-mail válido", + "com_auth_email_required": "E-mail é obrigatório", + "com_auth_email_resend_link": "Reenviar E-mail", + "com_auth_email_resent_failed": "Falha ao reenviar e-mail de verificação", + "com_auth_email_resent_success": "E-mail de verificação reenviado com sucesso", + "com_auth_email_verification_failed": "Falha na verificação de e-mail", + "com_auth_email_verification_failed_token_missing": "Falha na verificação, token ausente", + "com_auth_email_verification_in_progress": "Verificando seu e-mail, por favor, aguarde", + "com_auth_email_verification_invalid": "Verificação de e-mail inválida", + "com_auth_email_verification_redirecting": "Redirecionando em {{0}} segundos...", + "com_auth_email_verification_resend_prompt": "Não recebeu o e-mail?", + "com_auth_email_verification_success": "E-mail verificado com sucesso", + "com_auth_error_create": "Houve um erro ao tentar registrar sua conta. Por favor, tente novamente.", + "com_auth_error_invalid_reset_token": "Este token de redefinição de senha não é mais válido.", + "com_auth_error_login": "Não foi possível fazer login com as informações fornecidas. Por favor, verifique suas credenciais e tente novamente.", + "com_auth_error_login_ban": "Sua conta foi temporariamente banida devido a violações do nosso serviço.", + "com_auth_error_login_rl": "Muitas tentativas de login em um curto período de tempo. Por favor, tente novamente mais tarde.", + "com_auth_error_login_server": "Houve um erro interno no servidor. Por favor, aguarde alguns momentos e tente novamente.", + "com_auth_error_login_unverified": "Sua conta não foi verificada. Por favor, verifique seu e-mail para um link de verificação.", + "com_auth_facebook_login": "Continuar com Facebook", + "com_auth_full_name": "Nome completo", + "com_auth_github_login": "Continuar com Github", + "com_auth_google_login": "Continuar com Google", + "com_auth_here": "AQUI", + "com_auth_login": "Entrar", + "com_auth_login_with_new_password": "Agora você pode fazer login com sua nova senha.", + "com_auth_name_max_length": "O nome deve ter menos de 80 caracteres", + "com_auth_name_min_length": "O nome deve ter pelo menos 3 caracteres", + "com_auth_name_required": "Nome é obrigatório", + "com_auth_no_account": "Não tem uma conta?", + "com_auth_password": "Senha", + "com_auth_password_confirm": "Confirmar senha", + "com_auth_password_forgot": "Esqueceu a senha?", + "com_auth_password_max_length": "A senha deve ter menos de 128 caracteres", + "com_auth_password_min_length": "A senha deve ter pelo menos 8 caracteres", + "com_auth_password_not_match": "As senhas não coincidem", + "com_auth_password_required": "Senha é obrigatória", + "com_auth_registration_success_generic": "Por favor, verifique seu e-mail para verificar seu endereço de e-mail.", + "com_auth_registration_success_insecure": "Registro bem-sucedido.", + "com_auth_reset_password": "Redefinir sua senha", + "com_auth_reset_password_if_email_exists": "Se uma conta com esse e-mail existir, um e-mail com instruções para redefinir a senha foi enviado. Certifique-se de verificar sua pasta de spam.", + "com_auth_reset_password_link_sent": "E-mail enviado", + "com_auth_reset_password_success": "Senha redefinida com sucesso", + "com_auth_sign_in": "Entrar", + "com_auth_sign_up": "Inscrever-se", + "com_auth_submit_registration": "Enviar registro", + "com_auth_to_reset_your_password": "para redefinir sua senha.", + "com_auth_to_try_again": "para tentar novamente.", + "com_auth_username": "Nome de usuário (opcional)", + "com_auth_username_max_length": "O nome de usuário deve ter menos de 20 caracteres", + "com_auth_username_min_length": "O nome de usuário deve ter pelo menos 2 caracteres", + "com_auth_welcome_back": "Bem-vindo de volta", + "com_endpoint": "Endpoint", + "com_endpoint_agent": "Agente", + "com_endpoint_agent_model": "Modelo de Agente (Recomendado: GPT-3.5)", + "com_endpoint_anthropic_maxoutputtokens": "Número máximo de tokens que podem ser gerados na resposta. Especifique um valor mais baixo para respostas mais curtas e um valor mais alto para respostas mais longas. Nota: os modelos podem parar antes de atingir esse máximo.", + "com_endpoint_anthropic_prompt_cache": "O cache de prompt permite reutilizar um grande contexto ou instruções em chamadas de API, reduzindo custos e latência", + "com_endpoint_anthropic_temp": "Varia de 0 a 1. Use temperatura mais próxima de 0 para tarefas analíticas / de múltipla escolha, e mais próxima de 1 para tarefas criativas e generativas. Recomendamos alterar isso ou Top P, mas não ambos.", + "com_endpoint_anthropic_topk": "Top-k altera como o modelo seleciona tokens para saída. Um top-k de 1 significa que o token selecionado é o mais provável entre todos os tokens no vocabulário do modelo (também chamado de decodificação gananciosa), enquanto um top-k de 3 significa que o próximo token é selecionado entre os 3 tokens mais prováveis (usando temperatura).", + "com_endpoint_anthropic_topp": "Top-p altera como o modelo seleciona tokens para saída. Os tokens são selecionados dos mais prováveis (veja o parâmetro topK) até os menos prováveis até que a soma de suas probabilidades atinja o valor top-p.", + "com_endpoint_assistant": "Assistente", + "com_endpoint_assistant_model": "Modelo de Assistente", + "com_endpoint_assistant_placeholder": "Por favor, selecione um Assistente no Painel Lateral Direito", + "com_endpoint_completion": "Conclusão", + "com_endpoint_completion_model": "Modelo de Conclusão (Recomendado: GPT-4)", + "com_endpoint_config_click_here": "Clique Aqui", + "com_endpoint_config_google_api_info": "Para obter sua chave API de Linguagem Generativa (para Gemini),", + "com_endpoint_config_google_api_key": "Chave API do Google", + "com_endpoint_config_google_cloud_platform": "(do Google Cloud Platform)", + "com_endpoint_config_google_gemini_api": "(API Gemini)", + "com_endpoint_config_google_service_key": "Chave de Conta de Serviço do Google", + "com_endpoint_config_key": "Definir Chave API", + "com_endpoint_config_key_encryption": "Sua chave será criptografada e excluída em", + "com_endpoint_config_key_for": "Definir Chave API para", + "com_endpoint_config_key_google_need_to": "Você precisa", + "com_endpoint_config_key_google_service_account": "Criar uma Conta de Serviço", + "com_endpoint_config_key_google_vertex_ai": "Habilitar Vertex AI", + "com_endpoint_config_key_google_vertex_api": "API no Google Cloud, então", + "com_endpoint_config_key_google_vertex_api_role": "Certifique-se de clicar em \"Criar e Continuar\" para dar pelo menos o papel de \"Usuário do Vertex AI\". Por fim, crie uma chave JSON para importar aqui.", + "com_endpoint_config_key_import_json_key": "Importar Chave JSON da Conta de Serviço.", + "com_endpoint_config_key_import_json_key_invalid": "Chave JSON da Conta de Serviço Inválida, Você importou o arquivo correto?", + "com_endpoint_config_key_import_json_key_success": "Chave JSON da Conta de Serviço Importada com Sucesso", + "com_endpoint_config_key_name": "Chave", + "com_endpoint_config_key_never_expires": "Sua chave nunca expira", + "com_endpoint_config_placeholder": "Defina sua Chave no menu do Cabeçalho para conversar.", + "com_endpoint_config_value": "Insira o valor para", + "com_endpoint_context": "Contexto", + "com_endpoint_context_info": "O número máximo de tokens que podem ser usados para contexto. Use isso para controlar quantos tokens são enviados por solicitação. Se não especificado, usará os padrões do sistema com base no tamanho do contexto dos modelos conhecidos. Definir valores mais altos pode resultar em erros e/ou maior custo de tokens.", + "com_endpoint_context_tokens": "Máximo de Tokens de Contexto", + "com_endpoint_custom_name": "Nome Personalizado", + "com_endpoint_default": "padrão", + "com_endpoint_default_blank": "padrão: em branco", + "com_endpoint_default_empty": "padrão: vazio", + "com_endpoint_default_with_num": "padrão: {{0}}", + "com_endpoint_examples": "Presets", + "com_endpoint_export": "Exportar", + "com_endpoint_export_share": "Exportar/Compartilhar", + "com_endpoint_frequency_penalty": "Penalidade de Frequência", + "com_endpoint_func_hover": "Habilitar uso de Plugins como Funções OpenAI", + "com_endpoint_google_custom_name_placeholder": "Defina um nome personalizado para o Google", + "com_endpoint_google_maxoutputtokens": "Número máximo de tokens que podem ser gerados na resposta. Especifique um valor mais baixo para respostas mais curtas e um valor mais alto para respostas mais longas. Nota: os modelos podem parar antes de atingir esse máximo.", + "com_endpoint_google_temp": "Valores mais altos = mais aleatório, enquanto valores mais baixos = mais focado e determinístico. Recomendamos alterar isso ou Top P, mas não ambos.", + "com_endpoint_google_topk": "Top-k altera como o modelo seleciona tokens para saída. Um top-k de 1 significa que o token selecionado é o mais provável entre todos os tokens no vocabulário do modelo (também chamado de decodificação gananciosa), enquanto um top-k de 3 significa que o próximo token é selecionado entre os 3 tokens mais prováveis (usando temperatura).", + "com_endpoint_google_topp": "Top-p altera como o modelo seleciona tokens para saída. Os tokens são selecionados dos mais prováveis (veja o parâmetro topK) até os menos prováveis até que a soma de suas probabilidades atinja o valor top-p.", + "com_endpoint_import": "Importar", + "com_endpoint_instructions_assistants": "Substituir Instruções", + "com_endpoint_instructions_assistants_placeholder": "Substitui as instruções do assistente. Isso é útil para modificar o comportamento em uma base por execução.", + "com_endpoint_max_output_tokens": "Máximo de Tokens de Saída", + "com_endpoint_message": "Mensagem", + "com_endpoint_message_not_appendable": "Edite sua mensagem ou Regenerar.", + "com_endpoint_my_preset": "Meu Preset", + "com_endpoint_no_presets": "Ainda não há presets, use o botão de configurações para criar um", + "com_endpoint_open_menu": "Abrir Menu", + "com_endpoint_openai_custom_name_placeholder": "Defina um nome personalizado para a IA", + "com_endpoint_openai_detail": "A resolução para solicitações de Visão. \"Baixa\" é mais barata e rápida, \"Alta\" é mais detalhada e cara, e \"Auto\" escolherá automaticamente entre as duas com base na resolução da imagem.", + "com_endpoint_openai_freq": "Número entre -2.0 e 2.0. Valores positivos penalizam novos tokens com base em sua frequência existente no texto até agora, diminuindo a probabilidade do modelo de repetir a mesma linha literalmente.", + "com_endpoint_openai_max": "O máximo de tokens para gerar. O comprimento total dos tokens de entrada e dos tokens gerados é limitado pelo comprimento do contexto do modelo.", + "com_endpoint_openai_max_tokens": "Campo opcional `max_tokens`, representando o número máximo de tokens que podem ser gerados na conclusão do chat. O comprimento total dos tokens de entrada e dos tokens gerados é limitado pelo comprimento do contexto dos modelos. Você pode experimentar erros se esse número exceder o máximo de tokens de contexto.", + "com_endpoint_openai_pres": "Número entre -2.0 e 2.0. Valores positivos penalizam novos tokens com base em sua presença no texto até agora, aumentando a probabilidade do modelo de falar sobre novos tópicos.", + "com_endpoint_openai_prompt_prefix_placeholder": "Defina instruções personalizadas para incluir na Mensagem do Sistema. Padrão: nenhuma", + "com_endpoint_openai_resend": "Reenviar todas as imagens anexadas anteriormente. Nota: isso pode aumentar significativamente o custo de tokens e você pode experimentar erros com muitos anexos de imagem.", + "com_endpoint_openai_resend_files": "Reenviar todos os arquivos anexados anteriormente. Nota: isso aumentará o custo de tokens e você pode experimentar erros com muitos anexos.", + "com_endpoint_openai_stop": "Até 4 sequências onde a API parará de gerar mais tokens.", + "com_endpoint_openai_temp": "Valores mais altos = mais aleatório, enquanto valores mais baixos = mais focado e determinístico. Recomendamos alterar isso ou Top P, mas não ambos.", + "com_endpoint_openai_topp": "Uma alternativa à amostragem com temperatura, chamada amostragem de núcleo, onde o modelo considera os resultados dos tokens com massa de probabilidade top_p. Então, 0.1 significa que apenas os tokens que compreendem os 10% principais da massa de probabilidade são considerados. Recomendamos alterar isso ou a temperatura, mas não ambos.", + "com_endpoint_output": "Saída", + "com_endpoint_plug_image_detail": "Detalhe da Imagem", + "com_endpoint_plug_resend_files": "Reenviar Arquivos", + "com_endpoint_plug_set_custom_instructions_for_gpt_placeholder": "Defina instruções personalizadas para incluir na Mensagem do Sistema. Padrão: nenhuma", + "com_endpoint_plug_skip_completion": "Pular Conclusão", + "com_endpoint_plug_use_functions": "Usar Funções", + "com_endpoint_presence_penalty": "Penalidade de Presença", + "com_endpoint_preset": "preset", + "com_endpoint_preset_default": "é agora o preset padrão.", + "com_endpoint_preset_default_item": "Padrão:", + "com_endpoint_preset_default_none": "Nenhum preset padrão ativo.", + "com_endpoint_preset_default_removed": "não é mais o preset padrão.", + "com_endpoint_preset_delete_confirm": "Tem certeza de que deseja excluir este preset?", + "com_endpoint_preset_delete_error": "Houve um erro ao excluir seu preset. Por favor, tente novamente.", + "com_endpoint_preset_import": "Preset Importado!", + "com_endpoint_preset_import_error": "Houve um erro ao importar seu preset. Por favor, tente novamente.", + "com_endpoint_preset_name": "Nome do Preset", + "com_endpoint_preset_save_error": "Houve um erro ao salvar seu preset. Por favor, tente novamente.", + "com_endpoint_preset_selected": "Preset Ativo!", + "com_endpoint_preset_selected_title": "Ativo!", + "com_endpoint_preset_title": "Preset", + "com_endpoint_presets": "presets", + "com_endpoint_presets_clear_warning": "Tem certeza de que deseja limpar todos os presets? Isso é irreversível.", + "com_endpoint_prompt_cache": "Usar Cache de Prompt", + "com_endpoint_prompt_prefix": "Instruções Personalizadas", + "com_endpoint_prompt_prefix_assistants": "Instruções Adicionais", + "com_endpoint_prompt_prefix_assistants_placeholder": "Defina instruções ou contexto adicionais além das instruções principais do Assistente. Ignorado se vazio.", + "com_endpoint_prompt_prefix_placeholder": "Defina instruções ou contexto personalizados. Ignorado se vazio.", + "com_endpoint_save_as_preset": "Salvar Como Preset", + "com_endpoint_set_custom_name": "Defina um nome personalizado, caso você possa encontrar este preset", + "com_endpoint_skip_hover": "Habilitar pular a etapa de conclusão, que revisa a resposta final e os passos gerados", + "com_endpoint_stop": "Sequências de Parada", + "com_endpoint_stop_placeholder": "Separe os valores pressionando `Enter`", + "com_endpoint_temperature": "Temperatura", + "com_endpoint_top_k": "Top K", + "com_endpoint_top_p": "Top P", + "com_endpoint_use_active_assistant": "Usar Assistente Ativo", + "com_error_expired_user_key": "A chave fornecida para {{0}} expirou em {{1}}. Por favor, forneça uma nova chave e tente novamente.", + "com_error_input_length": "A contagem de tokens da última mensagem é muito longa, excedendo o limite de tokens ({{0}} respectivamente). Por favor, encurte sua mensagem, ajuste o tamanho máximo do contexto nos parâmetros da conversa ou divida a conversa para continuar.", + "com_error_invalid_user_key": "Chave fornecida inválida. Por favor, forneça uma chave válida e tente novamente.", + "com_error_moderation": "Parece que o conteúdo enviado foi sinalizado pelo nosso sistema de moderação por não estar alinhado com nossas diretrizes da comunidade. Não podemos prosseguir com este tópico específico. Se você tiver outras perguntas ou tópicos que gostaria de explorar, edite sua mensagem ou crie uma nova conversa.", + "com_error_no_base_url": "Nenhuma URL base encontrada. Por favor, forneça uma e tente novamente.", + "com_error_no_user_key": "Nenhuma chave encontrada. Por favor, forneça uma chave e tente novamente.", + "com_files_filter": "Filtrar arquivos...", + "com_files_no_results": "Nenhum resultado.", + "com_files_number_selected": "{{0}} de {{1}} arquivo(s) selecionado(s)", + "com_hide_examples": "Ocultar Exemplos", + "com_nav_account_settings": "Configurações da Conta", + "com_nav_always_make_prod": "Sempre tornar novas versões produção", + "com_nav_archive_created_at": "Data de Arquivamento", + "com_nav_archive_name": "Nome", + "com_nav_archived_chats": "Chats Arquivados", + "com_nav_archived_chats_empty": "Você não tem conversas arquivadas.", + "com_nav_archived_chats_manage": "Gerenciar", + "com_nav_at_command": "Comando @", + "com_nav_at_command_description": "Alternar comando \"@\" para alternar endpoints, modelos, predefinições, etc.", + "com_nav_audio_play_error": "Erro ao reproduzir áudio: {{0}}", + "com_nav_audio_process_error": "Erro ao processar áudio: {{0}}", + "com_nav_auto_scroll": "Rolagem Automática para a última mensagem ao abrir o chat", + "com_nav_auto_send_prompts": "Enviar prompts automaticamente", + "com_nav_auto_send_text": "Enviar texto automaticamente", + "com_nav_auto_send_text_disabled": "definir -1 para desativar", + "com_nav_auto_transcribe_audio": "Transcrever áudio automaticamente", + "com_nav_automatic_playback": "Reprodução Automática da Última Mensagem", + "com_nav_balance": "Equilíbrio", + "com_nav_browser": "Navegador", + "com_nav_buffer_append_error": "Problema com o streaming de áudio. A reprodução pode ser interrompida.", + "com_nav_change_picture": "Mudar foto", + "com_nav_chat_direction": "Direção do chat", + "com_nav_clear_all_chats": "Limpar todos os chats", + "com_nav_clear_conversation": "Limpar conversas", + "com_nav_clear_conversation_confirm_message": "Tem certeza de que deseja limpar todas as conversas? Isso é irreversível.", + "com_nav_close_sidebar": "Fechar barra lateral", + "com_nav_commands": "Comandos", + "com_nav_confirm_clear": "Confirmar Limpeza", + "com_nav_conversation_mode": "Modo de Conversa", + "com_nav_convo_menu_options": "Opções do Menu de Conversa", + "com_nav_db_sensitivity": "Sensibilidade de decibéis", + "com_nav_delete_account": "Excluir conta", + "com_nav_delete_account_button": "Excluir minha conta permanentemente", + "com_nav_delete_account_confirm": "Excluir conta - você tem certeza?", + "com_nav_delete_account_email_placeholder": "Por favor, insira o e-mail da sua conta", + "com_nav_delete_cache_storage": "Excluir armazenamento de cache TTS", + "com_nav_delete_data_info": "Todos os seus dados serão excluídos.", + "com_nav_delete_warning": "AVISO: Isso excluirá permanentemente sua conta.", + "com_nav_edge": "Edge", + "com_nav_enable_cache_tts": "Habilitar cache TTS", + "com_nav_enable_cloud_browser_voice": "Usar vozes baseadas na nuvem", + "com_nav_enabled": "Habilitado", + "com_nav_engine": "Motor", + "com_nav_enter_to_send": "Pressione Enter para enviar mensagens", + "com_nav_export": "Exportar", + "com_nav_export_all_message_branches": "Exportar todos os ramos de mensagens", + "com_nav_export_conversation": "Exportar conversa", + "com_nav_export_filename": "Nome do arquivo", + "com_nav_export_filename_placeholder": "Definir o nome do arquivo", + "com_nav_export_include_endpoint_options": "Incluir opções de endpoint", + "com_nav_export_recursive": "Recursivo", + "com_nav_export_recursive_or_sequential": "Recursivo ou sequencial?", + "com_nav_export_type": "Tipo", + "com_nav_external": "Externo", + "com_nav_font_size": "Tamanho da Fonte da Mensagem", + "com_nav_font_size_base": "Médio", + "com_nav_font_size_lg": "Grande", + "com_nav_font_size_sm": "Pequeno", + "com_nav_font_size_xl": "Extra Grande", + "com_nav_font_size_xs": "Extra Pequeno", + "com_nav_help_faq": "Ajuda & FAQ", + "com_nav_hide_panel": "Ocultar painel mais à direita", + "com_nav_info_code_artifacts": "Habilita a exibição de artefatos de código experimental ao lado do chat", + "com_nav_info_custom_prompt_mode": "Quando habilitado, o prompt padrão do sistema de artefatos não será incluído. Todas as instruções de geração de artefatos devem ser fornecidas manualmente neste modo.", + "com_nav_info_enter_to_send": "Quando habilitado, pressionar `ENTER` enviará sua mensagem. Quando desabilitado, pressionar Enter adicionará uma nova linha, e você precisará pressionar `CTRL + ENTER` / `⌘ + ENTER` para enviar sua mensagem.", + "com_nav_info_fork_change_default": "`Apenas mensagens visíveis` inclui apenas o caminho direto para a mensagem selecionada. `Incluir ramos relacionados` adiciona ramos ao longo do caminho. `Incluir tudo de/para aqui` inclui todas as mensagens e ramos conectados.", + "com_nav_info_fork_split_target_setting": "Quando habilitado, a bifurcação começará da mensagem alvo até a última mensagem na conversa, de acordo com o comportamento selecionado.", + "com_nav_info_include_shadcnui": "Quando habilitado, as instruções para usar componentes shadcn/ui serão incluídas. shadcn/ui é uma coleção de componentes reutilizáveis construídos usando Radix UI e Tailwind CSS. Nota: estas são instruções longas, você deve habilitar apenas se for importante informar o LLM sobre as importações e componentes corretos. Para mais informações sobre esses componentes, visite: https://ui.shadcn.com/", + "com_nav_info_latex_parsing": "Quando habilitado, o código LaTeX nas mensagens será renderizado como equações matemáticas. Desabilitar isso pode melhorar o desempenho se você não precisar de renderização LaTeX.", + "com_nav_info_save_draft": "Quando habilitado, o texto e os anexos que você inserir no formulário de chat serão salvos automaticamente localmente como rascunhos. Esses rascunhos estarão disponíveis mesmo se você recarregar a página ou mudar para uma conversa diferente. Os rascunhos são armazenados localmente no seu dispositivo e são excluídos uma vez que a mensagem é enviada.", + "com_nav_info_user_name_display": "Quando habilitado, o nome de usuário do remetente será mostrado acima de cada mensagem que você enviar. Quando desabilitado, você verá apenas \"Você\" acima de suas mensagens.", + "com_nav_lang_arabic": "العربية", + "com_nav_lang_auto": "Detecção automática", + "com_nav_lang_brazilian_portuguese": "Português Brasileiro", + "com_nav_lang_portuguese": "Português", + "com_nav_lang_chinese": "中文", + "com_nav_lang_estonian": "Eesti keel", + "com_nav_lang_dutch": "Nederlands", + "com_nav_lang_english": "English", + "com_nav_lang_finnish": "Suomi", + "com_nav_lang_french": "Français ", + "com_nav_lang_german": "Deutsch", + "com_nav_lang_hebrew": "עברית", + "com_nav_lang_indonesia": "Indonesia", + "com_nav_lang_italian": "Italiano", + "com_nav_lang_japanese": "日本語", + "com_nav_lang_korean": "한국어", + "com_nav_lang_polish": "Polski", + "com_nav_lang_russian": "Русский", + "com_nav_lang_spanish": "Español", + "com_nav_lang_swedish": "Svenska", + "com_nav_lang_turkish": "Türkçe", + "com_nav_lang_vietnamese": "Tiếng Việt", + "com_nav_lang_traditional_chinese": "繁體中文", + "com_nav_language": "Idioma", + "com_nav_latex_parsing": "Análise de LaTeX em mensagens (pode afetar o desempenho)", + "com_nav_log_out": "Sair", + "com_nav_long_audio_warning": "Textos mais longos levarão mais tempo para processar.", + "com_nav_modular_chat": "Habilitar troca de Endpoints no meio da conversa", + "com_nav_my_files": "Meus Arquivos", + "com_nav_not_supported": "Não Suportado", + "com_nav_open_sidebar": "Abrir barra lateral", + "com_nav_playback_rate": "Taxa de Reprodução de Áudio", + "com_nav_plugin_auth_error": "Houve um erro ao tentar autenticar este plugin. Por favor, tente novamente.", + "com_nav_plugin_install": "Instalar", + "com_nav_plugin_search": "Buscar plugins", + "com_nav_plugin_store": "Loja de Plugins", + "com_nav_plugin_uninstall": "Desinstalar", + "com_nav_plus_command": "Comando +", + "com_nav_plus_command_description": "Alternar comando \"+\" para adicionar uma configuração de resposta múltipla", + "com_nav_profile_picture": "Foto de Perfil", + "com_nav_save_drafts": "Salvar rascunhos localmente", + "com_nav_search_placeholder": "Buscar mensagens", + "com_nav_send_message": "Enviar mensagem", + "com_nav_setting_account": "Conta", + "com_nav_setting_beta": "Recursos beta", + "com_nav_setting_chat": "Chat", + "com_nav_setting_data": "Controles de dados", + "com_nav_setting_general": "Geral", + "com_nav_setting_speech": "Fala", + "com_nav_settings": "Configurações", + "com_nav_shared_links": "Links compartilhados", + "com_nav_shared_links_manage": "Gerenciar", + "com_nav_show_code": "Sempre mostrar código ao usar o interpretador de código", + "com_nav_slash_command": "Comando /", + "com_nav_slash_command_description": "Alternar comando \"/\" para selecionar um prompt via teclado", + "com_nav_source_buffer_error": "Erro ao configurar a reprodução de áudio. Por favor, atualize a página.", + "com_nav_speech_cancel_error": "Não foi possível parar a reprodução de áudio. Você pode precisar atualizar a página.", + "com_nav_speech_to_text": "Fala para Texto", + "com_nav_text_to_speech": "Texto para Fala", + "com_nav_theme": "Tema", + "com_nav_theme_dark": "Escuro", + "com_nav_theme_light": "Claro", + "com_nav_theme_system": "Sistema", + "com_nav_tool_dialog": "Ferramentas do Assistente", + "com_nav_tool_dialog_description": "O assistente deve ser salvo para persistir as seleções de ferramentas.", + "com_nav_tool_remove": "Remover", + "com_nav_tool_search": "Buscar ferramentas", + "com_nav_tts_init_error": "Falha ao inicializar texto-para-fala: {{0}}", + "com_nav_tts_unsupported_error": "Texto-para-fala para o mecanismo selecionado não é suportado neste navegador.", + "com_nav_user": "USUÁRIO", + "com_nav_user_name_display": "Exibir nome de usuário nas mensagens", + "com_nav_voice_select": "Voz", + "com_nav_voices_fetch_error": "Não foi possível recuperar as opções de voz. Por favor, verifique sua conexão com a internet.", + "com_nav_welcome_assistant": "Por favor, Selecione um Assistente", + "com_nav_welcome_message": "Como posso ajudar você hoje?", + "com_show_agent_settings": "Mostrar Configurações do Agente", + "com_show_completion_settings": "Mostrar Configurações de Conclusão", + "com_show_examples": "Mostrar Exemplos", + "com_sidepanel_agent_builder": "Construtor de Agente", + "com_sidepanel_assistant_builder": "Construtor de Assistente", + "com_sidepanel_attach_files": "Anexar Arquivos", + "com_sidepanel_conversation_tags": "Marcadores", + "com_sidepanel_hide_panel": "Ocultar Painel", + "com_sidepanel_manage_files": "Gerenciar Arquivos", + "com_sidepanel_parameters": "Parâmetros", + "com_sidepanel_select_assistant": "Selecionar um Assistente", + "com_ui_accept": "Eu aceito", + "com_ui_add": "Adicionar", + "com_ui_add_model_preset": "Adicionar um modelo ou predefinição para uma resposta adicional", + "com_ui_admin": "Admin", + "com_ui_admin_settings": "Configurações de Admin", + "com_ui_advanced": "Avançado", + "com_ui_agent": "Agente", + "com_ui_agent_delete_error": "Houve um erro ao excluir o agente", + "com_ui_agent_deleted": "Agente excluído com sucesso", + "com_ui_agents": "Agentes", + "com_ui_all": "todos", + "com_ui_all_proper": "Todos", + "com_ui_archive": "Arquivar", + "com_ui_archive_error": "Falha ao arquivar conversa", + "com_ui_artifact_click": "Clique para abrir", + "com_ui_artifacts": "Artefatos", + "com_ui_artifacts_toggle": "Alternar UI de Artefatos", + "com_ui_ascending": "Asc", + "com_ui_assistant": "Assistente", + "com_ui_assistant_delete_error": "Houve um erro ao excluir o assistente", + "com_ui_assistant_deleted": "Assistente excluído com sucesso", + "com_ui_assistants": "Assistentes", + "com_ui_assistants_output": "Saída dos Assistentes", + "com_ui_attach_error": "Não é possível anexar o arquivo. Crie ou selecione uma conversa, ou tente atualizar a página.", + "com_ui_attach_error_openai": "Não é possível anexar arquivos de Assistente a outros endpoints", + "com_ui_attach_error_size": "Limite de tamanho de arquivo excedido para o endpoint:", + "com_ui_attach_error_type": "Tipo de arquivo não suportado para o endpoint:", + "com_ui_attach_warn_endpoint": "Arquivos não compatíveis podem ser ignorados sem uma ferramenta compatível", + "com_ui_attachment": "Anexo", + "com_ui_authentication": "Autenticação", + "com_ui_avatar": "Avatar", + "com_ui_back_to_chat": "Voltar ao Chat", + "com_ui_back_to_prompts": "Voltar aos Prompts", + "com_ui_bookmark_delete_confirm": "Tem certeza de que deseja excluir este favorito?", + "com_ui_bookmarks": "Favoritos", + "com_ui_bookmarks_add_to_conversation": "Adicionar à conversa atual", + "com_ui_bookmarks_count": "Contagem", + "com_ui_bookmarks_create_error": "Houve um erro ao criar o favorito", + "com_ui_bookmarks_create_exists": "Este favorito já existe", + "com_ui_bookmarks_create_success": "Favorito criado com sucesso", + "com_ui_bookmarks_delete_error": "Houve um erro ao excluir o favorito", + "com_ui_bookmarks_delete_success": "Favorito excluído com sucesso", + "com_ui_bookmarks_description": "Descrição", + "com_ui_bookmarks_filter": "Filtrar favoritos...", + "com_ui_bookmarks_new": "Novo Favorito", + "com_ui_bookmarks_title": "Título", + "com_ui_bookmarks_update_error": "Houve um erro ao atualizar o favorito", + "com_ui_bookmarks_update_success": "Favorito atualizado com sucesso", + "com_ui_cancel": "Cancelar", + "com_ui_chat": "Chat", + "com_ui_chat_history": "Histórico de Chat", + "com_ui_clear": "Limpar", + "com_ui_clear_all": "Limpar tudo", + "com_ui_close": "Fechar", + "com_ui_code": "Código", + "com_ui_command_placeholder": "Opcional: Insira um comando para o prompt ou o nome será usado.", + "com_ui_command_usage_placeholder": "Selecione um Prompt por comando ou nome", + "com_ui_confirm_action": "Confirmar Ação", + "com_ui_context": "Contexto", + "com_ui_continue": "Continuar", + "com_ui_controls": "Controles", + "com_ui_copied": "Copiado!", + "com_ui_copied_to_clipboard": "Copiado para a área de transferência", + "com_ui_copy_code": "Copiar código", + "com_ui_copy_link": "Copiar link", + "com_ui_copy_to_clipboard": "Copiar para a área de transferência", + "com_ui_create": "Criar", + "com_ui_create_link": "Criar link", + "com_ui_create_prompt": "Criar Prompt", + "com_ui_custom_prompt_mode": "Modo de Prompt Personalizado", + "com_ui_dashboard": "Painel", + "com_ui_date": "Data", + "com_ui_date_april": "Abril", + "com_ui_date_august": "Agosto", + "com_ui_date_december": "Dezembro", + "com_ui_date_february": "Fevereiro", + "com_ui_date_january": "Janeiro", + "com_ui_date_july": "Julho", + "com_ui_date_june": "Junho", + "com_ui_date_march": "Março", + "com_ui_date_may": "Maio", + "com_ui_date_november": "Novembro", + "com_ui_date_october": "Outubro", + "com_ui_date_previous_30_days": "Últimos 30 dias", + "com_ui_date_previous_7_days": "Últimos 7 dias", + "com_ui_date_september": "Setembro", + "com_ui_date_today": "Hoje", + "com_ui_date_yesterday": "Ontem", + "com_ui_decline": "Eu não aceito", + "com_ui_delete": "Excluir", + "com_ui_delete_action": "Excluir Ação", + "com_ui_delete_action_confirm": "Tem certeza de que deseja excluir esta ação?", + "com_ui_delete_agent_confirm": "Tem certeza de que deseja excluir este agente?", + "com_ui_delete_assistant_confirm": "Tem certeza de que deseja excluir este Assistente? Isso não pode ser desfeito.", + "com_ui_delete_confirm": "Isso excluirá", + "com_ui_delete_confirm_prompt_version_var": "Isso excluirá a versão selecionada para \"{{0}}\". Se não houver outras versões, o prompt será excluído.", + "com_ui_delete_conversation": "Excluir chat?", + "com_ui_delete_prompt": "Excluir Prompt?", + "com_ui_delete_tool": "Excluir Ferramenta", + "com_ui_delete_tool_confirm": "Tem certeza de que deseja excluir esta ferramenta?", + "com_ui_descending": "Desc", + "com_ui_description": "Descrição", + "com_ui_description_placeholder": "Opcional: Insira uma descrição para exibir para o prompt", + "com_ui_download_error": "Erro ao baixar o arquivo. O arquivo pode ter sido excluído.", + "com_ui_dropdown_variables": "Variáveis de dropdown:", + "com_ui_dropdown_variables_info": "Crie menus dropdown personalizados para seus prompts: `{{nome_da_variável:opção1|opção2|opção3}}`", + "com_ui_edit": "Editar", + "com_ui_endpoint": "Endpoint", + "com_ui_enter": "Entrar", + "com_ui_enter_var": "Inserir {{0}}", + "com_ui_error": "Erro", + "com_ui_error_connection": "Erro ao conectar ao servidor, tente atualizar a página.", + "com_ui_error_save_admin_settings": "Houve um erro ao salvar suas configurações de admin.", + "com_ui_examples": "Exemplos", + "com_ui_field_required": "Este campo é obrigatório", + "com_ui_filter_prompts_name": "Filtrar prompts por nome", + "com_ui_fork": "Bifurcar", + "com_ui_fork_all_target": "Incluir todos para/de aqui", + "com_ui_fork_branches": "Incluir ramificações relacionadas", + "com_ui_fork_change_default": "Opção de bifurcação padrão", + "com_ui_fork_default": "Usar opção de bifurcação padrão", + "com_ui_fork_error": "Houve um erro ao bifurcar a conversa", + "com_ui_fork_from_message": "Selecione uma opção de bifurcação", + "com_ui_fork_info_1": "Use esta configuração para bifurcar mensagens com o comportamento desejado.", + "com_ui_fork_info_2": "\"Bifurcação\" refere-se à criação de uma nova conversa que começa/termina a partir de mensagens específicas na conversa atual, criando uma cópia de acordo com as opções selecionadas.", + "com_ui_fork_info_3": "A \"mensagem alvo\" refere-se à mensagem da qual este popup foi aberto, ou, se você marcar \"{{0}}\", a última mensagem na conversa.", + "com_ui_fork_info_branches": "Esta opção bifurca as mensagens visíveis, junto com ramificações relacionadas; em outras palavras, o caminho direto para a mensagem alvo, incluindo ramificações ao longo do caminho.", + "com_ui_fork_info_remember": "Marque isto para lembrar as opções que você seleciona para uso futuro, tornando mais rápido bifurcar conversas conforme preferido.", + "com_ui_fork_info_start": "Se marcado, a bifurcação começará desta mensagem até a última mensagem na conversa, de acordo com o comportamento selecionado acima.", + "com_ui_fork_info_target": "Esta opção bifurca todas as mensagens até a mensagem alvo, incluindo seus vizinhos; em outras palavras, todos os ramos de mensagens, estejam ou não visíveis ou ao longo do mesmo caminho, estão incluídos.", + "com_ui_fork_info_visible": "Esta opção bifurca apenas as mensagens visíveis; em outras palavras, o caminho direto para a mensagem alvo, sem quaisquer ramificações.", + "com_ui_fork_processing": "Bifurcando conversa...", + "com_ui_fork_remember": "Lembrar", + "com_ui_fork_remember_checked": "Sua seleção será lembrada após o uso. Altere isso a qualquer momento nas configurações.", + "com_ui_fork_split_target": "Iniciar bifurcação aqui", + "com_ui_fork_split_target_setting": "Iniciar bifurcação a partir da mensagem alvo por padrão", + "com_ui_fork_success": "Conversa bifurcada com sucesso", + "com_ui_fork_visible": "Apenas mensagens visíveis", + "com_ui_go_to_conversation": "Ir para a conversa", + "com_ui_happy_birthday": "É meu 1º aniversário!", + "com_ui_host": "Host", + "com_ui_image_gen": "Geração de Imagem", + "com_ui_import_conversation": "Importar", + "com_ui_import_conversation_error": "Houve um erro ao importar suas conversas", + "com_ui_import_conversation_file_type_error": "Tipo de importação não suportado", + "com_ui_import_conversation_info": "Importar conversas de um arquivo JSON", + "com_ui_import_conversation_success": "Conversas importadas com sucesso", + "com_ui_include_shadcnui": "Incluir instruções de componentes shadcn/ui", + "com_ui_input": "Entrada", + "com_ui_instructions": "Instruções", + "com_ui_latest_footer": "Toda IA para Todos.", + "com_ui_locked": "Bloqueado", + "com_ui_manage": "Gerenciar", + "com_ui_max_tags": "O número máximo permitido é {{0}}, usando os valores mais recentes.", + "com_ui_mention": "Mencione um endpoint, assistente ou predefinição para alternar rapidamente para ele", + "com_ui_min_tags": "Não é possível remover mais valores, um mínimo de {{0}} é necessário.", + "com_ui_model": "Modelo", + "com_ui_model_parameters": "Parâmetros do Modelo", + "com_ui_my_prompts": "Meus Prompts", + "com_ui_name": "Nome", + "com_ui_new_chat": "Novo chat", + "com_ui_next": "Próximo", + "com_ui_no": "Não", + "com_ui_no_bookmarks": "Parece que você ainda não tem favoritos. Clique em um chat e adicione um novo", + "com_ui_no_category": "Sem categoria", + "com_ui_no_terms_content": "Nenhum conteúdo de termos e condições para exibir", + "com_ui_none_selected": "Nenhum selecionado", + "com_ui_nothing_found": "Nada encontrado", + "com_ui_of": "de", + "com_ui_off": "Desligado", + "com_ui_on": "Ligado", + "com_ui_prev": "Anterior", + "com_ui_preview": "Pré-visualizar", + "com_ui_privacy_policy": "Política de Privacidade", + "com_ui_prompt": "Prompt", + "com_ui_prompt_already_shared_to_all": "Este prompt já está compartilhado com todos os usuários", + "com_ui_prompt_name": "Nome do Prompt", + "com_ui_prompt_name_required": "Nome do Prompt é obrigatório", + "com_ui_prompt_preview_not_shared": "O autor não permitiu colaboração para este prompt.", + "com_ui_prompt_text": "Texto", + "com_ui_prompt_text_required": "Texto é obrigatório", + "com_ui_prompt_update_error": "Houve um erro ao atualizar o prompt", + "com_ui_prompts": "Prompts", + "com_ui_prompts_allow_create": "Permitir criação de Prompts", + "com_ui_prompts_allow_share_global": "Permitir compartilhamento de Prompts com todos os usuários", + "com_ui_prompts_allow_use": "Permitir uso de Prompts", + "com_ui_provider": "Provedor", + "com_ui_read_aloud": "Ler em voz alta", + "com_ui_regenerate": "Regenerar", + "com_ui_rename": "Renomear", + "com_ui_result": "Resultado", + "com_ui_revoke": "Revogar", + "com_ui_revoke_info": "Revogar todas as credenciais fornecidas pelo usuário", + "com_ui_save": "Salvar", + "com_ui_save_submit": "Salvar & Enviar", + "com_ui_saved": "Salvo!", + "com_ui_select": "Selecionar", + "com_ui_select_file": "Selecionar um arquivo", + "com_ui_select_model": "Selecionar um modelo", + "com_ui_select_provider": "Selecionar um provedor", + "com_ui_select_provider_first": "Selecione um provedor primeiro", + "com_ui_select_search_model": "Pesquisar modelo por nome", + "com_ui_select_search_plugin": "Pesquisar plugin por nome", + "com_ui_share": "Compartilhar", + "com_ui_share_create_message": "Seu nome e quaisquer mensagens que você adicionar após o compartilhamento permanecerão privadas.", + "com_ui_share_delete_error": "Houve um erro ao excluir o link compartilhado", + "com_ui_share_error": "Houve um erro ao compartilhar o link do chat", + "com_ui_share_link_to_chat": "Compartilhar link para o chat", + "com_ui_share_to_all_users": "Compartilhar com todos os usuários", + "com_ui_share_update_message": "Seu nome, instruções personalizadas e quaisquer mensagens que você adicionar após o compartilhamento permanecerão privadas.", + "com_ui_share_var": "Compartilhar {{0}}", + "com_ui_shared_link_not_found": "Link compartilhado não encontrado", + "com_ui_shared_prompts": "Prompts Compartilhados", + "com_ui_show_all": "Mostrar Todos", + "com_ui_simple": "Simples", + "com_ui_size": "Tamanho", + "com_ui_special_variables": "Variáveis especiais:", + "com_ui_special_variables_info": "Use `{{current_date}}` para a data atual, e `{{current_user}}` para o nome da sua conta.", + "com_ui_stop": "Parar", + "com_ui_storage": "Armazenamento", + "com_ui_submit": "Enviar", + "com_ui_terms_and_conditions": "Termos e Condições", + "com_ui_terms_of_service": "Termos de Serviço", + "com_ui_tools": "Ferramentas", + "com_ui_unarchive": "Desarquivar", + "com_ui_unarchive_error": "Falha ao desarquivar conversa", + "com_ui_unknown": "Desconhecido", + "com_ui_update": "Atualizar", + "com_ui_upload": "Carregar", + "com_ui_upload_delay": "O upload de \"{{0}}\" está demorando mais do que o esperado. Por favor, aguarde enquanto o arquivo termina de ser indexado para recuperação.", + "com_ui_upload_error": "Houve um erro ao carregar seu arquivo", + "com_ui_upload_files": "Carregar arquivos", + "com_ui_upload_image": "Carregar uma imagem", + "com_ui_upload_invalid": "Arquivo inválido para upload. Deve ser uma imagem não excedendo o limite", + "com_ui_upload_invalid_var": "Arquivo inválido para upload. Deve ser uma imagem não excedendo {{0}} MB", + "com_ui_upload_success": "Arquivo carregado com sucesso", + "com_ui_use_micrphone": "Usar microfone", + "com_ui_use_prompt": "Usar prompt", + "com_ui_variables": "Variáveis", + "com_ui_variables_info": "Use chaves duplas no seu texto para criar variáveis, por exemplo, `{{exemplo de variável}}`, para preencher posteriormente ao usar o prompt.", + "com_ui_version_var": "Versão {{0}}", + "com_ui_versions": "Versões", + "com_ui_yes": "Sim", + "com_user_message": "Você" +} \ No newline at end of file diff --git a/client/src/locales/ru/translation.json b/client/src/locales/ru/translation.json index bfad6b16c2..90b7ff4beb 100644 --- a/client/src/locales/ru/translation.json +++ b/client/src/locales/ru/translation.json @@ -333,7 +333,9 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "Автоопределение", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", + "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", "com_nav_lang_finnish": "Suomi", @@ -350,6 +352,7 @@ "com_nav_lang_swedish": "Svenska", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_language": "Локализация", "com_nav_latex_parsing": "Обработка LaTeX в сообщениях (может повлиять на производительность)", "com_nav_log_out": "Выйти", diff --git a/client/src/locales/sv/translation.json b/client/src/locales/sv/translation.json index 7463a90de2..c3224ccec8 100644 --- a/client/src/locales/sv/translation.json +++ b/client/src/locales/sv/translation.json @@ -136,7 +136,9 @@ "com_nav_help_faq": "Hjälp & Vanliga frågor", "com_nav_lang_arabic": "العربية", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", + "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", "com_nav_lang_finnish": "Suomi", @@ -153,6 +155,7 @@ "com_nav_lang_swedish": "Svenska", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_log_out": "Logga ut", "com_nav_not_supported": "Stöds ej", "com_nav_open_sidebar": "Öppna sidofält", diff --git a/client/src/locales/tr/translation.json b/client/src/locales/tr/translation.json index daf714ba93..7cd2c78aef 100644 --- a/client/src/locales/tr/translation.json +++ b/client/src/locales/tr/translation.json @@ -337,7 +337,9 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "Auto detect", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", + "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", "com_nav_lang_finnish": "Suomi", @@ -354,6 +356,7 @@ "com_nav_lang_swedish": "Svenska", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_language": "Dil", "com_nav_latex_parsing": "Mesajlarda LaTeX işleme (performansı etkileyebilir)", "com_nav_log_out": "Çıkış yap", diff --git a/client/src/locales/vi/translation.json b/client/src/locales/vi/translation.json index 40a65d7f96..ef33076020 100644 --- a/client/src/locales/vi/translation.json +++ b/client/src/locales/vi/translation.json @@ -134,7 +134,9 @@ "com_nav_help_faq": "Trợ giúp & Câu hỏi thường gặp", "com_nav_lang_arabic": "العربية", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", + "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", "com_nav_lang_finnish": "Suomi", @@ -151,6 +153,7 @@ "com_nav_lang_swedish": "Svenska", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_log_out": "Đăng xuất", "com_nav_not_supported": "Không được hỗ trợ", "com_nav_open_sidebar": "Mở thanh bên", diff --git a/client/src/locales/zh/translation.json b/client/src/locales/zh-Hans/translation.json similarity index 99% rename from client/src/locales/zh/translation.json rename to client/src/locales/zh-Hans/translation.json index 03ee06a8bf..36ae7f6e02 100644 --- a/client/src/locales/zh/translation.json +++ b/client/src/locales/zh-Hans/translation.json @@ -339,7 +339,9 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "自动检测语言", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", + "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", "com_nav_lang_finnish": "Suomi", @@ -355,6 +357,7 @@ "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", "com_nav_lang_turkish": "Türkçe", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_lang_vietnamese": "Tiếng Việt", "com_nav_language": "语言", "com_nav_latex_parsing": "解析消息中的 LaTeX(可能会影响性能)", diff --git a/client/src/locales/zh-Hant/translation.json b/client/src/locales/zh-Hant/translation.json index 835ccbffe7..97068a8704 100644 --- a/client/src/locales/zh-Hant/translation.json +++ b/client/src/locales/zh-Hant/translation.json @@ -333,6 +333,8 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "自動偵測", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", + "com_nav_lang_portuguese": "Português", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_chinese": "中文", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", @@ -350,6 +352,7 @@ "com_nav_lang_swedish": "Svenska", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_language": "語言", "com_nav_latex_parsing": "解析訊息中的 LaTeX 內容(可能影響效能)", "com_nav_log_out": "登出", diff --git a/package-lock.json b/package-lock.json index 3b72a2f66f..8601c651e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1634,6 +1634,7 @@ "framer-motion": "^11.5.4", "html-to-image": "^1.11.11", "i18next": "^24.2.2", + "i18next-browser-languagedetector": "^8.0.3", "js-cookie": "^3.0.5", "librechat-data-provider": "*", "lodash": "^4.17.21", @@ -23287,6 +23288,15 @@ } } }, + "node_modules/i18next-browser-languagedetector": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.3.tgz", + "integrity": "sha512-beOOLArattPBc2YZG5IXGJytdYFgUR7cS8Wd6HT4IczIoWKgmTspOQ2yasaGklelVo5seLPmnEKvLHR+E/MdWQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, "node_modules/ibm-cloud-sdk-core": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ibm-cloud-sdk-core/-/ibm-cloud-sdk-core-5.1.0.tgz", diff --git a/packages/data-provider/src/types.ts b/packages/data-provider/src/types.ts index 6d9cd87c88..bf31a48cc0 100644 --- a/packages/data-provider/src/types.ts +++ b/packages/data-provider/src/types.ts @@ -82,6 +82,7 @@ export type TUpdateUserPlugins = { auth?: unknown; }; +// TODO `label` needs to be changed to the proper `TranslationKeys` export type TCategory = { id?: string; value: string; From 61f0480b57fbe239b158ff98d80feca9df2e17ff Mon Sep 17 00:00:00 2001 From: Ruben Talstra Date: Fri, 14 Feb 2025 16:52:59 +0100 Subject: [PATCH 07/23] =?UTF-8?q?=F0=9F=90=9E=20i18n:=20Remove=20Debug=20M?= =?UTF-8?q?ode=20(#5879)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/locales/i18n.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/locales/i18n.ts b/client/src/locales/i18n.ts index 03d7605d32..3a8c2a92fb 100644 --- a/client/src/locales/i18n.ts +++ b/client/src/locales/i18n.ts @@ -65,7 +65,7 @@ i18n }, fallbackNS: 'translation', ns: ['translation'], - debug: true, + debug: false, defaultNS, resources, interpolation: { escapeValue: false }, From e3b5c599495c917a46d2f09361915351c85cfe65 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Fri, 14 Feb 2025 11:37:41 -0500 Subject: [PATCH 08/23] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20fix:=20File=20Config?= =?UTF-8?q?=20Handling=20(revisited)=20(#5881)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: improve file handling by preventing memoization issues, providing config values at run time * 🌍 i18n: Update translation.json with latest translations --- client/src/hooks/Files/useFileHandling.ts | 117 ++------------ client/src/locales/ar/translation.json | 6 +- client/src/locales/de/translation.json | 6 +- client/src/locales/en/translation.json | 24 +-- client/src/locales/es/translation.json | 6 +- client/src/locales/et/translation.json | 6 +- client/src/locales/fi/translation.json | 6 +- client/src/locales/fr/translation.json | 6 +- client/src/locales/he/translation.json | 6 +- client/src/locales/id/translation.json | 6 +- client/src/locales/it/translation.json | 6 +- client/src/locales/ja/translation.json | 6 +- client/src/locales/ko/translation.json | 6 +- client/src/locales/nl/translation.json | 7 +- client/src/locales/pl/translation.json | 6 +- client/src/locales/pt-BR/translation.json | 6 +- client/src/locales/pt-PT/translation.json | 164 +++++++++++++++++++- client/src/locales/ru/translation.json | 6 +- client/src/locales/sv/translation.json | 7 +- client/src/locales/tr/translation.json | 6 +- client/src/locales/vi/translation.json | 7 +- client/src/locales/zh-Hans/translation.json | 6 +- client/src/locales/zh-Hant/translation.json | 6 +- client/src/utils/files.ts | 100 +++++++++++- 24 files changed, 348 insertions(+), 180 deletions(-) diff --git a/client/src/hooks/Files/useFileHandling.ts b/client/src/hooks/Files/useFileHandling.ts index a6ec375945..69a99fef33 100644 --- a/client/src/hooks/Files/useFileHandling.ts +++ b/client/src/hooks/Files/useFileHandling.ts @@ -1,12 +1,10 @@ import { v4 } from 'uuid'; import debounce from 'lodash/debounce'; import { useQueryClient } from '@tanstack/react-query'; -import { useState, useEffect, useCallback, useRef, useMemo } from 'react'; +import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react'; import { - megabyte, QueryKeys, EModelEndpoint, - codeTypeMapping, mergeFileConfig, isAgentsEndpoint, isAssistantsEndpoint, @@ -16,14 +14,12 @@ import { import type { TEndpointsConfig, TError } from 'librechat-data-provider'; import type { ExtendedFile, FileSetter } from '~/common'; import { useUploadFileMutation, useGetFileConfig } from '~/data-provider'; +import useLocalize, { TranslationKeys } from '~/hooks/useLocalize'; import { useDelayedUploadToast } from './useDelayedUploadToast'; import { useToastContext } from '~/Providers/ToastContext'; import { useChatContext } from '~/Providers/ChatContext'; -import useLocalize from '~/hooks/useLocalize'; +import { logger, validateFiles } from '~/utils'; import useUpdateFiles from './useUpdateFiles'; -import { logger } from '~/utils'; - -const { checkType } = defaultFileConfig; type UseFileHandling = { overrideEndpoint?: EModelEndpoint; @@ -58,20 +54,11 @@ const useFileHandling = (params?: UseFileHandling) => { [params?.overrideEndpoint, conversation?.endpointType, conversation?.endpoint], ); - const { fileLimit, fileSizeLimit, totalSizeLimit, supportedMimeTypes } = useMemo( - () => - fileConfig?.endpoints[endpoint] ?? - fileConfig?.endpoints.default ?? - defaultFileConfig.endpoints[endpoint] ?? - defaultFileConfig.endpoints.default, - [fileConfig, endpoint], - ); - const displayToast = useCallback(() => { if (errors.length > 1) { // TODO: this should not be a dynamic localize input!! const errorList = Array.from(new Set(errors)) - .map((e, i) => `${i > 0 ? '• ' : ''}${localize(e) || e}\n`) + .map((e, i) => `${i > 0 ? '• ' : ''}${localize(e as TranslationKeys) || e}\n`) .join(''); showToast({ message: errorList, @@ -80,7 +67,7 @@ const useFileHandling = (params?: UseFileHandling) => { }); } else if (errors.length === 1) { // TODO: this should not be a dynamic localize input!! - const message = localize(errors[0]) || errors[0]; + const message = localize(errors[0] as TranslationKeys) || errors[0]; showToast({ message, status: 'error', @@ -147,7 +134,7 @@ const useFileHandling = (params?: UseFileHandling) => { const errorMessage = error?.code === 'ERR_CANCELED' ? 'com_error_files_upload_canceled' - : error?.response?.data?.message ?? 'com_error_files_upload'; + : (error?.response?.data?.message ?? 'com_error_files_upload'); setError(errorMessage); }, }, @@ -228,87 +215,6 @@ const useFileHandling = (params?: UseFileHandling) => { uploadFile.mutate(formData); }; - const validateFiles = useCallback( - (fileList: File[]) => { - const existingFiles = Array.from(files.values()); - const incomingTotalSize = fileList.reduce((total, file) => total + file.size, 0); - if (incomingTotalSize === 0) { - setError('com_error_files_empty'); - return false; - } - const currentTotalSize = existingFiles.reduce((total, file) => total + file.size, 0); - - if (fileList.length + files.size > fileLimit) { - setError(`You can only upload up to ${fileLimit} files at a time.`); - return false; - } - - for (let i = 0; i < fileList.length; i++) { - let originalFile = fileList[i]; - let fileType = originalFile.type; - const extension = originalFile.name.split('.').pop() ?? ''; - const knownCodeType = codeTypeMapping[extension]; - - // Infer MIME type for Known Code files when the type is empty or a mismatch - if (knownCodeType && (!fileType || fileType !== knownCodeType)) { - fileType = knownCodeType; - } - - // Check if the file type is still empty after the extension check - if (!fileType) { - setError('Unable to determine file type for: ' + originalFile.name); - return false; - } - - // Replace empty type with inferred type - if (originalFile.type !== fileType) { - const newFile = new File([originalFile], originalFile.name, { type: fileType }); - originalFile = newFile; - fileList[i] = newFile; - } - - if (!checkType(originalFile.type, supportedMimeTypes)) { - console.log(originalFile); - setError('Currently, unsupported file type: ' + originalFile.type); - return false; - } - - if (originalFile.size >= fileSizeLimit) { - setError(`File size exceeds ${fileSizeLimit / megabyte} MB.`); - return false; - } - } - - if (currentTotalSize + incomingTotalSize > totalSizeLimit) { - setError(`The total size of the files cannot exceed ${totalSizeLimit / megabyte} MB.`); - return false; - } - - const combinedFilesInfo = [ - ...existingFiles.map( - (file) => - `${file.file?.name ?? file.filename}-${file.size}-${ - file.type?.split('/')[0] ?? 'file' - }`, - ), - ...fileList.map( - (file: File | undefined) => - `${file?.name}-${file?.size}-${file?.type.split('/')[0] ?? 'file'}`, - ), - ]; - - const uniqueFilesSet = new Set(combinedFilesInfo); - - if (uniqueFilesSet.size !== combinedFilesInfo.length) { - setError('com_error_files_dupe'); - return false; - } - - return true; - }, - [files, fileLimit, fileSizeLimit, totalSizeLimit, supportedMimeTypes], - ); - const loadImage = (extendedFile: ExtendedFile, preview: string) => { const img = new Image(); img.onload = async () => { @@ -332,7 +238,16 @@ const useFileHandling = (params?: UseFileHandling) => { /* Validate files */ let filesAreValid: boolean; try { - filesAreValid = validateFiles(fileList); + filesAreValid = validateFiles({ + files, + fileList, + setError, + endpointFileConfig: + fileConfig?.endpoints[endpoint] ?? + fileConfig?.endpoints.default ?? + defaultFileConfig.endpoints[endpoint] ?? + defaultFileConfig.endpoints.default, + }); } catch (error) { console.error('file validation error', error); setError('com_error_files_validation'); diff --git a/client/src/locales/ar/translation.json b/client/src/locales/ar/translation.json index f3add85df2..54824fd1a7 100644 --- a/client/src/locales/ar/translation.json +++ b/client/src/locales/ar/translation.json @@ -333,11 +333,10 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "اكتشاف تلقائي", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", - "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", - "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_finnish": "Suomi", "com_nav_lang_french": "Français ", "com_nav_lang_german": "Deutsch", @@ -347,12 +346,13 @@ "com_nav_lang_japanese": "日本語", "com_nav_lang_korean": "한국어", "com_nav_lang_polish": "Polski", + "com_nav_lang_portuguese": "Português", "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", - "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_language": "اللغة", "com_nav_latex_parsing": "تحليل LaTeX في الرسائل (قد يؤثر على الأداء)", "com_nav_log_out": "تسجيل الخروج", diff --git a/client/src/locales/de/translation.json b/client/src/locales/de/translation.json index a38e05a468..e02657b4b4 100644 --- a/client/src/locales/de/translation.json +++ b/client/src/locales/de/translation.json @@ -340,11 +340,10 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "Automatisch erkennen", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", - "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", - "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_finnish": "Suomi", "com_nav_lang_french": "Français ", "com_nav_lang_german": "Deutsch", @@ -354,12 +353,13 @@ "com_nav_lang_japanese": "日本語", "com_nav_lang_korean": "한국어", "com_nav_lang_polish": "Polski", + "com_nav_lang_portuguese": "Português", "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", - "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_language": "Sprache", "com_nav_latex_parsing": "LaTeX in Nachrichten parsen (kann die Leistung beeinflussen)", "com_nav_log_out": "Abmelden", diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index c123d67f17..1daa6794d6 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -345,11 +345,10 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "Auto detect", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", - "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", - "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_finnish": "Suomi", "com_nav_lang_french": "Français ", "com_nav_lang_german": "Deutsch", @@ -359,6 +358,7 @@ "com_nav_lang_japanese": "日本語", "com_nav_lang_korean": "한국어", "com_nav_lang_polish": "Polski", + "com_nav_lang_portuguese": "Português", "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", @@ -587,6 +587,7 @@ "com_ui_duplication_processing": "Duplicating conversation...", "com_ui_duplication_success": "Successfully duplicated conversation", "com_ui_edit": "Edit", + "com_ui_empty_category": "-", "com_ui_endpoint": "Endpoint", "com_ui_endpoint_menu": "LLM Endpoint Menu", "com_ui_endpoints_available": "Available Endpoints", @@ -602,6 +603,7 @@ "com_ui_field_required": "This field is required", "com_ui_filter_prompts": "Filter Prompts", "com_ui_filter_prompts_name": "Filter prompts by name", + "com_ui_finance": "Finance", "com_ui_fork": "Fork", "com_ui_fork_all_target": "Include all to/from here", "com_ui_fork_branches": "Include related branches", @@ -630,6 +632,7 @@ "com_ui_happy_birthday": "It's my 1st birthday!", "com_ui_hide_qr": "Hide QR Code", "com_ui_host": "Host", + "com_ui_idea": "Ideas", "com_ui_image_gen": "Image Gen", "com_ui_import_conversation": "Import", "com_ui_import_conversation_error": "There was an error importing your conversations", @@ -648,12 +651,14 @@ "com_ui_librechat_code_api_title": "Run AI Code", "com_ui_llm_menu": "LLM Menu", "com_ui_llms_available": "Available LLMs", + "com_ui_loading": "Loading...", "com_ui_locked": "Locked", "com_ui_logo": "{{0}} Logo", "com_ui_manage": "Manage", "com_ui_max_tags": "Maximum number allowed is {{0}}, using latest values.", "com_ui_mention": "Mention an endpoint, assistant, or preset to quickly switch to it", "com_ui_min_tags": "Cannot remove more values, a minimum of {{0}} are required.", + "com_ui_misc": "Misc.", "com_ui_model": "Model", "com_ui_model_parameters": "Model Parameters", "com_ui_more_info": "More info", @@ -710,6 +715,7 @@ "com_ui_revoke_keys": "Revoke Keys", "com_ui_revoke_keys_confirm": "Are you sure you want to revoke all keys?", "com_ui_role_select": "Role", + "com_ui_roleplay": "Roleplay", "com_ui_run_code": "Run Code", "com_ui_run_code_error": "There was an error running the code", "com_ui_save": "Save", @@ -741,6 +747,7 @@ "com_ui_shared_link_delete_success": "Successfully deleted shared link", "com_ui_shared_link_not_found": "Shared link not found", "com_ui_shared_prompts": "Shared Prompts", + "com_ui_shop": "Shopping", "com_ui_show_all": "Show All", "com_ui_show_qr": "Show QR Code", "com_ui_sign_in_to_domain": "Sign-in to {{0}}", @@ -752,6 +759,7 @@ "com_ui_stop": "Stop", "com_ui_storage": "Storage", "com_ui_submit": "Submit", + "com_ui_teach_or_explain": "Learning", "com_ui_temporary_chat": "Temporary Chat", "com_ui_terms_and_conditions": "Terms and Conditions", "com_ui_terms_of_service": "Terms of service", @@ -760,6 +768,7 @@ "com_ui_token_exchange_method": "Token Exchange Method", "com_ui_token_url": "Token URL", "com_ui_tools": "Tools", + "com_ui_travel": "Travel", "com_ui_unarchive": "Unarchive", "com_ui_unarchive_error": "Failed to unarchive conversation", "com_ui_unknown": "Unknown", @@ -783,18 +792,9 @@ "com_ui_version_var": "Version {{0}}", "com_ui_versions": "Versions", "com_ui_view_source": "View source chat", + "com_ui_write": "Writing", "com_ui_yes": "Yes", "com_ui_zoom": "Zoom", "com_user_message": "You", - "com_ui_loading": "Loading...", - "com_ui_finance": "Finance", - "com_ui_idea": "Ideas", - "com_ui_misc": "Misc.", - "com_ui_roleplay": "Roleplay", - "com_ui_shop": "Shopping", - "com_ui_teach_or_explain": "Learning", - "com_ui_travel": "Travel", - "com_ui_write": "Writing", - "com_ui_empty_category": "-", "com_warning_resubmit_unsupported": "Resubmitting the AI message is not supported for this endpoint." } \ No newline at end of file diff --git a/client/src/locales/es/translation.json b/client/src/locales/es/translation.json index 299bbea3b8..a662d76452 100644 --- a/client/src/locales/es/translation.json +++ b/client/src/locales/es/translation.json @@ -333,11 +333,10 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "Detección automática", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", - "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", - "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_finnish": "Suomi", "com_nav_lang_french": "Français ", "com_nav_lang_german": "Deutsch", @@ -347,12 +346,13 @@ "com_nav_lang_japanese": "日本語", "com_nav_lang_korean": "한국어", "com_nav_lang_polish": "Polski", + "com_nav_lang_portuguese": "Português", "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", - "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_language": "Idioma", "com_nav_latex_parsing": "Analizar LaTeX en los mensajes (puede afectar el rendimiento)", "com_nav_log_out": "Cerrar sesión", diff --git a/client/src/locales/et/translation.json b/client/src/locales/et/translation.json index 5d2c40d96a..cbf616cec1 100644 --- a/client/src/locales/et/translation.json +++ b/client/src/locales/et/translation.json @@ -345,11 +345,10 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "Tuvasta automaatselt", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", - "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", - "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_finnish": "Suomi", "com_nav_lang_french": "Français ", "com_nav_lang_german": "Deutsch", @@ -359,12 +358,13 @@ "com_nav_lang_japanese": "日本語", "com_nav_lang_korean": "한국어", "com_nav_lang_polish": "Polski", + "com_nav_lang_portuguese": "Português", "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", - "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_language": "Keel", "com_nav_latex_parsing": "LaTeXi parsimine sõnumites (võib mõjutada jõudlust)", "com_nav_log_out": "Logi välja", diff --git a/client/src/locales/fi/translation.json b/client/src/locales/fi/translation.json index 709a77e762..3005dcfb6b 100644 --- a/client/src/locales/fi/translation.json +++ b/client/src/locales/fi/translation.json @@ -270,11 +270,10 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "Tunnista automaattisesti", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", - "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", - "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_finnish": "Suomi", "com_nav_lang_french": "Français ", "com_nav_lang_german": "Deutsch", @@ -284,12 +283,13 @@ "com_nav_lang_japanese": "日本語", "com_nav_lang_korean": "한국어", "com_nav_lang_polish": "Polski", + "com_nav_lang_portuguese": "Português", "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", - "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_language": "Kieli", "com_nav_latex_parsing": "Tulkitse LaTeX:ia viesteissä (saattaa vaikuttaa suoritustehoon)", "com_nav_log_out": "Kirjaudu ulos", diff --git a/client/src/locales/fr/translation.json b/client/src/locales/fr/translation.json index 2a55e52a8b..ccfae170d2 100644 --- a/client/src/locales/fr/translation.json +++ b/client/src/locales/fr/translation.json @@ -337,11 +337,10 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "Détection automatique", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", - "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", - "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_finnish": "Suomi", "com_nav_lang_french": "Français ", "com_nav_lang_german": "Deutsch", @@ -351,12 +350,13 @@ "com_nav_lang_japanese": "日本語", "com_nav_lang_korean": "한국어", "com_nav_lang_polish": "Polski", + "com_nav_lang_portuguese": "Português", "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", - "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_language": "Langue", "com_nav_latex_parsing": "Analyse LaTeX dans les messages (peut affecter les performances)", "com_nav_log_out": "Se déconnecter", diff --git a/client/src/locales/he/translation.json b/client/src/locales/he/translation.json index bf58f26929..3c2f1f6317 100644 --- a/client/src/locales/he/translation.json +++ b/client/src/locales/he/translation.json @@ -191,11 +191,10 @@ "com_nav_hide_panel": "הסתר לוח הצד הימני ביותר", "com_nav_lang_arabic": "العربية", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", - "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", - "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_finnish": "Suomi", "com_nav_lang_french": "Français ", "com_nav_lang_german": "Deutsch", @@ -205,12 +204,13 @@ "com_nav_lang_japanese": "日本語", "com_nav_lang_korean": "한국어", "com_nav_lang_polish": "Polski", + "com_nav_lang_portuguese": "Português", "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", - "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_latex_parsing": "ניתוח LaTeX בהודעות (עשוי להשפיע על הביצועים)", "com_nav_log_out": "צא", "com_nav_modular_chat": "אפשר החלפת נקודות קצה באמצע שיחה", diff --git a/client/src/locales/id/translation.json b/client/src/locales/id/translation.json index 9d90275f95..e5d1a23594 100644 --- a/client/src/locales/id/translation.json +++ b/client/src/locales/id/translation.json @@ -166,11 +166,10 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "Deteksi otomatis", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", - "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", - "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_finnish": "Suomi", "com_nav_lang_french": "Français ", "com_nav_lang_german": "Deutsch", @@ -180,12 +179,13 @@ "com_nav_lang_japanese": "日本語", "com_nav_lang_korean": "한국어", "com_nav_lang_polish": "Polski", + "com_nav_lang_portuguese": "Português", "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", - "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_language": "Bahasa", "com_nav_latex_parsing": "Parsing LaTeX dalam pesan (dapat memengaruhi kinerja)", "com_nav_log_out": "Keluar", diff --git a/client/src/locales/it/translation.json b/client/src/locales/it/translation.json index 399e7cadd9..45c26e11e2 100644 --- a/client/src/locales/it/translation.json +++ b/client/src/locales/it/translation.json @@ -337,11 +337,10 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "Rileva automaticamente", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", - "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", - "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_finnish": "Suomi", "com_nav_lang_french": "Français ", "com_nav_lang_german": "Deutsch", @@ -351,12 +350,13 @@ "com_nav_lang_japanese": "日本語", "com_nav_lang_korean": "한국어", "com_nav_lang_polish": "Polski", + "com_nav_lang_portuguese": "Português", "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", - "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_language": "Lingua", "com_nav_latex_parsing": "Analizza LaTeX nei messaggi (potrebbe influire sulle prestazioni)", "com_nav_log_out": "Disconnetti", diff --git a/client/src/locales/ja/translation.json b/client/src/locales/ja/translation.json index cfad406bef..52846cd59a 100644 --- a/client/src/locales/ja/translation.json +++ b/client/src/locales/ja/translation.json @@ -333,11 +333,10 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "自動検出", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", - "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", - "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_finnish": "Suomi", "com_nav_lang_french": "Français ", "com_nav_lang_german": "Deutsch", @@ -347,12 +346,13 @@ "com_nav_lang_japanese": "日本語", "com_nav_lang_korean": "한국어", "com_nav_lang_polish": "Polski", + "com_nav_lang_portuguese": "Português", "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", - "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_language": "言語", "com_nav_latex_parsing": "メッセージ内の LaTeX の構文解析 (パフォーマンスに影響する可能性があります)", "com_nav_log_out": "ログアウト", diff --git a/client/src/locales/ko/translation.json b/client/src/locales/ko/translation.json index 994cf386e0..61571435c3 100644 --- a/client/src/locales/ko/translation.json +++ b/client/src/locales/ko/translation.json @@ -333,11 +333,10 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "자동 감지", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", - "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", - "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_finnish": "Suomi", "com_nav_lang_french": "Français ", "com_nav_lang_german": "Deutsch", @@ -347,12 +346,13 @@ "com_nav_lang_japanese": "日本語", "com_nav_lang_korean": "한국어", "com_nav_lang_polish": "Polski", + "com_nav_lang_portuguese": "Português", "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", - "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_language": "언어", "com_nav_latex_parsing": "메시지에서 LaTeX 구문 분석(성능에 영향을 줄 수 있음)", "com_nav_log_out": "로그아웃", diff --git a/client/src/locales/nl/translation.json b/client/src/locales/nl/translation.json index d0f881e3bd..55d96bb096 100644 --- a/client/src/locales/nl/translation.json +++ b/client/src/locales/nl/translation.json @@ -148,12 +148,12 @@ "com_nav_font_size": "Lettertypegrootte", "com_nav_help_faq": "Help & FAQ", "com_nav_lang_arabic": "العربية", + "com_nav_lang_auto": "Automatisch detecteren", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", - "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", - "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_finnish": "Suomi", "com_nav_lang_french": "Français ", "com_nav_lang_german": "Deutsch", @@ -163,12 +163,13 @@ "com_nav_lang_japanese": "日本語", "com_nav_lang_korean": "한국어", "com_nav_lang_polish": "Polski", + "com_nav_lang_portuguese": "Português", "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", - "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_log_out": "Uitloggen", "com_nav_not_supported": "Niet ondersteund", "com_nav_open_sidebar": "Zijbalk openen", diff --git a/client/src/locales/pl/translation.json b/client/src/locales/pl/translation.json index f4b82ec13b..e9ee3d2279 100644 --- a/client/src/locales/pl/translation.json +++ b/client/src/locales/pl/translation.json @@ -305,11 +305,10 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "Automatyczne wykrywanie", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", - "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", - "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_finnish": "Suomi", "com_nav_lang_french": "Français ", "com_nav_lang_german": "Deutsch", @@ -319,12 +318,13 @@ "com_nav_lang_japanese": "日本語", "com_nav_lang_korean": "한국어", "com_nav_lang_polish": "Polski", + "com_nav_lang_portuguese": "Português", "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", - "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_language": "Język", "com_nav_latex_parsing": "Parsowanie LaTeX w wiadomościach (może wpływać na wydajność)", "com_nav_log_out": "Wyloguj", diff --git a/client/src/locales/pt-BR/translation.json b/client/src/locales/pt-BR/translation.json index bdebde7057..06a22184c5 100644 --- a/client/src/locales/pt-BR/translation.json +++ b/client/src/locales/pt-BR/translation.json @@ -305,11 +305,10 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "Detecção automática", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", - "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", - "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_finnish": "Suomi", "com_nav_lang_french": "Français ", "com_nav_lang_german": "Deutsch", @@ -319,12 +318,13 @@ "com_nav_lang_japanese": "日本語", "com_nav_lang_korean": "한국어", "com_nav_lang_polish": "Polski", + "com_nav_lang_portuguese": "Português", "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", - "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_language": "Idioma", "com_nav_latex_parsing": "Análise de LaTeX em mensagens (pode afetar o desempenho)", "com_nav_log_out": "Sair", diff --git a/client/src/locales/pt-PT/translation.json b/client/src/locales/pt-PT/translation.json index bdebde7057..c687f7b2e0 100644 --- a/client/src/locales/pt-PT/translation.json +++ b/client/src/locales/pt-PT/translation.json @@ -1,20 +1,33 @@ { - "com_a11y_ai_composing": "A IA ainda está compondo.", + "com_a11y_ai_composing": "A IA ainda está a escrever.", "com_a11y_end": "A IA terminou de responder.", "com_a11y_start": "A IA começou a responder.", + "com_agents_allow_editing": "Permitir que outros utilizadores editem o seu agente", + "com_agents_by_librechat": "por LibreChat", + "com_agents_code_interpreter": "Quando ativo, permite que os seus agentes usem a API de Interpretação de código do LibreChat para correr código gerado, inclusivé processamento de ficheiros em segurança. Requer uma chave API válida.", + "com_agents_code_interpreter_title": "API de Interpretação de Código", "com_agents_create_error": "Houve um erro ao criar seu agente.", "com_agents_description_placeholder": "Opcional: Descreva seu Agente aqui", + "com_agents_enable_file_search": "Permitir Pesquisa de Ficheiros.", + "com_agents_file_search_disabled": "O Agente deve ser criado antes carregar ficheiros para Pesquisar.", + "com_agents_file_search_info": "Quando ativo, os agentes serão informados dos nomes de ficheiros listados abaixo, permitindo aos mesmos a extração de contexto relevante.", "com_agents_instructions_placeholder": "As instruções do sistema que o agente usa", + "com_agents_missing_provider_model": "Por favor, escolhe um provedor e modelo antes de criar um agente.", "com_agents_name_placeholder": "Opcional: O nome do agente", + "com_agents_no_access": "Não tens permissões para editar este agente.", + "com_agents_not_available": "Agente não disponível.", "com_agents_search_name": "Pesquisar agentes por nome", "com_agents_update_error": "Houve um erro ao atualizar seu agente.", + "com_assistants_action_attempt": "Assistente quer falar com {{0}}", "com_assistants_actions": "Ações", "com_assistants_actions_disabled": "Você precisa criar um assistente antes de adicionar ações.", "com_assistants_actions_info": "Permita que seu Assistente recupere informações ou execute ações via API's", "com_assistants_add_actions": "Adicionar Ações", "com_assistants_add_tools": "Adicionar Ferramentas", + "com_assistants_allow_sites_you_trust": "Apenas permitir sites que confia.", "com_assistants_append_date": "Anexar Data e Hora Atual", "com_assistants_append_date_tooltip": "Quando ativado, a data e hora atual do cliente serão anexadas às instruções do sistema do assistente.", + "com_assistants_attempt_info": "O Assistente quer enviar o seguinte:", "com_assistants_available_actions": "Ações Disponíveis", "com_assistants_capabilities": "Capacidades", "com_assistants_code_interpreter": "Interpretador de Código", @@ -49,6 +62,7 @@ "com_assistants_update_error": "Houve um erro ao atualizar seu assistente.", "com_assistants_update_success": "Atualizado com sucesso", "com_auth_already_have_account": "Já tem uma conta?", + "com_auth_apple_login": "Autenticar com a Apple.", "com_auth_back_to_login": "Voltar para Login", "com_auth_click": "Clique", "com_auth_click_here": "Clique aqui", @@ -111,9 +125,14 @@ "com_auth_username_max_length": "O nome de usuário deve ter menos de 20 caracteres", "com_auth_username_min_length": "O nome de usuário deve ter pelo menos 2 caracteres", "com_auth_welcome_back": "Bem-vindo de volta", + "com_click_to_download": "(Carrega aqui para descarregar)", + "com_download_expired": "(Ficheiro expirado)", + "com_download_expires": "(carrega aqui para descarregar - expira a {{0}})", "com_endpoint": "Endpoint", "com_endpoint_agent": "Agente", "com_endpoint_agent_model": "Modelo de Agente (Recomendado: GPT-3.5)", + "com_endpoint_agent_placeholder": "Por favor, seleciona um Agente.", + "com_endpoint_ai": "IA", "com_endpoint_anthropic_maxoutputtokens": "Número máximo de tokens que podem ser gerados na resposta. Especifique um valor mais baixo para respostas mais curtas e um valor mais alto para respostas mais longas. Nota: os modelos podem parar antes de atingir esse máximo.", "com_endpoint_anthropic_prompt_cache": "O cache de prompt permite reutilizar um grande contexto ou instruções em chamadas de API, reduzindo custos e latência", "com_endpoint_anthropic_temp": "Varia de 0 a 1. Use temperatura mais próxima de 0 para tarefas analíticas / de múltipla escolha, e mais próxima de 1 para tarefas criativas e generativas. Recomendamos alterar isso ou Top P, mas não ambos.", @@ -168,6 +187,7 @@ "com_endpoint_instructions_assistants_placeholder": "Substitui as instruções do assistente. Isso é útil para modificar o comportamento em uma base por execução.", "com_endpoint_max_output_tokens": "Máximo de Tokens de Saída", "com_endpoint_message": "Mensagem", + "com_endpoint_message_new": "Mensagem {{0}}", "com_endpoint_message_not_appendable": "Edite sua mensagem ou Regenerar.", "com_endpoint_my_preset": "Meu Preset", "com_endpoint_no_presets": "Ainda não há presets, use o botão de configurações para criar um", @@ -179,6 +199,7 @@ "com_endpoint_openai_max_tokens": "Campo opcional `max_tokens`, representando o número máximo de tokens que podem ser gerados na conclusão do chat. O comprimento total dos tokens de entrada e dos tokens gerados é limitado pelo comprimento do contexto dos modelos. Você pode experimentar erros se esse número exceder o máximo de tokens de contexto.", "com_endpoint_openai_pres": "Número entre -2.0 e 2.0. Valores positivos penalizam novos tokens com base em sua presença no texto até agora, aumentando a probabilidade do modelo de falar sobre novos tópicos.", "com_endpoint_openai_prompt_prefix_placeholder": "Defina instruções personalizadas para incluir na Mensagem do Sistema. Padrão: nenhuma", + "com_endpoint_openai_reasoning_effort": "Apenas para modelos o1: Restringir o esforço de raciocínio. Isto poderá resultar em respostas mais rápidas e menos tokens usados na lógica da resposta.", "com_endpoint_openai_resend": "Reenviar todas as imagens anexadas anteriormente. Nota: isso pode aumentar significativamente o custo de tokens e você pode experimentar erros com muitos anexos de imagem.", "com_endpoint_openai_resend_files": "Reenviar todos os arquivos anexados anteriormente. Nota: isso aumentará o custo de tokens e você pode experimentar erros com muitos anexos.", "com_endpoint_openai_stop": "Até 4 sequências onde a API parará de gerar mais tokens.", @@ -212,7 +233,9 @@ "com_endpoint_prompt_prefix_assistants": "Instruções Adicionais", "com_endpoint_prompt_prefix_assistants_placeholder": "Defina instruções ou contexto adicionais além das instruções principais do Assistente. Ignorado se vazio.", "com_endpoint_prompt_prefix_placeholder": "Defina instruções ou contexto personalizados. Ignorado se vazio.", + "com_endpoint_reasoning_effort": "Esforço de raciocínio", "com_endpoint_save_as_preset": "Salvar Como Preset", + "com_endpoint_search": "Procurar endereço por nome", "com_endpoint_set_custom_name": "Defina um nome personalizado, caso você possa encontrar este preset", "com_endpoint_skip_hover": "Habilitar pular a etapa de conclusão, que revisa a resposta final e os passos gerados", "com_endpoint_stop": "Sequências de Parada", @@ -222,6 +245,13 @@ "com_endpoint_top_p": "Top P", "com_endpoint_use_active_assistant": "Usar Assistente Ativo", "com_error_expired_user_key": "A chave fornecida para {{0}} expirou em {{1}}. Por favor, forneça uma nova chave e tente novamente.", + "com_error_files_dupe": "Ficheiro duplicado detectado", + "com_error_files_empty": "Ficheiros vazios não são permitidos.", + "com_error_files_process": "Ocorreu um erro ao processar o ficheiro.", + "com_error_files_unsupported_capability": "Não existem funcionalidades ativas que suportem este tipo de ficheiro.", + "com_error_files_upload": "Ocorreu um erro ao enviar o ficheiro.", + "com_error_files_upload_canceled": "O enviar do ficheiro foi cancelado. Nota: O envio pode estar ainda a ser processado e poderá necessitar de ser apagado manualmente.", + "com_error_files_validation": "Ocorreu um erro ao validar o ficheiro.", "com_error_input_length": "A contagem de tokens da última mensagem é muito longa, excedendo o limite de tokens ({{0}} respectivamente). Por favor, encurte sua mensagem, ajuste o tamanho máximo do contexto nos parâmetros da conversa ou divida a conversa para continuar.", "com_error_invalid_user_key": "Chave fornecida inválida. Por favor, forneça uma chave válida e tente novamente.", "com_error_moderation": "Parece que o conteúdo enviado foi sinalizado pelo nosso sistema de moderação por não estar alinhado com nossas diretrizes da comunidade. Não podemos prosseguir com este tópico específico. Se você tiver outras perguntas ou tópicos que gostaria de explorar, edite sua mensagem ou crie uma nova conversa.", @@ -230,6 +260,7 @@ "com_files_filter": "Filtrar arquivos...", "com_files_no_results": "Nenhum resultado.", "com_files_number_selected": "{{0}} de {{1}} arquivo(s) selecionado(s)", + "com_generated_files": "Ficheiros gerados:", "com_hide_examples": "Ocultar Exemplos", "com_nav_account_settings": "Configurações da Conta", "com_nav_always_make_prod": "Sempre tornar novas versões produção", @@ -252,8 +283,11 @@ "com_nav_browser": "Navegador", "com_nav_buffer_append_error": "Problema com o streaming de áudio. A reprodução pode ser interrompida.", "com_nav_change_picture": "Mudar foto", + "com_nav_chat_commands": "Comandos de conversa", + "com_nav_chat_commands_info": "Estes comandos são ativados ao escrever caracteres específicos no início da sua mensagem. Cada comando corre segundo o seu prefixo. Poderá desligar os mesmos se usa esses caracteres no início das suas mensagens com alguma frequência.", "com_nav_chat_direction": "Direção do chat", "com_nav_clear_all_chats": "Limpar todos os chats", + "com_nav_clear_cache_confirm_message": "Tem a certeza que pretende apagar a cache?", "com_nav_clear_conversation": "Limpar conversas", "com_nav_clear_conversation_confirm_message": "Tem certeza de que deseja limpar todas as conversas? Isso é irreversível.", "com_nav_close_sidebar": "Fechar barra lateral", @@ -294,6 +328,7 @@ "com_nav_help_faq": "Ajuda & FAQ", "com_nav_hide_panel": "Ocultar painel mais à direita", "com_nav_info_code_artifacts": "Habilita a exibição de artefatos de código experimental ao lado do chat", + "com_nav_info_code_artifacts_agent": "Permitir o uso de artefactos de código por este agente. Por defeito, instruções adicionais específicas ao uso de artefactos são adicionadas, caso o \"Modo de comando personalizado\" esteja ativo.", "com_nav_info_custom_prompt_mode": "Quando habilitado, o prompt padrão do sistema de artefatos não será incluído. Todas as instruções de geração de artefatos devem ser fornecidas manualmente neste modo.", "com_nav_info_enter_to_send": "Quando habilitado, pressionar `ENTER` enviará sua mensagem. Quando desabilitado, pressionar Enter adicionará uma nova linha, e você precisará pressionar `CTRL + ENTER` / `⌘ + ENTER` para enviar sua mensagem.", "com_nav_info_fork_change_default": "`Apenas mensagens visíveis` inclui apenas o caminho direto para a mensagem selecionada. `Incluir ramos relacionados` adiciona ramos ao longo do caminho. `Incluir tudo de/para aqui` inclui todas as mensagens e ramos conectados.", @@ -305,11 +340,10 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "Detecção automática", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", - "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", - "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_finnish": "Suomi", "com_nav_lang_french": "Français ", "com_nav_lang_german": "Deutsch", @@ -319,18 +353,21 @@ "com_nav_lang_japanese": "日本語", "com_nav_lang_korean": "한국어", "com_nav_lang_polish": "Polski", + "com_nav_lang_portuguese": "Português", "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", - "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_language": "Idioma", "com_nav_latex_parsing": "Análise de LaTeX em mensagens (pode afetar o desempenho)", "com_nav_log_out": "Sair", "com_nav_long_audio_warning": "Textos mais longos levarão mais tempo para processar.", + "com_nav_maximize_chat_space": "Maximizar espaço de conversa", "com_nav_modular_chat": "Habilitar troca de Endpoints no meio da conversa", "com_nav_my_files": "Meus Arquivos", + "com_nav_no_search_results": "0 Resultados encontrados", "com_nav_not_supported": "Não Suportado", "com_nav_open_sidebar": "Abrir barra lateral", "com_nav_playback_rate": "Taxa de Reprodução de Áudio", @@ -343,6 +380,7 @@ "com_nav_plus_command_description": "Alternar comando \"+\" para adicionar uma configuração de resposta múltipla", "com_nav_profile_picture": "Foto de Perfil", "com_nav_save_drafts": "Salvar rascunhos localmente", + "com_nav_scroll_button": "Botão de ir para o fim", "com_nav_search_placeholder": "Buscar mensagens", "com_nav_send_message": "Enviar mensagem", "com_nav_setting_account": "Conta", @@ -355,26 +393,31 @@ "com_nav_shared_links": "Links compartilhados", "com_nav_shared_links_manage": "Gerenciar", "com_nav_show_code": "Sempre mostrar código ao usar o interpretador de código", + "com_nav_show_thinking": "Abrir Dropdown de lógica por defeito.", "com_nav_slash_command": "Comando /", "com_nav_slash_command_description": "Alternar comando \"/\" para selecionar um prompt via teclado", "com_nav_source_buffer_error": "Erro ao configurar a reprodução de áudio. Por favor, atualize a página.", "com_nav_speech_cancel_error": "Não foi possível parar a reprodução de áudio. Você pode precisar atualizar a página.", "com_nav_speech_to_text": "Fala para Texto", + "com_nav_stop_generating": "Parar de gerar", "com_nav_text_to_speech": "Texto para Fala", "com_nav_theme": "Tema", "com_nav_theme_dark": "Escuro", "com_nav_theme_light": "Claro", "com_nav_theme_system": "Sistema", "com_nav_tool_dialog": "Ferramentas do Assistente", + "com_nav_tool_dialog_agents": "Ferramentas do Agente", "com_nav_tool_dialog_description": "O assistente deve ser salvo para persistir as seleções de ferramentas.", "com_nav_tool_remove": "Remover", "com_nav_tool_search": "Buscar ferramentas", "com_nav_tts_init_error": "Falha ao inicializar texto-para-fala: {{0}}", "com_nav_tts_unsupported_error": "Texto-para-fala para o mecanismo selecionado não é suportado neste navegador.", "com_nav_user": "USUÁRIO", + "com_nav_user_msg_markdown": "Mostrar as mensagens do utilizador como markdown", "com_nav_user_name_display": "Exibir nome de usuário nas mensagens", "com_nav_voice_select": "Voz", "com_nav_voices_fetch_error": "Não foi possível recuperar as opções de voz. Por favor, verifique sua conexão com a internet.", + "com_nav_welcome_agent": "Por favor, seleciona um Agente.", "com_nav_welcome_assistant": "Por favor, Selecione um Assistente", "com_nav_welcome_message": "Como posso ajudar você hoje?", "com_show_agent_settings": "Mostrar Configurações do Agente", @@ -387,24 +430,37 @@ "com_sidepanel_hide_panel": "Ocultar Painel", "com_sidepanel_manage_files": "Gerenciar Arquivos", "com_sidepanel_parameters": "Parâmetros", + "com_sidepanel_select_agent": "Seleciona um Agente.", "com_sidepanel_select_assistant": "Selecionar um Assistente", "com_ui_accept": "Eu aceito", "com_ui_add": "Adicionar", "com_ui_add_model_preset": "Adicionar um modelo ou predefinição para uma resposta adicional", + "com_ui_add_multi_conversation": "Adicionar conversação múltiplca", "com_ui_admin": "Admin", + "com_ui_admin_access_warning": "Desligar o acesso administrativo a esta funcionalidade poderá causar problemas inesperados na UI que precisem de atualizar. Se salvo, a única maneira de reverter será pela configuração do librechat.yaml que afecta todos os cargos.", "com_ui_admin_settings": "Configurações de Admin", "com_ui_advanced": "Avançado", "com_ui_agent": "Agente", "com_ui_agent_delete_error": "Houve um erro ao excluir o agente", "com_ui_agent_deleted": "Agente excluído com sucesso", + "com_ui_agent_duplicate_error": "Ocorreu um erro ao duplicar o agente.", + "com_ui_agent_duplicated": "Agente duplicado com sucesso", + "com_ui_agent_editing_allowed": "Este agente já pode ser editado por outros utilizadores.", "com_ui_agents": "Agentes", + "com_ui_agents_allow_create": "Permitir a criação de Agentes", + "com_ui_agents_allow_share_global": "Permitir a partilha de Agentes com todos os utilizadores", + "com_ui_agents_allow_use": "Permitir o uso de Agentes", "com_ui_all": "todos", "com_ui_all_proper": "Todos", + "com_ui_analyzing": "A Analisar", + "com_ui_analyzing_finished": "Análise concluída", + "com_ui_api_key": "Chave da API", "com_ui_archive": "Arquivar", "com_ui_archive_error": "Falha ao arquivar conversa", "com_ui_artifact_click": "Clique para abrir", "com_ui_artifacts": "Artefatos", "com_ui_artifacts_toggle": "Alternar UI de Artefatos", + "com_ui_artifacts_toggle_agent": "Permitir Artefactos", "com_ui_ascending": "Asc", "com_ui_assistant": "Assistente", "com_ui_assistant_delete_error": "Houve um erro ao excluir o assistente", @@ -417,35 +473,53 @@ "com_ui_attach_error_type": "Tipo de arquivo não suportado para o endpoint:", "com_ui_attach_warn_endpoint": "Arquivos não compatíveis podem ser ignorados sem uma ferramenta compatível", "com_ui_attachment": "Anexo", + "com_ui_auth_type": "Tipo de Autenticação", + "com_ui_auth_url": "Endereço de Autorização", "com_ui_authentication": "Autenticação", + "com_ui_authentication_type": "Tipo de Autenticação", "com_ui_avatar": "Avatar", + "com_ui_azure": "Azure", "com_ui_back_to_chat": "Voltar ao Chat", "com_ui_back_to_prompts": "Voltar aos Prompts", + "com_ui_basic": "Basic", + "com_ui_basic_auth_header": "Cabeçalho de Autorização Basic", + "com_ui_bearer": "Bearer", "com_ui_bookmark_delete_confirm": "Tem certeza de que deseja excluir este favorito?", "com_ui_bookmarks": "Favoritos", + "com_ui_bookmarks_add": "Adicionar Bookmarks", "com_ui_bookmarks_add_to_conversation": "Adicionar à conversa atual", "com_ui_bookmarks_count": "Contagem", "com_ui_bookmarks_create_error": "Houve um erro ao criar o favorito", "com_ui_bookmarks_create_exists": "Este favorito já existe", "com_ui_bookmarks_create_success": "Favorito criado com sucesso", + "com_ui_bookmarks_delete": "Apagar Favorito", "com_ui_bookmarks_delete_error": "Houve um erro ao excluir o favorito", "com_ui_bookmarks_delete_success": "Favorito excluído com sucesso", "com_ui_bookmarks_description": "Descrição", + "com_ui_bookmarks_edit": "Editar Favorito", "com_ui_bookmarks_filter": "Filtrar favoritos...", "com_ui_bookmarks_new": "Novo Favorito", "com_ui_bookmarks_title": "Título", "com_ui_bookmarks_update_error": "Houve um erro ao atualizar o favorito", "com_ui_bookmarks_update_success": "Favorito atualizado com sucesso", + "com_ui_bulk_delete_error": "Falha ao apagar ligações partilhadas", + "com_ui_callback_url": "Endereço de Callback", "com_ui_cancel": "Cancelar", "com_ui_chat": "Chat", "com_ui_chat_history": "Histórico de Chat", "com_ui_clear": "Limpar", "com_ui_clear_all": "Limpar tudo", + "com_ui_client_id": "ID de Cliente", + "com_ui_client_secret": "Client Secret", "com_ui_close": "Fechar", + "com_ui_close_menu": "Fechar Menu", "com_ui_code": "Código", + "com_ui_collapse_chat": "Colapsar Conversa", "com_ui_command_placeholder": "Opcional: Insira um comando para o prompt ou o nome será usado.", "com_ui_command_usage_placeholder": "Selecione um Prompt por comando ou nome", "com_ui_confirm_action": "Confirmar Ação", + "com_ui_confirm_admin_use_change": "Mudar esta configuração irá bloquear acessos para administradores, você inclusivé. Tem a certeza que pretende avançar?", + "com_ui_confirm_change": "Confirmar alteração", "com_ui_context": "Contexto", "com_ui_continue": "Continuar", "com_ui_controls": "Controles", @@ -457,6 +531,9 @@ "com_ui_create": "Criar", "com_ui_create_link": "Criar link", "com_ui_create_prompt": "Criar Prompt", + "com_ui_currently_production": "Atualmente em produção", + "com_ui_custom": "Costumizar", + "com_ui_custom_header_name": "Nome de Cabeçalho Customizado", "com_ui_custom_prompt_mode": "Modo de Prompt Personalizado", "com_ui_dashboard": "Painel", "com_ui_date": "Data", @@ -477,6 +554,7 @@ "com_ui_date_today": "Hoje", "com_ui_date_yesterday": "Ontem", "com_ui_decline": "Eu não aceito", + "com_ui_default_post_request": "Default (Pedido POST)", "com_ui_delete": "Excluir", "com_ui_delete_action": "Excluir Ação", "com_ui_delete_action_confirm": "Tem certeza de que deseja excluir esta ação?", @@ -486,24 +564,39 @@ "com_ui_delete_confirm_prompt_version_var": "Isso excluirá a versão selecionada para \"{{0}}\". Se não houver outras versões, o prompt será excluído.", "com_ui_delete_conversation": "Excluir chat?", "com_ui_delete_prompt": "Excluir Prompt?", + "com_ui_delete_shared_link": "Apagar endereço partilhado?", "com_ui_delete_tool": "Excluir Ferramenta", "com_ui_delete_tool_confirm": "Tem certeza de que deseja excluir esta ferramenta?", "com_ui_descending": "Desc", "com_ui_description": "Descrição", "com_ui_description_placeholder": "Opcional: Insira uma descrição para exibir para o prompt", + "com_ui_download": "Descarregar", + "com_ui_download_artifact": "Descarregar Artefacto", "com_ui_download_error": "Erro ao baixar o arquivo. O arquivo pode ter sido excluído.", "com_ui_dropdown_variables": "Variáveis de dropdown:", "com_ui_dropdown_variables_info": "Crie menus dropdown personalizados para seus prompts: `{{nome_da_variável:opção1|opção2|opção3}}`", + "com_ui_duplicate": "Duplicar", + "com_ui_duplication_error": "Ocorreu um erro ao duplicar esta conversa", + "com_ui_duplication_processing": "A duplicar conversa...", + "com_ui_duplication_success": "Conversa duplicada com sucesso", "com_ui_edit": "Editar", + "com_ui_empty_category": "-", "com_ui_endpoint": "Endpoint", + "com_ui_endpoint_menu": "Menu de Endereços do LLM", + "com_ui_endpoints_available": "Endereços Disponíveis", "com_ui_enter": "Entrar", + "com_ui_enter_api_key": "Inserir Chave da API", + "com_ui_enter_openapi_schema": "Insira o seu schema da OpenAPI aqui", "com_ui_enter_var": "Inserir {{0}}", "com_ui_error": "Erro", "com_ui_error_connection": "Erro ao conectar ao servidor, tente atualizar a página.", "com_ui_error_save_admin_settings": "Houve um erro ao salvar suas configurações de admin.", "com_ui_examples": "Exemplos", + "com_ui_export_convo_modal": "Exportar Modal da Conversa", "com_ui_field_required": "Este campo é obrigatório", + "com_ui_filter_prompts": "Filtro de Comando", "com_ui_filter_prompts_name": "Filtrar prompts por nome", + "com_ui_finance": "Finanças", "com_ui_fork": "Bifurcar", "com_ui_fork_all_target": "Incluir todos para/de aqui", "com_ui_fork_branches": "Incluir ramificações relacionadas", @@ -526,9 +619,12 @@ "com_ui_fork_split_target_setting": "Iniciar bifurcação a partir da mensagem alvo por padrão", "com_ui_fork_success": "Conversa bifurcada com sucesso", "com_ui_fork_visible": "Apenas mensagens visíveis", + "com_ui_go_back": "Para trás", "com_ui_go_to_conversation": "Ir para a conversa", "com_ui_happy_birthday": "É meu 1º aniversário!", + "com_ui_hide_qr": "Esconder QR Code", "com_ui_host": "Host", + "com_ui_idea": "Ideias", "com_ui_image_gen": "Geração de Imagem", "com_ui_import_conversation": "Importar", "com_ui_import_conversation_error": "Houve um erro ao importar suas conversas", @@ -536,16 +632,28 @@ "com_ui_import_conversation_info": "Importar conversas de um arquivo JSON", "com_ui_import_conversation_success": "Conversas importadas com sucesso", "com_ui_include_shadcnui": "Incluir instruções de componentes shadcn/ui", + "com_ui_include_shadcnui_agent": "Incluir instruções shadcn/ui", "com_ui_input": "Entrada", "com_ui_instructions": "Instruções", "com_ui_latest_footer": "Toda IA para Todos.", + "com_ui_latest_production_version": "Última versão produtiva", + "com_ui_latest_version": "Última versão", + "com_ui_librechat_code_api_key": "Obtem a tua chave da API do Interpretador de código Librechat", + "com_ui_librechat_code_api_subtitle": "Seguro. Multilíngua. Entrada/Saída de Ficheiros.", + "com_ui_librechat_code_api_title": "Correr código AI", + "com_ui_llm_menu": "Menu LLM", + "com_ui_llms_available": "LLMs disponíveis", + "com_ui_loading": "A Carregar....", "com_ui_locked": "Bloqueado", + "com_ui_logo": "Logotipo {{0}}", "com_ui_manage": "Gerenciar", "com_ui_max_tags": "O número máximo permitido é {{0}}, usando os valores mais recentes.", "com_ui_mention": "Mencione um endpoint, assistente ou predefinição para alternar rapidamente para ele", "com_ui_min_tags": "Não é possível remover mais valores, um mínimo de {{0}} é necessário.", + "com_ui_misc": "Outros", "com_ui_model": "Modelo", "com_ui_model_parameters": "Parâmetros do Modelo", + "com_ui_more_info": "Mais informação", "com_ui_my_prompts": "Meus Prompts", "com_ui_name": "Nome", "com_ui_new_chat": "Novo chat", @@ -553,15 +661,21 @@ "com_ui_no": "Não", "com_ui_no_bookmarks": "Parece que você ainda não tem favoritos. Clique em um chat e adicione um novo", "com_ui_no_category": "Sem categoria", + "com_ui_no_changes": "Sem alterações para atualizar", "com_ui_no_terms_content": "Nenhum conteúdo de termos e condições para exibir", + "com_ui_none": "Nenhum", "com_ui_none_selected": "Nenhum selecionado", "com_ui_nothing_found": "Nada encontrado", + "com_ui_oauth": "OAuth", "com_ui_of": "de", "com_ui_off": "Desligado", "com_ui_on": "Ligado", + "com_ui_openai": "OpenAI", + "com_ui_page": "Página", "com_ui_prev": "Anterior", "com_ui_preview": "Pré-visualizar", "com_ui_privacy_policy": "Política de Privacidade", + "com_ui_privacy_policy_url": "Endereço de política de privacidade", "com_ui_prompt": "Prompt", "com_ui_prompt_already_shared_to_all": "Este prompt já está compartilhado com todos os usuários", "com_ui_prompt_name": "Nome do Prompt", @@ -576,21 +690,40 @@ "com_ui_prompts_allow_use": "Permitir uso de Prompts", "com_ui_provider": "Provedor", "com_ui_read_aloud": "Ler em voz alta", + "com_ui_refresh_link": "Atualizar endereço", "com_ui_regenerate": "Regenerar", + "com_ui_region": "Região", "com_ui_rename": "Renomear", + "com_ui_rename_prompt": "Renomear comando", + "com_ui_requires_auth": "Requer autenticação", + "com_ui_reset_var": "Reiniciar {{0}}", "com_ui_result": "Resultado", "com_ui_revoke": "Revogar", "com_ui_revoke_info": "Revogar todas as credenciais fornecidas pelo usuário", + "com_ui_revoke_key_confirm": "Tem a certeza que pretende revogar esta chave?", + "com_ui_revoke_key_endpoint": "Revogar chave para {{0}}", + "com_ui_revoke_keys": "Revogar chaves", + "com_ui_revoke_keys_confirm": "Tem a certeza que pretende revogar todas as chaves?", + "com_ui_role_select": "Papel", + "com_ui_roleplay": "Roleplay", + "com_ui_run_code": "Correr código", + "com_ui_run_code_error": "Ocorreu um erro ao correr o código", "com_ui_save": "Salvar", "com_ui_save_submit": "Salvar & Enviar", "com_ui_saved": "Salvo!", + "com_ui_schema": "Schema", + "com_ui_scope": "Scope", + "com_ui_search": "Procurar", "com_ui_select": "Selecionar", "com_ui_select_file": "Selecionar um arquivo", "com_ui_select_model": "Selecionar um modelo", "com_ui_select_provider": "Selecionar um provedor", "com_ui_select_provider_first": "Selecione um provedor primeiro", + "com_ui_select_region": "Escolha uma região", "com_ui_select_search_model": "Pesquisar modelo por nome", "com_ui_select_search_plugin": "Pesquisar plugin por nome", + "com_ui_select_search_provider": "Procurar provedor por nome", + "com_ui_select_search_region": "Procurar região por nome", "com_ui_share": "Compartilhar", "com_ui_share_create_message": "Seu nome e quaisquer mensagens que você adicionar após o compartilhamento permanecerão privadas.", "com_ui_share_delete_error": "Houve um erro ao excluir o link compartilhado", @@ -599,37 +732,58 @@ "com_ui_share_to_all_users": "Compartilhar com todos os usuários", "com_ui_share_update_message": "Seu nome, instruções personalizadas e quaisquer mensagens que você adicionar após o compartilhamento permanecerão privadas.", "com_ui_share_var": "Compartilhar {{0}}", + "com_ui_shared_link_bulk_delete_success": "Endereços partilhados apagados com sucesso", + "com_ui_shared_link_delete_success": "Endereço partilhado apagado com sucesso", "com_ui_shared_link_not_found": "Link compartilhado não encontrado", "com_ui_shared_prompts": "Prompts Compartilhados", + "com_ui_shop": "Compras", "com_ui_show_all": "Mostrar Todos", + "com_ui_show_qr": "Mostrar QR Code", + "com_ui_sign_in_to_domain": "Autenticar em {{0}}", "com_ui_simple": "Simples", "com_ui_size": "Tamanho", "com_ui_special_variables": "Variáveis especiais:", "com_ui_special_variables_info": "Use `{{current_date}}` para a data atual, e `{{current_user}}` para o nome da sua conta.", + "com_ui_speech_while_submitting": "Não é possível submeter fala enquanto a resposta está a ser gerada.", "com_ui_stop": "Parar", "com_ui_storage": "Armazenamento", "com_ui_submit": "Enviar", + "com_ui_teach_or_explain": "A aprender", + "com_ui_temporary_chat": "Conversa temporária", "com_ui_terms_and_conditions": "Termos e Condições", "com_ui_terms_of_service": "Termos de Serviço", + "com_ui_thinking": "A pensar...", + "com_ui_thoughts": "Raciocínio", + "com_ui_token_exchange_method": "Método de troca de Token", + "com_ui_token_url": "Endereço do Token", "com_ui_tools": "Ferramentas", + "com_ui_travel": "Viajar", "com_ui_unarchive": "Desarquivar", "com_ui_unarchive_error": "Falha ao desarquivar conversa", "com_ui_unknown": "Desconhecido", "com_ui_update": "Atualizar", "com_ui_upload": "Carregar", + "com_ui_upload_code_files": "Enviar para o interpretador de código", "com_ui_upload_delay": "O upload de \"{{0}}\" está demorando mais do que o esperado. Por favor, aguarde enquanto o arquivo termina de ser indexado para recuperação.", "com_ui_upload_error": "Houve um erro ao carregar seu arquivo", + "com_ui_upload_file_search": "Enviar para a pesquisa de Ficheiro", "com_ui_upload_files": "Carregar arquivos", "com_ui_upload_image": "Carregar uma imagem", + "com_ui_upload_image_input": "Inserir Imagem", "com_ui_upload_invalid": "Arquivo inválido para upload. Deve ser uma imagem não excedendo o limite", "com_ui_upload_invalid_var": "Arquivo inválido para upload. Deve ser uma imagem não excedendo {{0}} MB", "com_ui_upload_success": "Arquivo carregado com sucesso", + "com_ui_upload_type": "Escolher tipo de Carregamento", "com_ui_use_micrphone": "Usar microfone", "com_ui_use_prompt": "Usar prompt", "com_ui_variables": "Variáveis", "com_ui_variables_info": "Use chaves duplas no seu texto para criar variáveis, por exemplo, `{{exemplo de variável}}`, para preencher posteriormente ao usar o prompt.", "com_ui_version_var": "Versão {{0}}", "com_ui_versions": "Versões", + "com_ui_view_source": "Ver fonte da conversa", + "com_ui_write": "A escrever", "com_ui_yes": "Sim", - "com_user_message": "Você" + "com_ui_zoom": "Ampliar", + "com_user_message": "Você", + "com_warning_resubmit_unsupported": "O reenvio da mensagem de IA não é suportado por este endereço." } \ No newline at end of file diff --git a/client/src/locales/ru/translation.json b/client/src/locales/ru/translation.json index 90b7ff4beb..9580d54f54 100644 --- a/client/src/locales/ru/translation.json +++ b/client/src/locales/ru/translation.json @@ -333,11 +333,10 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "Автоопределение", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", - "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", - "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_finnish": "Suomi", "com_nav_lang_french": "Français ", "com_nav_lang_german": "Deutsch", @@ -347,12 +346,13 @@ "com_nav_lang_japanese": "日本語", "com_nav_lang_korean": "한국어", "com_nav_lang_polish": "Polski", + "com_nav_lang_portuguese": "Português", "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", - "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_language": "Локализация", "com_nav_latex_parsing": "Обработка LaTeX в сообщениях (может повлиять на производительность)", "com_nav_log_out": "Выйти", diff --git a/client/src/locales/sv/translation.json b/client/src/locales/sv/translation.json index c3224ccec8..96e37de09e 100644 --- a/client/src/locales/sv/translation.json +++ b/client/src/locales/sv/translation.json @@ -135,12 +135,12 @@ "com_nav_font_size": "Textstorlek", "com_nav_help_faq": "Hjälp & Vanliga frågor", "com_nav_lang_arabic": "العربية", + "com_nav_lang_auto": "Automatisk detektering", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", - "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", - "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_finnish": "Suomi", "com_nav_lang_french": "Français ", "com_nav_lang_german": "Deutsch", @@ -150,12 +150,13 @@ "com_nav_lang_japanese": "日本語", "com_nav_lang_korean": "한국어", "com_nav_lang_polish": "Polski", + "com_nav_lang_portuguese": "Português", "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", - "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_log_out": "Logga ut", "com_nav_not_supported": "Stöds ej", "com_nav_open_sidebar": "Öppna sidofält", diff --git a/client/src/locales/tr/translation.json b/client/src/locales/tr/translation.json index 7cd2c78aef..cbf93c3474 100644 --- a/client/src/locales/tr/translation.json +++ b/client/src/locales/tr/translation.json @@ -337,11 +337,10 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "Auto detect", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", - "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", - "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_finnish": "Suomi", "com_nav_lang_french": "Français ", "com_nav_lang_german": "Deutsch", @@ -351,12 +350,13 @@ "com_nav_lang_japanese": "日本語", "com_nav_lang_korean": "한국어", "com_nav_lang_polish": "Polski", + "com_nav_lang_portuguese": "Português", "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", - "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_language": "Dil", "com_nav_latex_parsing": "Mesajlarda LaTeX işleme (performansı etkileyebilir)", "com_nav_log_out": "Çıkış yap", diff --git a/client/src/locales/vi/translation.json b/client/src/locales/vi/translation.json index ef33076020..b7b460d1ea 100644 --- a/client/src/locales/vi/translation.json +++ b/client/src/locales/vi/translation.json @@ -133,12 +133,12 @@ "com_nav_font_size": "Cỡ chữ", "com_nav_help_faq": "Trợ giúp & Câu hỏi thường gặp", "com_nav_lang_arabic": "العربية", + "com_nav_lang_auto": "Tự động phát hiện", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", - "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", - "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_finnish": "Suomi", "com_nav_lang_french": "Français ", "com_nav_lang_german": "Deutsch", @@ -148,12 +148,13 @@ "com_nav_lang_japanese": "日本語", "com_nav_lang_korean": "한국어", "com_nav_lang_polish": "Polski", + "com_nav_lang_portuguese": "Português", "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", - "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_log_out": "Đăng xuất", "com_nav_not_supported": "Không được hỗ trợ", "com_nav_open_sidebar": "Mở thanh bên", diff --git a/client/src/locales/zh-Hans/translation.json b/client/src/locales/zh-Hans/translation.json index 36ae7f6e02..6570bf1ae5 100644 --- a/client/src/locales/zh-Hans/translation.json +++ b/client/src/locales/zh-Hans/translation.json @@ -339,11 +339,10 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "自动检测语言", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", - "com_nav_lang_portuguese": "Português", "com_nav_lang_chinese": "中文", - "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_finnish": "Suomi", "com_nav_lang_french": "Français ", "com_nav_lang_german": "Deutsch", @@ -353,11 +352,12 @@ "com_nav_lang_japanese": "日本語", "com_nav_lang_korean": "한국어", "com_nav_lang_polish": "Polski", + "com_nav_lang_portuguese": "Português", "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", - "com_nav_lang_turkish": "Türkçe", "com_nav_lang_traditional_chinese": "繁體中文", + "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", "com_nav_language": "语言", "com_nav_latex_parsing": "解析消息中的 LaTeX(可能会影响性能)", diff --git a/client/src/locales/zh-Hant/translation.json b/client/src/locales/zh-Hant/translation.json index 97068a8704..1913f05f56 100644 --- a/client/src/locales/zh-Hant/translation.json +++ b/client/src/locales/zh-Hant/translation.json @@ -333,11 +333,10 @@ "com_nav_lang_arabic": "العربية", "com_nav_lang_auto": "自動偵測", "com_nav_lang_brazilian_portuguese": "Português Brasileiro", - "com_nav_lang_portuguese": "Português", - "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_chinese": "中文", "com_nav_lang_dutch": "Nederlands", "com_nav_lang_english": "English", + "com_nav_lang_estonian": "Eesti keel", "com_nav_lang_finnish": "Suomi", "com_nav_lang_french": "Français ", "com_nav_lang_german": "Deutsch", @@ -347,12 +346,13 @@ "com_nav_lang_japanese": "日本語", "com_nav_lang_korean": "한국어", "com_nav_lang_polish": "Polski", + "com_nav_lang_portuguese": "Português", "com_nav_lang_russian": "Русский", "com_nav_lang_spanish": "Español", "com_nav_lang_swedish": "Svenska", + "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_lang_turkish": "Türkçe", "com_nav_lang_vietnamese": "Tiếng Việt", - "com_nav_lang_traditional_chinese": "繁體中文", "com_nav_language": "語言", "com_nav_latex_parsing": "解析訊息中的 LaTeX 內容(可能影響效能)", "com_nav_log_out": "登出", diff --git a/client/src/utils/files.ts b/client/src/utils/files.ts index 2840d694d0..337d1cc84f 100644 --- a/client/src/utils/files.ts +++ b/client/src/utils/files.ts @@ -1,6 +1,13 @@ -import { excelMimeTypes, QueryKeys } from 'librechat-data-provider'; +import { + megabyte, + QueryKeys, + excelMimeTypes, + codeTypeMapping, + fileConfig as defaultFileConfig, +} from 'librechat-data-provider'; +import type { TFile, EndpointFileConfig } from 'librechat-data-provider'; import type { QueryClient } from '@tanstack/react-query'; -import type { TFile } from 'librechat-data-provider'; +import type { ExtendedFile } from '~/common'; import SheetPaths from '~/components/svg/Files/SheetPaths'; import TextPaths from '~/components/svg/Files/TextPaths'; import FilePaths from '~/components/svg/Files/FilePaths'; @@ -190,3 +197,92 @@ export function formatBytes(bytes: number, decimals = 2) { const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)); } + +const { checkType } = defaultFileConfig; + +export const validateFiles = ({ + files, + fileList, + setError, + endpointFileConfig, +}: { + fileList: File[]; + files: Map; + setError: (error: string) => void; + endpointFileConfig: EndpointFileConfig; +}) => { + const { fileLimit, fileSizeLimit, totalSizeLimit, supportedMimeTypes } = endpointFileConfig; + const existingFiles = Array.from(files.values()); + const incomingTotalSize = fileList.reduce((total, file) => total + file.size, 0); + if (incomingTotalSize === 0) { + setError('com_error_files_empty'); + return false; + } + const currentTotalSize = existingFiles.reduce((total, file) => total + file.size, 0); + + if (fileLimit && fileList.length + files.size > fileLimit) { + setError(`You can only upload up to ${fileLimit} files at a time.`); + return false; + } + + for (let i = 0; i < fileList.length; i++) { + let originalFile = fileList[i]; + let fileType = originalFile.type; + const extension = originalFile.name.split('.').pop() ?? ''; + const knownCodeType = codeTypeMapping[extension]; + + // Infer MIME type for Known Code files when the type is empty or a mismatch + if (knownCodeType && (!fileType || fileType !== knownCodeType)) { + fileType = knownCodeType; + } + + // Check if the file type is still empty after the extension check + if (!fileType) { + setError('Unable to determine file type for: ' + originalFile.name); + return false; + } + + // Replace empty type with inferred type + if (originalFile.type !== fileType) { + const newFile = new File([originalFile], originalFile.name, { type: fileType }); + originalFile = newFile; + fileList[i] = newFile; + } + + if (!checkType(originalFile.type, supportedMimeTypes)) { + console.log(originalFile); + setError('Currently, unsupported file type: ' + originalFile.type); + return false; + } + + if (fileSizeLimit && originalFile.size >= fileSizeLimit) { + setError(`File size exceeds ${fileSizeLimit / megabyte} MB.`); + return false; + } + } + + if (totalSizeLimit && currentTotalSize + incomingTotalSize > totalSizeLimit) { + setError(`The total size of the files cannot exceed ${totalSizeLimit / megabyte} MB.`); + return false; + } + + const combinedFilesInfo = [ + ...existingFiles.map( + (file) => + `${file.file?.name ?? file.filename}-${file.size}-${file.type?.split('/')[0] ?? 'file'}`, + ), + ...fileList.map( + (file: File | undefined) => + `${file?.name}-${file?.size}-${file?.type.split('/')[0] ?? 'file'}`, + ), + ]; + + const uniqueFilesSet = new Set(combinedFilesInfo); + + if (uniqueFilesSet.size !== combinedFilesInfo.length) { + setError('com_error_files_dupe'); + return false; + } + + return true; +}; From 350e72dede41bdd7298a17201f07201d68551df2 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Sat, 15 Feb 2025 18:52:29 -0500 Subject: [PATCH 09/23] =?UTF-8?q?=F0=9F=A7=A0=20feat:=20Reasoning=20UI=20f?= =?UTF-8?q?or=20Agents=20(#5904)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: bump https-proxy-agent and @librechat/agents * refactor: Improve error logging in OllamaClient for API fetch failures * feat: Add DeepSeek provider support and enhance provider name handling * refactor: Use Providers.OLLAMA constant for model name check in fetchModels function * feat: Enhance formatAgentMessages to handle reasoning content type * feat: OpenRouter Agent Reasoning * hard work and dedicationgit add .env.example :) * fix: Handle Google social login with missing last name Social login with Google was previously displaying 'undefined' when a user's last name was empty or not provided. Changes: - Conditionally render last name only if it exists - Prevent displaying 'undefined' when last name is missing * fix: add missing file endings for developers yml,yaml and log --------- Co-authored-by: Mohamed Al-Duraji Co-authored-by: Deepak Kendole Co-authored-by: Peter Rothlaender --- .env.example | 4 +- api/app/clients/OllamaClient.js | 4 +- api/app/clients/OpenAIClient.js | 7 +- .../prompts/formatAgentMessages.spec.js | 43 + api/app/clients/prompts/formatMessages.js | 15 + api/package.json | 3 +- api/server/controllers/agents/callbacks.js | 16 + api/server/controllers/agents/client.js | 20 +- api/server/controllers/agents/run.js | 10 +- .../services/Endpoints/agents/initialize.js | 12 +- api/server/services/Endpoints/openAI/llm.js | 8 +- api/server/services/ModelService.js | 3 +- api/strategies/googleStrategy.js | 2 +- package-lock.json | 1224 +++++++++-------- packages/data-provider/src/config.ts | 1 + packages/data-provider/src/file-config.ts | 3 + 16 files changed, 757 insertions(+), 618 deletions(-) diff --git a/.env.example b/.env.example index d87021ea4b..142fe88202 100644 --- a/.env.example +++ b/.env.example @@ -389,7 +389,7 @@ FACEBOOK_CALLBACK_URL=/oauth/facebook/callback GITHUB_CLIENT_ID= GITHUB_CLIENT_SECRET= GITHUB_CALLBACK_URL=/oauth/github/callback -# GitHub Eenterprise +# GitHub Enterprise # GITHUB_ENTERPRISE_BASE_URL= # GITHUB_ENTERPRISE_USER_AGENT= @@ -527,4 +527,4 @@ HELP_AND_FAQ_URL=https://librechat.ai #=====================================================# # OpenWeather # #=====================================================# -OPENWEATHER_API_KEY= \ No newline at end of file +OPENWEATHER_API_KEY= diff --git a/api/app/clients/OllamaClient.js b/api/app/clients/OllamaClient.js index d86e120f43..77d007580c 100644 --- a/api/app/clients/OllamaClient.js +++ b/api/app/clients/OllamaClient.js @@ -2,7 +2,7 @@ const { z } = require('zod'); const axios = require('axios'); const { Ollama } = require('ollama'); const { Constants } = require('librechat-data-provider'); -const { deriveBaseURL } = require('~/utils'); +const { deriveBaseURL, logAxiosError } = require('~/utils'); const { sleep } = require('~/server/utils'); const { logger } = require('~/config'); @@ -68,7 +68,7 @@ class OllamaClient { } catch (error) { const logMessage = 'Failed to fetch models from Ollama API. If you are not using Ollama directly, and instead, through some aggregator or reverse proxy that handles fetching via OpenAI spec, ensure the name of the endpoint doesn\'t start with `ollama` (case-insensitive).'; - logger.error(logMessage, error); + logAxiosError({ message: logMessage, error }); return []; } } diff --git a/api/app/clients/OpenAIClient.js b/api/app/clients/OpenAIClient.js index 368e7d6e84..7bd7879dcf 100644 --- a/api/app/clients/OpenAIClient.js +++ b/api/app/clients/OpenAIClient.js @@ -7,6 +7,7 @@ const { ImageDetail, EModelEndpoint, resolveHeaders, + KnownEndpoints, openAISettings, ImageDetailCost, CohereConstants, @@ -116,11 +117,7 @@ class OpenAIClient extends BaseClient { const { reverseProxyUrl: reverseProxy } = this.options; - if ( - !this.useOpenRouter && - reverseProxy && - reverseProxy.includes('https://openrouter.ai/api/v1') - ) { + if (!this.useOpenRouter && reverseProxy && reverseProxy.includes(KnownEndpoints.openrouter)) { this.useOpenRouter = true; } diff --git a/api/app/clients/prompts/formatAgentMessages.spec.js b/api/app/clients/prompts/formatAgentMessages.spec.js index 20731f6984..957409d6ab 100644 --- a/api/app/clients/prompts/formatAgentMessages.spec.js +++ b/api/app/clients/prompts/formatAgentMessages.spec.js @@ -282,4 +282,47 @@ describe('formatAgentMessages', () => { // Additional check to ensure the consecutive assistant messages were combined expect(result[1].content).toHaveLength(2); }); + + it('should skip THINK type content parts', () => { + const payload = [ + { + role: 'assistant', + content: [ + { type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Initial response' }, + { type: ContentTypes.THINK, [ContentTypes.THINK]: 'Reasoning about the problem...' }, + { type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Final answer' }, + ], + }, + ]; + + const result = formatAgentMessages(payload); + + expect(result).toHaveLength(1); + expect(result[0]).toBeInstanceOf(AIMessage); + expect(result[0].content).toEqual('Initial response\nFinal answer'); + }); + + it('should join TEXT content as string when THINK content type is present', () => { + const payload = [ + { + role: 'assistant', + content: [ + { type: ContentTypes.THINK, [ContentTypes.THINK]: 'Analyzing the problem...' }, + { type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'First part of response' }, + { type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Second part of response' }, + { type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Final part of response' }, + ], + }, + ]; + + const result = formatAgentMessages(payload); + + expect(result).toHaveLength(1); + expect(result[0]).toBeInstanceOf(AIMessage); + expect(typeof result[0].content).toBe('string'); + expect(result[0].content).toBe( + 'First part of response\nSecond part of response\nFinal part of response', + ); + expect(result[0].content).not.toContain('Analyzing the problem...'); + }); }); diff --git a/api/app/clients/prompts/formatMessages.js b/api/app/clients/prompts/formatMessages.js index d84e62cca8..235e51e51f 100644 --- a/api/app/clients/prompts/formatMessages.js +++ b/api/app/clients/prompts/formatMessages.js @@ -153,6 +153,7 @@ const formatAgentMessages = (payload) => { let currentContent = []; let lastAIMessage = null; + let hasReasoning = false; for (const part of message.content) { if (part.type === ContentTypes.TEXT && part.tool_call_ids) { /* @@ -207,11 +208,25 @@ const formatAgentMessages = (payload) => { content: output || '', }), ); + } else if (part.type === ContentTypes.THINK) { + hasReasoning = true; + continue; } else { currentContent.push(part); } } + if (hasReasoning) { + currentContent = currentContent + .reduce((acc, curr) => { + if (curr.type === ContentTypes.TEXT) { + return `${acc}${curr[ContentTypes.TEXT]}\n`; + } + return acc; + }, '') + .trim(); + } + if (currentContent.length > 0) { messages.push(new AIMessage({ content: currentContent })); } diff --git a/api/package.json b/api/package.json index 7c37a8deef..e000dd8bd0 100644 --- a/api/package.json +++ b/api/package.json @@ -45,7 +45,7 @@ "@langchain/google-genai": "^0.1.7", "@langchain/google-vertexai": "^0.1.8", "@langchain/textsplitters": "^0.1.0", - "@librechat/agents": "^2.0.5", + "@librechat/agents": "^2.1.2", "@waylaidwanderer/fetch-event-source": "^3.0.1", "axios": "1.7.8", "bcryptjs": "^2.4.3", @@ -65,6 +65,7 @@ "firebase": "^11.0.2", "googleapis": "^126.0.1", "handlebars": "^4.7.7", + "https-proxy-agent": "^7.0.6", "ioredis": "^5.3.2", "js-yaml": "^4.1.0", "jsonwebtoken": "^9.0.0", diff --git a/api/server/controllers/agents/callbacks.js b/api/server/controllers/agents/callbacks.js index 33fe585f42..f43c9db5ba 100644 --- a/api/server/controllers/agents/callbacks.js +++ b/api/server/controllers/agents/callbacks.js @@ -199,6 +199,22 @@ function getDefaultHandlers({ res, aggregateContent, toolEndCallback, collectedU aggregateContent({ event, data }); }, }, + [GraphEvents.ON_REASONING_DELTA]: { + /** + * Handle ON_REASONING_DELTA event. + * @param {string} event - The event name. + * @param {StreamEventData} data - The event data. + * @param {GraphRunnableConfig['configurable']} [metadata] The runnable metadata. + */ + handle: (event, data, metadata) => { + if (metadata?.last_agent_index === metadata?.agent_index) { + sendEvent(res, { event, data }); + } else if (!metadata?.hide_sequential_outputs) { + sendEvent(res, { event, data }); + } + aggregateContent({ event, data }); + }, + }, }; return handlers; diff --git a/api/server/controllers/agents/client.js b/api/server/controllers/agents/client.js index a8e9ad82f7..156424e035 100644 --- a/api/server/controllers/agents/client.js +++ b/api/server/controllers/agents/client.js @@ -20,11 +20,6 @@ const { bedrockOutputParser, removeNullishValues, } = require('librechat-data-provider'); -const { - extractBaseURL, - // constructAzureURL, - // genAzureChatCompletion, -} = require('~/utils'); const { formatMessage, formatAgentMessages, @@ -477,19 +472,6 @@ class AgentClient extends BaseClient { abortController = new AbortController(); } - const baseURL = extractBaseURL(this.completionsUrl); - logger.debug('[api/server/controllers/agents/client.js] chatCompletion', { - baseURL, - payload, - }); - - // if (this.useOpenRouter) { - // opts.defaultHeaders = { - // 'HTTP-Referer': 'https://librechat.ai', - // 'X-Title': 'LibreChat', - // }; - // } - // if (this.options.headers) { // opts.defaultHeaders = { ...opts.defaultHeaders, ...this.options.headers }; // } @@ -626,7 +608,7 @@ class AgentClient extends BaseClient { let systemContent = [ systemMessage, agent.instructions ?? '', - i !== 0 ? agent.additional_instructions ?? '' : '', + i !== 0 ? (agent.additional_instructions ?? '') : '', ] .join('\n') .trim(); diff --git a/api/server/controllers/agents/run.js b/api/server/controllers/agents/run.js index 0fcc58a379..346b9e6df8 100644 --- a/api/server/controllers/agents/run.js +++ b/api/server/controllers/agents/run.js @@ -1,5 +1,5 @@ const { Run, Providers } = require('@librechat/agents'); -const { providerEndpointMap } = require('librechat-data-provider'); +const { providerEndpointMap, KnownEndpoints } = require('librechat-data-provider'); /** * @typedef {import('@librechat/agents').t} t @@ -7,6 +7,7 @@ const { providerEndpointMap } = require('librechat-data-provider'); * @typedef {import('@librechat/agents').StreamEventData} StreamEventData * @typedef {import('@librechat/agents').EventHandler} EventHandler * @typedef {import('@librechat/agents').GraphEvents} GraphEvents + * @typedef {import('@librechat/agents').LLMConfig} LLMConfig * @typedef {import('@librechat/agents').IState} IState */ @@ -32,6 +33,7 @@ async function createRun({ streamUsage = true, }) { const provider = providerEndpointMap[agent.provider] ?? agent.provider; + /** @type {LLMConfig} */ const llmConfig = Object.assign( { provider, @@ -41,6 +43,11 @@ async function createRun({ agent.model_parameters, ); + /** @type {'reasoning_content' | 'reasoning'} */ + let reasoningKey; + if (llmConfig.configuration?.baseURL.includes(KnownEndpoints.openrouter)) { + reasoningKey = 'reasoning'; + } if (/o1(?!-(?:mini|preview)).*$/.test(llmConfig.model)) { llmConfig.streaming = false; llmConfig.disableStreaming = true; @@ -50,6 +57,7 @@ async function createRun({ const graphConfig = { signal, llmConfig, + reasoningKey, tools: agent.tools, instructions: agent.instructions, additional_instructions: agent.additional_instructions, diff --git a/api/server/services/Endpoints/agents/initialize.js b/api/server/services/Endpoints/agents/initialize.js index 3e03a45125..1726ef3460 100644 --- a/api/server/services/Endpoints/agents/initialize.js +++ b/api/server/services/Endpoints/agents/initialize.js @@ -22,12 +22,14 @@ const { getAgent } = require('~/models/Agent'); const { logger } = require('~/config'); const providerConfigMap = { + [Providers.OLLAMA]: initCustom, + [Providers.DEEPSEEK]: initCustom, + [Providers.OPENROUTER]: initCustom, [EModelEndpoint.openAI]: initOpenAI, + [EModelEndpoint.google]: initGoogle, [EModelEndpoint.azureOpenAI]: initOpenAI, [EModelEndpoint.anthropic]: initAnthropic, [EModelEndpoint.bedrock]: getBedrockOptions, - [EModelEndpoint.google]: initGoogle, - [Providers.OLLAMA]: initCustom, }; /** @@ -100,8 +102,10 @@ const initializeAgentOptions = async ({ const provider = agent.provider; let getOptions = providerConfigMap[provider]; - - if (!getOptions) { + if (!getOptions && providerConfigMap[provider.toLowerCase()] != null) { + agent.provider = provider.toLowerCase(); + getOptions = providerConfigMap[agent.provider]; + } else if (!getOptions) { const customEndpointConfig = await getCustomEndpointConfig(provider); if (!customEndpointConfig) { throw new Error(`Provider ${provider} not supported`); diff --git a/api/server/services/Endpoints/openAI/llm.js b/api/server/services/Endpoints/openAI/llm.js index 2587b242c9..05b08b284b 100644 --- a/api/server/services/Endpoints/openAI/llm.js +++ b/api/server/services/Endpoints/openAI/llm.js @@ -1,4 +1,5 @@ const { HttpsProxyAgent } = require('https-proxy-agent'); +const { KnownEndpoints } = require('librechat-data-provider'); const { sanitizeModelName, constructAzureURL } = require('~/utils'); const { isEnabled } = require('~/server/utils'); @@ -57,10 +58,9 @@ function getLLMConfig(apiKey, options = {}) { /** @type {OpenAIClientOptions['configuration']} */ const configOptions = {}; - - // Handle OpenRouter or custom reverse proxy - if (useOpenRouter || reverseProxyUrl === 'https://openrouter.ai/api/v1') { - configOptions.baseURL = 'https://openrouter.ai/api/v1'; + if (useOpenRouter || reverseProxyUrl.includes(KnownEndpoints.openrouter)) { + llmConfig.include_reasoning = true; + configOptions.baseURL = reverseProxyUrl; configOptions.defaultHeaders = Object.assign( { 'HTTP-Referer': 'https://librechat.ai', diff --git a/api/server/services/ModelService.js b/api/server/services/ModelService.js index 1394a5d697..9630f0bd87 100644 --- a/api/server/services/ModelService.js +++ b/api/server/services/ModelService.js @@ -1,4 +1,5 @@ const axios = require('axios'); +const { Providers } = require('@librechat/agents'); const { HttpsProxyAgent } = require('https-proxy-agent'); const { EModelEndpoint, defaultModels, CacheKeys } = require('librechat-data-provider'); const { inputSchema, logAxiosError, extractBaseURL, processModelData } = require('~/utils'); @@ -57,7 +58,7 @@ const fetchModels = async ({ return models; } - if (name && name.toLowerCase().startsWith('ollama')) { + if (name && name.toLowerCase().startsWith(Providers.OLLAMA)) { return await OllamaClient.fetchModels(baseURL); } diff --git a/api/strategies/googleStrategy.js b/api/strategies/googleStrategy.js index ab8a268953..fd65823327 100644 --- a/api/strategies/googleStrategy.js +++ b/api/strategies/googleStrategy.js @@ -6,7 +6,7 @@ const getProfileDetails = ({ profile }) => ({ id: profile.id, avatarUrl: profile.photos[0].value, username: profile.name.givenName, - name: `${profile.name.givenName} ${profile.name.familyName}`, + name: `${profile.name.givenName}${profile.name.familyName ? ` ${profile.name.familyName}` : ''}`, emailVerified: profile.emails[0].verified, }); diff --git a/package-lock.json b/package-lock.json index 8601c651e3..dfae230c57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,7 +61,7 @@ "@langchain/google-genai": "^0.1.7", "@langchain/google-vertexai": "^0.1.8", "@langchain/textsplitters": "^0.1.0", - "@librechat/agents": "^2.0.5", + "@librechat/agents": "^2.1.2", "@waylaidwanderer/fetch-event-source": "^3.0.1", "axios": "1.7.8", "bcryptjs": "^2.4.3", @@ -81,6 +81,7 @@ "firebase": "^11.0.2", "googleapis": "^126.0.1", "handlebars": "^4.7.7", + "https-proxy-agent": "^7.0.6", "ioredis": "^5.3.2", "js-yaml": "^4.1.0", "jsonwebtoken": "^9.0.0", @@ -649,565 +650,6 @@ "@langchain/core": ">=0.2.21 <0.4.0" } }, - "api/node_modules/@librechat/agents": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-2.0.5.tgz", - "integrity": "sha512-LSB/j6+KsuIfJVVSIK/qoQnG5tBym+wOeKzhb0I9bli1fZBC3UcTx9C5P779u3tHNnINGz6NpZ4UgkrNg1BABA==", - "dependencies": { - "@aws-crypto/sha256-js": "^5.2.0", - "@aws-sdk/credential-provider-node": "^3.613.0", - "@aws-sdk/types": "^3.609.0", - "@langchain/anthropic": "^0.3.12", - "@langchain/aws": "^0.1.3", - "@langchain/community": "^0.3.27", - "@langchain/core": "^0.3.37", - "@langchain/google-genai": "^0.1.7", - "@langchain/google-vertexai": "^0.1.8", - "@langchain/langgraph": "^0.2.41", - "@langchain/mistralai": "^0.0.26", - "@langchain/ollama": "^0.1.5", - "@langchain/openai": "^0.4.2", - "@smithy/eventstream-codec": "^2.2.0", - "@smithy/protocol-http": "^3.0.6", - "@smithy/signature-v4": "^2.0.10", - "@smithy/util-utf8": "^2.0.0", - "dotenv": "^16.4.5", - "nanoid": "^3.3.7" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "api/node_modules/@librechat/agents/node_modules/@langchain/community": { - "version": "0.3.30", - "resolved": "https://registry.npmjs.org/@langchain/community/-/community-0.3.30.tgz", - "integrity": "sha512-KJEZqFrsLfpa/3aWm96F2At2/l0YGTkdcwCguJtt30bSCN5nyHt0pCAftXjdsmb5/eSnGX24BEb7BGOl3rRw2w==", - "dependencies": { - "@langchain/openai": ">=0.2.0 <0.5.0", - "binary-extensions": "^2.2.0", - "expr-eval": "^2.0.2", - "flat": "^5.0.2", - "js-yaml": "^4.1.0", - "langchain": ">=0.2.3 <0.3.0 || >=0.3.4 <0.4.0", - "langsmith": ">=0.2.8 <0.4.0", - "uuid": "^10.0.0", - "zod": "^3.22.3", - "zod-to-json-schema": "^3.22.5" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@arcjet/redact": "^v1.0.0-alpha.23", - "@aws-crypto/sha256-js": "^5.0.0", - "@aws-sdk/client-bedrock-agent-runtime": "^3.583.0", - "@aws-sdk/client-bedrock-runtime": "^3.422.0", - "@aws-sdk/client-dynamodb": "^3.310.0", - "@aws-sdk/client-kendra": "^3.352.0", - "@aws-sdk/client-lambda": "^3.310.0", - "@aws-sdk/client-s3": "^3.310.0", - "@aws-sdk/client-sagemaker-runtime": "^3.310.0", - "@aws-sdk/client-sfn": "^3.310.0", - "@aws-sdk/credential-provider-node": "^3.388.0", - "@azure/search-documents": "^12.0.0", - "@azure/storage-blob": "^12.15.0", - "@browserbasehq/sdk": "*", - "@browserbasehq/stagehand": "^1.0.0", - "@clickhouse/client": "^0.2.5", - "@cloudflare/ai": "*", - "@datastax/astra-db-ts": "^1.0.0", - "@elastic/elasticsearch": "^8.4.0", - "@getmetal/metal-sdk": "*", - "@getzep/zep-cloud": "^1.0.6", - "@getzep/zep-js": "^0.9.0", - "@gomomento/sdk": "^1.51.1", - "@gomomento/sdk-core": "^1.51.1", - "@google-ai/generativelanguage": "*", - "@google-cloud/storage": "^6.10.1 || ^7.7.0", - "@gradientai/nodejs-sdk": "^1.2.0", - "@huggingface/inference": "^2.6.4", - "@huggingface/transformers": "^3.2.3", - "@ibm-cloud/watsonx-ai": "*", - "@lancedb/lancedb": "^0.12.0", - "@langchain/core": ">=0.2.21 <0.4.0", - "@layerup/layerup-security": "^1.5.12", - "@libsql/client": "^0.14.0", - "@mendable/firecrawl-js": "^1.4.3", - "@mlc-ai/web-llm": "*", - "@mozilla/readability": "*", - "@neondatabase/serverless": "*", - "@notionhq/client": "^2.2.10", - "@opensearch-project/opensearch": "*", - "@pinecone-database/pinecone": "*", - "@planetscale/database": "^1.8.0", - "@premai/prem-sdk": "^0.3.25", - "@qdrant/js-client-rest": "^1.8.2", - "@raycast/api": "^1.55.2", - "@rockset/client": "^0.9.1", - "@smithy/eventstream-codec": "^2.0.5", - "@smithy/protocol-http": "^3.0.6", - "@smithy/signature-v4": "^2.0.10", - "@smithy/util-utf8": "^2.0.0", - "@spider-cloud/spider-client": "^0.0.21", - "@supabase/supabase-js": "^2.45.0", - "@tensorflow-models/universal-sentence-encoder": "*", - "@tensorflow/tfjs-converter": "*", - "@tensorflow/tfjs-core": "*", - "@upstash/ratelimit": "^1.1.3 || ^2.0.3", - "@upstash/redis": "^1.20.6", - "@upstash/vector": "^1.1.1", - "@vercel/kv": "*", - "@vercel/postgres": "*", - "@writerai/writer-sdk": "^0.40.2", - "@xata.io/client": "^0.28.0", - "@zilliz/milvus2-sdk-node": ">=2.3.5", - "apify-client": "^2.7.1", - "assemblyai": "^4.6.0", - "better-sqlite3": ">=9.4.0 <12.0.0", - "cassandra-driver": "^4.7.2", - "cborg": "^4.1.1", - "cheerio": "^1.0.0-rc.12", - "chromadb": "*", - "closevector-common": "0.1.3", - "closevector-node": "0.1.6", - "closevector-web": "0.1.6", - "cohere-ai": "*", - "convex": "^1.3.1", - "crypto-js": "^4.2.0", - "d3-dsv": "^2.0.0", - "discord.js": "^14.14.1", - "dria": "^0.0.3", - "duck-duck-scrape": "^2.2.5", - "epub2": "^3.0.1", - "faiss-node": "^0.5.1", - "fast-xml-parser": "*", - "firebase-admin": "^11.9.0 || ^12.0.0", - "google-auth-library": "*", - "googleapis": "*", - "hnswlib-node": "^3.0.0", - "html-to-text": "^9.0.5", - "ibm-cloud-sdk-core": "*", - "ignore": "^5.2.0", - "interface-datastore": "^8.2.11", - "ioredis": "^5.3.2", - "it-all": "^3.0.4", - "jsdom": "*", - "jsonwebtoken": "^9.0.2", - "llmonitor": "^0.5.9", - "lodash": "^4.17.21", - "lunary": "^0.7.10", - "mammoth": "^1.6.0", - "mongodb": ">=5.2.0", - "mysql2": "^3.9.8", - "neo4j-driver": "*", - "notion-to-md": "^3.1.0", - "officeparser": "^4.0.4", - "openai": "*", - "pdf-parse": "1.1.1", - "pg": "^8.11.0", - "pg-copy-streams": "^6.0.5", - "pickleparser": "^0.2.1", - "playwright": "^1.32.1", - "portkey-ai": "^0.1.11", - "puppeteer": "*", - "pyodide": ">=0.24.1 <0.27.0", - "redis": "*", - "replicate": "*", - "sonix-speech-recognition": "^2.1.1", - "srt-parser-2": "^1.2.3", - "typeorm": "^0.3.20", - "typesense": "^1.5.3", - "usearch": "^1.1.1", - "voy-search": "0.6.2", - "weaviate-ts-client": "*", - "web-auth-library": "^1.0.3", - "word-extractor": "*", - "ws": "^8.14.2", - "youtubei.js": "*" - }, - "peerDependenciesMeta": { - "@arcjet/redact": { - "optional": true - }, - "@aws-crypto/sha256-js": { - "optional": true - }, - "@aws-sdk/client-bedrock-agent-runtime": { - "optional": true - }, - "@aws-sdk/client-bedrock-runtime": { - "optional": true - }, - "@aws-sdk/client-dynamodb": { - "optional": true - }, - "@aws-sdk/client-kendra": { - "optional": true - }, - "@aws-sdk/client-lambda": { - "optional": true - }, - "@aws-sdk/client-s3": { - "optional": true - }, - "@aws-sdk/client-sagemaker-runtime": { - "optional": true - }, - "@aws-sdk/client-sfn": { - "optional": true - }, - "@aws-sdk/credential-provider-node": { - "optional": true - }, - "@azure/search-documents": { - "optional": true - }, - "@azure/storage-blob": { - "optional": true - }, - "@browserbasehq/sdk": { - "optional": true - }, - "@clickhouse/client": { - "optional": true - }, - "@cloudflare/ai": { - "optional": true - }, - "@datastax/astra-db-ts": { - "optional": true - }, - "@elastic/elasticsearch": { - "optional": true - }, - "@getmetal/metal-sdk": { - "optional": true - }, - "@getzep/zep-cloud": { - "optional": true - }, - "@getzep/zep-js": { - "optional": true - }, - "@gomomento/sdk": { - "optional": true - }, - "@gomomento/sdk-core": { - "optional": true - }, - "@google-ai/generativelanguage": { - "optional": true - }, - "@google-cloud/storage": { - "optional": true - }, - "@gradientai/nodejs-sdk": { - "optional": true - }, - "@huggingface/inference": { - "optional": true - }, - "@huggingface/transformers": { - "optional": true - }, - "@lancedb/lancedb": { - "optional": true - }, - "@layerup/layerup-security": { - "optional": true - }, - "@libsql/client": { - "optional": true - }, - "@mendable/firecrawl-js": { - "optional": true - }, - "@mlc-ai/web-llm": { - "optional": true - }, - "@mozilla/readability": { - "optional": true - }, - "@neondatabase/serverless": { - "optional": true - }, - "@notionhq/client": { - "optional": true - }, - "@opensearch-project/opensearch": { - "optional": true - }, - "@pinecone-database/pinecone": { - "optional": true - }, - "@planetscale/database": { - "optional": true - }, - "@premai/prem-sdk": { - "optional": true - }, - "@qdrant/js-client-rest": { - "optional": true - }, - "@raycast/api": { - "optional": true - }, - "@rockset/client": { - "optional": true - }, - "@smithy/eventstream-codec": { - "optional": true - }, - "@smithy/protocol-http": { - "optional": true - }, - "@smithy/signature-v4": { - "optional": true - }, - "@smithy/util-utf8": { - "optional": true - }, - "@spider-cloud/spider-client": { - "optional": true - }, - "@supabase/supabase-js": { - "optional": true - }, - "@tensorflow-models/universal-sentence-encoder": { - "optional": true - }, - "@tensorflow/tfjs-converter": { - "optional": true - }, - "@tensorflow/tfjs-core": { - "optional": true - }, - "@upstash/ratelimit": { - "optional": true - }, - "@upstash/redis": { - "optional": true - }, - "@upstash/vector": { - "optional": true - }, - "@vercel/kv": { - "optional": true - }, - "@vercel/postgres": { - "optional": true - }, - "@writerai/writer-sdk": { - "optional": true - }, - "@xata.io/client": { - "optional": true - }, - "@zilliz/milvus2-sdk-node": { - "optional": true - }, - "apify-client": { - "optional": true - }, - "assemblyai": { - "optional": true - }, - "better-sqlite3": { - "optional": true - }, - "cassandra-driver": { - "optional": true - }, - "cborg": { - "optional": true - }, - "cheerio": { - "optional": true - }, - "chromadb": { - "optional": true - }, - "closevector-common": { - "optional": true - }, - "closevector-node": { - "optional": true - }, - "closevector-web": { - "optional": true - }, - "cohere-ai": { - "optional": true - }, - "convex": { - "optional": true - }, - "crypto-js": { - "optional": true - }, - "d3-dsv": { - "optional": true - }, - "discord.js": { - "optional": true - }, - "dria": { - "optional": true - }, - "duck-duck-scrape": { - "optional": true - }, - "epub2": { - "optional": true - }, - "faiss-node": { - "optional": true - }, - "fast-xml-parser": { - "optional": true - }, - "firebase-admin": { - "optional": true - }, - "google-auth-library": { - "optional": true - }, - "googleapis": { - "optional": true - }, - "hnswlib-node": { - "optional": true - }, - "html-to-text": { - "optional": true - }, - "ignore": { - "optional": true - }, - "interface-datastore": { - "optional": true - }, - "ioredis": { - "optional": true - }, - "it-all": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "jsonwebtoken": { - "optional": true - }, - "llmonitor": { - "optional": true - }, - "lodash": { - "optional": true - }, - "lunary": { - "optional": true - }, - "mammoth": { - "optional": true - }, - "mongodb": { - "optional": true - }, - "mysql2": { - "optional": true - }, - "neo4j-driver": { - "optional": true - }, - "notion-to-md": { - "optional": true - }, - "officeparser": { - "optional": true - }, - "pdf-parse": { - "optional": true - }, - "pg": { - "optional": true - }, - "pg-copy-streams": { - "optional": true - }, - "pickleparser": { - "optional": true - }, - "playwright": { - "optional": true - }, - "portkey-ai": { - "optional": true - }, - "puppeteer": { - "optional": true - }, - "pyodide": { - "optional": true - }, - "redis": { - "optional": true - }, - "replicate": { - "optional": true - }, - "sonix-speech-recognition": { - "optional": true - }, - "srt-parser-2": { - "optional": true - }, - "typeorm": { - "optional": true - }, - "typesense": { - "optional": true - }, - "usearch": { - "optional": true - }, - "voy-search": { - "optional": true - }, - "weaviate-ts-client": { - "optional": true - }, - "web-auth-library": { - "optional": true - }, - "word-extractor": { - "optional": true - }, - "ws": { - "optional": true - }, - "youtubei.js": { - "optional": true - } - } - }, - "api/node_modules/@librechat/agents/node_modules/@langchain/openai": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.4.4.tgz", - "integrity": "sha512-UZybJeMd8+UX7Kn47kuFYfqKdBCeBUWNqDtmAr6ZUIMMnlsNIb6MkrEEhGgAEjGCpdT4CU8U/DyyddTz+JayOQ==", - "dependencies": { - "js-tiktoken": "^1.0.12", - "openai": "^4.77.0", - "zod": "^3.22.4", - "zod-to-json-schema": "^3.22.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@langchain/core": ">=0.3.39 <0.4.0" - } - }, "api/node_modules/@types/node": { "version": "18.19.14", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.14.tgz", @@ -1229,7 +671,6 @@ "version": "7.1.3", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 14" @@ -1330,8 +771,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", "dependencies": { "agent-base": "^7.1.2", "debug": "4" @@ -7778,9 +7217,9 @@ } }, "node_modules/@browserbasehq/stagehand/node_modules/@types/node": { - "version": "18.19.75", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.75.tgz", - "integrity": "sha512-UIksWtThob6ZVSyxcOqCLOUNg/dyO1Qvx4McgeuhrEtHTLFTf7BBhEazaE4K806FGTPtzd/2sE90qn4fVr7cyw==", + "version": "18.19.76", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.76.tgz", + "integrity": "sha512-yvR7Q9LdPz2vGpmpJX5LolrgRdWvB67MJKDPSgIIzpFbaf9a1j/f5DnLp5VDyHGMR0QZHlTr1afsD87QCXFHKw==", "peer": true, "dependencies": { "undici-types": "~5.26.4" @@ -11663,6 +11102,38 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/@langchain/deepseek": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@langchain/deepseek/-/deepseek-0.0.1.tgz", + "integrity": "sha512-jgrbitvV4p7Kqo/Fyni9coCliNXUrJ2XChdR8eHvQg3RL+w13DIQjJn2mrkCrb7v6Is1rI7It2x3yIbADL71Yg==", + "dependencies": { + "@langchain/openai": "^0.4.2", + "zod": "^3.24.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.3.0 <0.4.0" + } + }, + "node_modules/@langchain/deepseek/node_modules/@langchain/openai": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.4.4.tgz", + "integrity": "sha512-UZybJeMd8+UX7Kn47kuFYfqKdBCeBUWNqDtmAr6ZUIMMnlsNIb6MkrEEhGgAEjGCpdT4CU8U/DyyddTz+JayOQ==", + "dependencies": { + "js-tiktoken": "^1.0.12", + "openai": "^4.77.0", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.3.39 <0.4.0" + } + }, "node_modules/@langchain/google-common": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/@langchain/google-common/-/google-common-0.1.8.tgz", @@ -11735,9 +11206,9 @@ } }, "node_modules/@langchain/langgraph": { - "version": "0.2.45", - "resolved": "https://registry.npmjs.org/@langchain/langgraph/-/langgraph-0.2.45.tgz", - "integrity": "sha512-yemuA+aTIRLL3WBVQ5TGvFMeEJQm2zoVyjMvHWyekIvg4w7Q4cu3CYB8f+yOXwd6OaxMtnNIX0wGh4hIw/Db+A==", + "version": "0.2.46", + "resolved": "https://registry.npmjs.org/@langchain/langgraph/-/langgraph-0.2.46.tgz", + "integrity": "sha512-dO/EKvSyYfHCmvF7JYBwSuc2czy5dOF6ScSNEmQxq/XHXkN7QkdahBkWPAU/+iw6Cafeljc/XuTxBJxMZB2pFQ==", "dependencies": { "@langchain/langgraph-checkpoint": "~0.0.15", "@langchain/langgraph-sdk": "~0.0.32", @@ -11778,14 +11249,18 @@ } }, "node_modules/@langchain/langgraph-sdk": { - "version": "0.0.37", - "resolved": "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-0.0.37.tgz", - "integrity": "sha512-+6aTfUQZsAQBrz2DuKyMt6SrCElJvNWm8Iw8gYZhlHFVwJHrpu0cvn5leOzWrG2gO1DDH9aR4Zi2AzJ95gT0ig==", + "version": "0.0.42", + "resolved": "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-0.0.42.tgz", + "integrity": "sha512-seOcLN6+MWxCGEVfKY2pFcFpJB56DZq7iXhe5Br/xF7Uxspm1e50Z8f0k21XOJFQyMheR6AZ9zXEBVeX1ZsDMg==", "dependencies": { "@types/json-schema": "^7.0.15", "p-queue": "^6.6.2", "p-retry": "4", "uuid": "^9.0.0" + }, + "peerDependencies": { + "@langchain/core": ">=0.2.31 <0.4.0", + "react": "^18 || ^19" } }, "node_modules/@langchain/langgraph/node_modules/uuid": { @@ -12053,6 +11528,599 @@ "@lezer/common": "^1.0.0" } }, + "node_modules/@librechat/agents": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-2.1.2.tgz", + "integrity": "sha512-OYGIX4qJAcd++sdtYDVW31QSpnJjsK90Bks7hASxbyegy6WRVU3LUd6/ko0gRv0O7RUVE98/wtly3Q3qLbEz1w==", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-sdk/credential-provider-node": "^3.613.0", + "@aws-sdk/types": "^3.609.0", + "@langchain/anthropic": "^0.3.12", + "@langchain/aws": "^0.1.3", + "@langchain/community": "^0.3.27", + "@langchain/core": "^0.3.37", + "@langchain/deepseek": "^0.0.1", + "@langchain/google-genai": "^0.1.7", + "@langchain/google-vertexai": "^0.1.8", + "@langchain/langgraph": "^0.2.41", + "@langchain/mistralai": "^0.0.26", + "@langchain/ollama": "^0.1.5", + "@langchain/openai": "^0.4.2", + "@smithy/eventstream-codec": "^2.2.0", + "@smithy/protocol-http": "^3.0.6", + "@smithy/signature-v4": "^2.0.10", + "@smithy/util-utf8": "^2.0.0", + "dotenv": "^16.4.7", + "https-proxy-agent": "^7.0.6", + "nanoid": "^3.3.7" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@librechat/agents/node_modules/@langchain/community": { + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@langchain/community/-/community-0.3.30.tgz", + "integrity": "sha512-KJEZqFrsLfpa/3aWm96F2At2/l0YGTkdcwCguJtt30bSCN5nyHt0pCAftXjdsmb5/eSnGX24BEb7BGOl3rRw2w==", + "dependencies": { + "@langchain/openai": ">=0.2.0 <0.5.0", + "binary-extensions": "^2.2.0", + "expr-eval": "^2.0.2", + "flat": "^5.0.2", + "js-yaml": "^4.1.0", + "langchain": ">=0.2.3 <0.3.0 || >=0.3.4 <0.4.0", + "langsmith": ">=0.2.8 <0.4.0", + "uuid": "^10.0.0", + "zod": "^3.22.3", + "zod-to-json-schema": "^3.22.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@arcjet/redact": "^v1.0.0-alpha.23", + "@aws-crypto/sha256-js": "^5.0.0", + "@aws-sdk/client-bedrock-agent-runtime": "^3.583.0", + "@aws-sdk/client-bedrock-runtime": "^3.422.0", + "@aws-sdk/client-dynamodb": "^3.310.0", + "@aws-sdk/client-kendra": "^3.352.0", + "@aws-sdk/client-lambda": "^3.310.0", + "@aws-sdk/client-s3": "^3.310.0", + "@aws-sdk/client-sagemaker-runtime": "^3.310.0", + "@aws-sdk/client-sfn": "^3.310.0", + "@aws-sdk/credential-provider-node": "^3.388.0", + "@azure/search-documents": "^12.0.0", + "@azure/storage-blob": "^12.15.0", + "@browserbasehq/sdk": "*", + "@browserbasehq/stagehand": "^1.0.0", + "@clickhouse/client": "^0.2.5", + "@cloudflare/ai": "*", + "@datastax/astra-db-ts": "^1.0.0", + "@elastic/elasticsearch": "^8.4.0", + "@getmetal/metal-sdk": "*", + "@getzep/zep-cloud": "^1.0.6", + "@getzep/zep-js": "^0.9.0", + "@gomomento/sdk": "^1.51.1", + "@gomomento/sdk-core": "^1.51.1", + "@google-ai/generativelanguage": "*", + "@google-cloud/storage": "^6.10.1 || ^7.7.0", + "@gradientai/nodejs-sdk": "^1.2.0", + "@huggingface/inference": "^2.6.4", + "@huggingface/transformers": "^3.2.3", + "@ibm-cloud/watsonx-ai": "*", + "@lancedb/lancedb": "^0.12.0", + "@langchain/core": ">=0.2.21 <0.4.0", + "@layerup/layerup-security": "^1.5.12", + "@libsql/client": "^0.14.0", + "@mendable/firecrawl-js": "^1.4.3", + "@mlc-ai/web-llm": "*", + "@mozilla/readability": "*", + "@neondatabase/serverless": "*", + "@notionhq/client": "^2.2.10", + "@opensearch-project/opensearch": "*", + "@pinecone-database/pinecone": "*", + "@planetscale/database": "^1.8.0", + "@premai/prem-sdk": "^0.3.25", + "@qdrant/js-client-rest": "^1.8.2", + "@raycast/api": "^1.55.2", + "@rockset/client": "^0.9.1", + "@smithy/eventstream-codec": "^2.0.5", + "@smithy/protocol-http": "^3.0.6", + "@smithy/signature-v4": "^2.0.10", + "@smithy/util-utf8": "^2.0.0", + "@spider-cloud/spider-client": "^0.0.21", + "@supabase/supabase-js": "^2.45.0", + "@tensorflow-models/universal-sentence-encoder": "*", + "@tensorflow/tfjs-converter": "*", + "@tensorflow/tfjs-core": "*", + "@upstash/ratelimit": "^1.1.3 || ^2.0.3", + "@upstash/redis": "^1.20.6", + "@upstash/vector": "^1.1.1", + "@vercel/kv": "*", + "@vercel/postgres": "*", + "@writerai/writer-sdk": "^0.40.2", + "@xata.io/client": "^0.28.0", + "@zilliz/milvus2-sdk-node": ">=2.3.5", + "apify-client": "^2.7.1", + "assemblyai": "^4.6.0", + "better-sqlite3": ">=9.4.0 <12.0.0", + "cassandra-driver": "^4.7.2", + "cborg": "^4.1.1", + "cheerio": "^1.0.0-rc.12", + "chromadb": "*", + "closevector-common": "0.1.3", + "closevector-node": "0.1.6", + "closevector-web": "0.1.6", + "cohere-ai": "*", + "convex": "^1.3.1", + "crypto-js": "^4.2.0", + "d3-dsv": "^2.0.0", + "discord.js": "^14.14.1", + "dria": "^0.0.3", + "duck-duck-scrape": "^2.2.5", + "epub2": "^3.0.1", + "faiss-node": "^0.5.1", + "fast-xml-parser": "*", + "firebase-admin": "^11.9.0 || ^12.0.0", + "google-auth-library": "*", + "googleapis": "*", + "hnswlib-node": "^3.0.0", + "html-to-text": "^9.0.5", + "ibm-cloud-sdk-core": "*", + "ignore": "^5.2.0", + "interface-datastore": "^8.2.11", + "ioredis": "^5.3.2", + "it-all": "^3.0.4", + "jsdom": "*", + "jsonwebtoken": "^9.0.2", + "llmonitor": "^0.5.9", + "lodash": "^4.17.21", + "lunary": "^0.7.10", + "mammoth": "^1.6.0", + "mongodb": ">=5.2.0", + "mysql2": "^3.9.8", + "neo4j-driver": "*", + "notion-to-md": "^3.1.0", + "officeparser": "^4.0.4", + "openai": "*", + "pdf-parse": "1.1.1", + "pg": "^8.11.0", + "pg-copy-streams": "^6.0.5", + "pickleparser": "^0.2.1", + "playwright": "^1.32.1", + "portkey-ai": "^0.1.11", + "puppeteer": "*", + "pyodide": ">=0.24.1 <0.27.0", + "redis": "*", + "replicate": "*", + "sonix-speech-recognition": "^2.1.1", + "srt-parser-2": "^1.2.3", + "typeorm": "^0.3.20", + "typesense": "^1.5.3", + "usearch": "^1.1.1", + "voy-search": "0.6.2", + "weaviate-ts-client": "*", + "web-auth-library": "^1.0.3", + "word-extractor": "*", + "ws": "^8.14.2", + "youtubei.js": "*" + }, + "peerDependenciesMeta": { + "@arcjet/redact": { + "optional": true + }, + "@aws-crypto/sha256-js": { + "optional": true + }, + "@aws-sdk/client-bedrock-agent-runtime": { + "optional": true + }, + "@aws-sdk/client-bedrock-runtime": { + "optional": true + }, + "@aws-sdk/client-dynamodb": { + "optional": true + }, + "@aws-sdk/client-kendra": { + "optional": true + }, + "@aws-sdk/client-lambda": { + "optional": true + }, + "@aws-sdk/client-s3": { + "optional": true + }, + "@aws-sdk/client-sagemaker-runtime": { + "optional": true + }, + "@aws-sdk/client-sfn": { + "optional": true + }, + "@aws-sdk/credential-provider-node": { + "optional": true + }, + "@azure/search-documents": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@browserbasehq/sdk": { + "optional": true + }, + "@clickhouse/client": { + "optional": true + }, + "@cloudflare/ai": { + "optional": true + }, + "@datastax/astra-db-ts": { + "optional": true + }, + "@elastic/elasticsearch": { + "optional": true + }, + "@getmetal/metal-sdk": { + "optional": true + }, + "@getzep/zep-cloud": { + "optional": true + }, + "@getzep/zep-js": { + "optional": true + }, + "@gomomento/sdk": { + "optional": true + }, + "@gomomento/sdk-core": { + "optional": true + }, + "@google-ai/generativelanguage": { + "optional": true + }, + "@google-cloud/storage": { + "optional": true + }, + "@gradientai/nodejs-sdk": { + "optional": true + }, + "@huggingface/inference": { + "optional": true + }, + "@huggingface/transformers": { + "optional": true + }, + "@lancedb/lancedb": { + "optional": true + }, + "@layerup/layerup-security": { + "optional": true + }, + "@libsql/client": { + "optional": true + }, + "@mendable/firecrawl-js": { + "optional": true + }, + "@mlc-ai/web-llm": { + "optional": true + }, + "@mozilla/readability": { + "optional": true + }, + "@neondatabase/serverless": { + "optional": true + }, + "@notionhq/client": { + "optional": true + }, + "@opensearch-project/opensearch": { + "optional": true + }, + "@pinecone-database/pinecone": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@premai/prem-sdk": { + "optional": true + }, + "@qdrant/js-client-rest": { + "optional": true + }, + "@raycast/api": { + "optional": true + }, + "@rockset/client": { + "optional": true + }, + "@smithy/eventstream-codec": { + "optional": true + }, + "@smithy/protocol-http": { + "optional": true + }, + "@smithy/signature-v4": { + "optional": true + }, + "@smithy/util-utf8": { + "optional": true + }, + "@spider-cloud/spider-client": { + "optional": true + }, + "@supabase/supabase-js": { + "optional": true + }, + "@tensorflow-models/universal-sentence-encoder": { + "optional": true + }, + "@tensorflow/tfjs-converter": { + "optional": true + }, + "@tensorflow/tfjs-core": { + "optional": true + }, + "@upstash/ratelimit": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@upstash/vector": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "@vercel/postgres": { + "optional": true + }, + "@writerai/writer-sdk": { + "optional": true + }, + "@xata.io/client": { + "optional": true + }, + "@zilliz/milvus2-sdk-node": { + "optional": true + }, + "apify-client": { + "optional": true + }, + "assemblyai": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "cassandra-driver": { + "optional": true + }, + "cborg": { + "optional": true + }, + "cheerio": { + "optional": true + }, + "chromadb": { + "optional": true + }, + "closevector-common": { + "optional": true + }, + "closevector-node": { + "optional": true + }, + "closevector-web": { + "optional": true + }, + "cohere-ai": { + "optional": true + }, + "convex": { + "optional": true + }, + "crypto-js": { + "optional": true + }, + "d3-dsv": { + "optional": true + }, + "discord.js": { + "optional": true + }, + "dria": { + "optional": true + }, + "duck-duck-scrape": { + "optional": true + }, + "epub2": { + "optional": true + }, + "faiss-node": { + "optional": true + }, + "fast-xml-parser": { + "optional": true + }, + "firebase-admin": { + "optional": true + }, + "google-auth-library": { + "optional": true + }, + "googleapis": { + "optional": true + }, + "hnswlib-node": { + "optional": true + }, + "html-to-text": { + "optional": true + }, + "ignore": { + "optional": true + }, + "interface-datastore": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "it-all": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "jsonwebtoken": { + "optional": true + }, + "llmonitor": { + "optional": true + }, + "lodash": { + "optional": true + }, + "lunary": { + "optional": true + }, + "mammoth": { + "optional": true + }, + "mongodb": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "neo4j-driver": { + "optional": true + }, + "notion-to-md": { + "optional": true + }, + "officeparser": { + "optional": true + }, + "pdf-parse": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-copy-streams": { + "optional": true + }, + "pickleparser": { + "optional": true + }, + "playwright": { + "optional": true + }, + "portkey-ai": { + "optional": true + }, + "puppeteer": { + "optional": true + }, + "pyodide": { + "optional": true + }, + "redis": { + "optional": true + }, + "replicate": { + "optional": true + }, + "sonix-speech-recognition": { + "optional": true + }, + "srt-parser-2": { + "optional": true + }, + "typeorm": { + "optional": true + }, + "typesense": { + "optional": true + }, + "usearch": { + "optional": true + }, + "voy-search": { + "optional": true + }, + "weaviate-ts-client": { + "optional": true + }, + "web-auth-library": { + "optional": true + }, + "word-extractor": { + "optional": true + }, + "ws": { + "optional": true + }, + "youtubei.js": { + "optional": true + } + } + }, + "node_modules/@librechat/agents/node_modules/@langchain/openai": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.4.4.tgz", + "integrity": "sha512-UZybJeMd8+UX7Kn47kuFYfqKdBCeBUWNqDtmAr6ZUIMMnlsNIb6MkrEEhGgAEjGCpdT4CU8U/DyyddTz+JayOQ==", + "dependencies": { + "js-tiktoken": "^1.0.12", + "openai": "^4.77.0", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.3.39 <0.4.0" + } + }, + "node_modules/@librechat/agents/node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/@librechat/agents/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@librechat/agents/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@librechat/backend": { "resolved": "api", "link": true @@ -20079,9 +20147,9 @@ } }, "node_modules/dotenv": { - "version": "16.4.5", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", - "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", "engines": { "node": ">=12" }, @@ -37249,9 +37317,9 @@ } }, "node_modules/zod": { - "version": "3.23.8", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", - "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "version": "3.24.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", + "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/packages/data-provider/src/config.ts b/packages/data-provider/src/config.ts index 82137e6157..350bf8eb53 100644 --- a/packages/data-provider/src/config.ts +++ b/packages/data-provider/src/config.ts @@ -620,6 +620,7 @@ export const alternateName = { [EModelEndpoint.custom]: 'Custom', [EModelEndpoint.bedrock]: 'AWS Bedrock', [KnownEndpoints.ollama]: 'Ollama', + [KnownEndpoints.deepseek]: 'DeepSeek', [KnownEndpoints.xai]: 'xAI', }; diff --git a/packages/data-provider/src/file-config.ts b/packages/data-provider/src/file-config.ts index a34d44ff3b..c380dacf6b 100644 --- a/packages/data-provider/src/file-config.ts +++ b/packages/data-provider/src/file-config.ts @@ -149,6 +149,9 @@ export const codeTypeMapping: { [key: string]: string } = { ts: 'application/typescript', tar: 'application/x-tar', zip: 'application/zip', + yml: 'application/x-yaml', + yaml: 'application/x-yaml', + log: 'text/plain', }; export const retrievalMimeTypes = [ From 93dd365fdab6dc3030bfbb4271396dd02700e486 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Sun, 16 Feb 2025 10:52:29 -0500 Subject: [PATCH 10/23] =?UTF-8?q?=F0=9F=90=9E=20fix:=20Add=20Null=20Checks?= =?UTF-8?q?=20for=20BaseURL=20in=20Agent=20Config=20(#5908)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/server/controllers/agents/run.js | 2 +- api/server/services/Endpoints/openAI/llm.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/server/controllers/agents/run.js b/api/server/controllers/agents/run.js index 346b9e6df8..6c98a641db 100644 --- a/api/server/controllers/agents/run.js +++ b/api/server/controllers/agents/run.js @@ -45,7 +45,7 @@ async function createRun({ /** @type {'reasoning_content' | 'reasoning'} */ let reasoningKey; - if (llmConfig.configuration?.baseURL.includes(KnownEndpoints.openrouter)) { + if (llmConfig.configuration?.baseURL?.includes(KnownEndpoints.openrouter)) { reasoningKey = 'reasoning'; } if (/o1(?!-(?:mini|preview)).*$/.test(llmConfig.model)) { diff --git a/api/server/services/Endpoints/openAI/llm.js b/api/server/services/Endpoints/openAI/llm.js index 05b08b284b..c12f835f2f 100644 --- a/api/server/services/Endpoints/openAI/llm.js +++ b/api/server/services/Endpoints/openAI/llm.js @@ -58,7 +58,7 @@ function getLLMConfig(apiKey, options = {}) { /** @type {OpenAIClientOptions['configuration']} */ const configOptions = {}; - if (useOpenRouter || reverseProxyUrl.includes(KnownEndpoints.openrouter)) { + if (useOpenRouter || (reverseProxyUrl && reverseProxyUrl.includes(KnownEndpoints.openrouter))) { llmConfig.include_reasoning = true; configOptions.baseURL = reverseProxyUrl; configOptions.defaultHeaders = Object.assign( From a65647a7dea18ee58b0c64f578a1332f177e1162 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Sun, 16 Feb 2025 11:47:01 -0500 Subject: [PATCH 11/23] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20refactor:=20Enhance?= =?UTF-8?q?=20Logging,=20Navigation=20And=20Error=20Handling=20(#5910)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: Ensure Axios Errors are less Verbose if No Response * refactor: Improve error handling in logAxiosError function * fix: Prevent ModelSelect from rendering for Agent Endpoints * refactor: Enhance logging functions with type parameter for better clarity * refactor: Update buildDefaultConvo function to use optional endpoint parameter since we pass a default value for undefined * refactor: Replace console logs with logger warnings and errors in useNavigateToConvo hook, and handle removed endpoint edge case * chore: import order --- api/utils/axios.js | 56 ++++++++----------- .../components/Chat/Input/HeaderOptions.tsx | 12 ++-- .../Conversations/useNavigateToConvo.tsx | 16 +++--- client/src/utils/buildDefaultConvo.ts | 8 +-- client/src/utils/logger.ts | 21 ++++--- 5 files changed, 57 insertions(+), 56 deletions(-) diff --git a/api/utils/axios.js b/api/utils/axios.js index 8b12a5ca99..acd23a184f 100644 --- a/api/utils/axios.js +++ b/api/utils/axios.js @@ -5,40 +5,32 @@ const { logger } = require('~/config'); * * @param {Object} options - The options object. * @param {string} options.message - The custom message to be logged. - * @param {Error} options.error - The Axios error object. + * @param {import('axios').AxiosError} options.error - The Axios error object. */ const logAxiosError = ({ message, error }) => { - const timedOutMessage = 'Cannot read properties of undefined (reading \'status\')'; - if (error.response) { - logger.error( - `${message} The request was made and the server responded with a status code that falls out of the range of 2xx: ${ - error.message ? error.message : '' - }. Error response data:\n`, - { - headers: error.response?.headers, - status: error.response?.status, - data: error.response?.data, - }, - ); - } else if (error.request) { - logger.error( - `${message} The request was made but no response was received: ${ - error.message ? error.message : '' - }. Error Request:\n`, - { - request: error.request, - }, - ); - } else if (error?.message?.includes(timedOutMessage)) { - logger.error( - `${message}\nThe request either timed out or was unsuccessful. Error message:\n`, - error, - ); - } else { - logger.error( - `${message}\nSomething happened in setting up the request. Error message:\n`, - error, - ); + try { + if (error.response?.status) { + const { status, headers, data } = error.response; + logger.error(`${message} The server responded with status ${status}: ${error.message}`, { + status, + headers, + data, + }); + } else if (error.request) { + const { method, url } = error.config || {}; + logger.error( + `${message} No response received for ${method ? method.toUpperCase() : ''} ${url || ''}: ${error.message}`, + { requestInfo: { method, url } }, + ); + } else if (error?.message?.includes('Cannot read properties of undefined (reading \'status\')')) { + logger.error( + `${message} It appears the request timed out or was unsuccessful: ${error.message}`, + ); + } else { + logger.error(`${message} An error occurred while setting up the request: ${error.message}`); + } + } catch (err) { + logger.error(`Error in logAxiosError: ${err.message}`); } }; diff --git a/client/src/components/Chat/Input/HeaderOptions.tsx b/client/src/components/Chat/Input/HeaderOptions.tsx index 0bd3326b53..5313f43b8d 100644 --- a/client/src/components/Chat/Input/HeaderOptions.tsx +++ b/client/src/components/Chat/Input/HeaderOptions.tsx @@ -1,8 +1,13 @@ import { useRecoilState } from 'recoil'; import { Settings2 } from 'lucide-react'; -import { Root, Anchor } from '@radix-ui/react-popover'; import { useState, useEffect, useMemo } from 'react'; -import { tConvoUpdateSchema, EModelEndpoint, isParamEndpoint } from 'librechat-data-provider'; +import { Root, Anchor } from '@radix-ui/react-popover'; +import { + EModelEndpoint, + isParamEndpoint, + isAgentsEndpoint, + tConvoUpdateSchema, +} from 'librechat-data-provider'; import type { TPreset, TInterfaceConfig } from 'librechat-data-provider'; import { EndpointSettings, SaveAsPresetDialog, AlternativeSettings } from '~/components/Endpoints'; import { PluginStoreDialog, TooltipAnchor } from '~/components'; @@ -42,7 +47,6 @@ export default function HeaderOptions({ if (endpoint && noSettings[endpoint]) { setShowPopover(false); } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [endpoint, noSettings]); const saveAsPreset = () => { @@ -67,7 +71,7 @@ export default function HeaderOptions({
- {interfaceConfig?.modelSelect === true && ( + {interfaceConfig?.modelSelect === true && !isAgentsEndpoint(endpoint) && ( { @@ -20,7 +20,7 @@ const useNavigateToConvo = (index = 0) => { invalidateMessages = false, ) => { if (!conversation) { - console.log('Conversation not provided'); + logger.warn('conversation', 'Conversation not provided to `navigateToConvo`'); return; } hasSetConversation.current = true; @@ -34,10 +34,10 @@ const useNavigateToConvo = (index = 0) => { } let convo = { ...conversation }; - if (!convo.endpoint) { - /* undefined endpoint edge case */ + const endpointsConfig = queryClient.getQueryData([QueryKeys.endpoints]); + if (!convo.endpoint || !endpointsConfig?.[convo.endpoint]) { + /* undefined/removed endpoint edge case */ const modelsConfig = queryClient.getQueryData([QueryKeys.models]); - const endpointsConfig = queryClient.getQueryData([QueryKeys.endpoints]); const defaultEndpoint = getDefaultEndpoint({ convoSetup: conversation, endpointsConfig, @@ -51,10 +51,10 @@ const useNavigateToConvo = (index = 0) => { const models = modelsConfig?.[defaultEndpoint ?? ''] ?? []; convo = buildDefaultConvo({ + models, conversation, endpoint: defaultEndpoint, lastConversationSetup: conversation, - models, }); } clearAllConversations(true); @@ -68,7 +68,7 @@ const useNavigateToConvo = (index = 0) => { invalidateMessages?: boolean, ) => { if (!conversation) { - console.log('Conversation not provided'); + logger.warn('conversation', 'Conversation not provided to `navigateToConvo`'); return; } // set conversation to the new conversation @@ -78,7 +78,7 @@ const useNavigateToConvo = (index = 0) => { lastSelectedTools = JSON.parse(localStorage.getItem(LocalStorageKeys.LAST_TOOLS) ?? '') ?? []; } catch (e) { - // console.error(e); + logger.error('conversation', 'Error parsing last selected tools', e); } const hasTools = (conversation.tools?.length ?? 0) > 0; navigateToConvo( diff --git a/client/src/utils/buildDefaultConvo.ts b/client/src/utils/buildDefaultConvo.ts index 429e400cf8..7d60aecfda 100644 --- a/client/src/utils/buildDefaultConvo.ts +++ b/client/src/utils/buildDefaultConvo.ts @@ -8,14 +8,14 @@ import type { TConversation } from 'librechat-data-provider'; import { getLocalStorageItems } from './localStorage'; const buildDefaultConvo = ({ + models, conversation, endpoint = null, - models, lastConversationSetup, }: { - conversation: TConversation; - endpoint: EModelEndpoint | null; models: string[]; + conversation: TConversation; + endpoint?: EModelEndpoint | null; lastConversationSetup: TConversation | null; }): TConversation => { const { lastSelectedModel, lastSelectedTools } = getLocalStorageItems(); @@ -33,7 +33,7 @@ const buildDefaultConvo = ({ const model = lastConversationSetup?.model ?? lastSelectedModel?.[endpoint] ?? ''; const secondaryModel: string | null = endpoint === EModelEndpoint.gptPlugins - ? lastConversationSetup?.agentOptions?.model ?? lastSelectedModel?.secondaryModel ?? null + ? (lastConversationSetup?.agentOptions?.model ?? lastSelectedModel?.secondaryModel ?? null) : null; let possibleModels: string[], secondaryModels: string[]; diff --git a/client/src/utils/logger.ts b/client/src/utils/logger.ts index 413a96f17d..6bc1d21db6 100644 --- a/client/src/utils/logger.ts +++ b/client/src/utils/logger.ts @@ -4,12 +4,17 @@ const loggerFilter = import.meta.env.VITE_LOGGER_FILTER || ''; type LogFunction = (...args: unknown[]) => void; -const createLogFunction = (consoleMethod: LogFunction): LogFunction => { +const createLogFunction = ( + consoleMethod: LogFunction, + type?: 'log' | 'warn' | 'error' | 'info' | 'debug' | 'dir', +): LogFunction => { return (...args: unknown[]) => { if (isDevelopment || isLoggerEnabled) { const tag = typeof args[0] === 'string' ? args[0] : ''; if (shouldLog(tag)) { - if (tag && args.length > 1) { + if (tag && typeof args[1] === 'string' && type === 'error') { + consoleMethod(`[${tag}] ${args[1]}`, ...args.slice(2)); + } else if (tag && args.length > 1) { consoleMethod(`[${tag}]`, ...args.slice(1)); } else { consoleMethod(...args); @@ -20,12 +25,12 @@ const createLogFunction = (consoleMethod: LogFunction): LogFunction => { }; const logger = { - log: createLogFunction(console.log), - warn: createLogFunction(console.warn), - error: createLogFunction(console.error), - info: createLogFunction(console.info), - debug: createLogFunction(console.debug), - dir: createLogFunction(console.dir), + log: createLogFunction(console.log, 'log'), + dir: createLogFunction(console.dir, 'dir'), + warn: createLogFunction(console.warn, 'warn'), + info: createLogFunction(console.info, 'info'), + error: createLogFunction(console.error, 'error'), + debug: createLogFunction(console.debug, 'debug'), }; function shouldLog(tag: string): boolean { From 46ceae1a9361770dd81f25181e23ff1d36bf8bbf Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Mon, 17 Feb 2025 16:39:46 +0100 Subject: [PATCH 12/23] =?UTF-8?q?=E2=9A=96=EF=B8=8F=20docs:=20Update=20LIC?= =?UTF-8?q?ENSE.md=20Year:=202024=20->=202025=20(#5915)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 49a224977b..535850a920 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 LibreChat +Copyright (c) 2025 LibreChat Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From f0f09138bd6db18ea05b44c2cdcff6e54b873032 Mon Sep 17 00:00:00 2001 From: Ruben Talstra Date: Tue, 18 Feb 2025 01:09:36 +0100 Subject: [PATCH 13/23] =?UTF-8?q?=F0=9F=94=92=20feat:=20Two-Factor=20Authe?= =?UTF-8?q?ntication=20with=20Backup=20Codes=20&=20QR=20support=20(#5685)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🔒 feat: add Two-Factor Authentication (2FA) with backup codes & QR support (#5684) * working version for generating TOTP and authenticate. * better looking UI * refactored + better TOTP logic * fixed issue with UI * fixed issue: remove initial setup when closing window before completion. * added: onKeyDown for verify and disable * refactored some code and cleaned it up a bit. * refactored some code and cleaned it up a bit. * refactored some code and cleaned it up a bit. * refactored some code and cleaned it up a bit. * fixed issue after updating to new main branch * updated example * refactored controllers * removed `passport-totp` not used. * update the generateBackupCodes function to generate 10 codes by default: * update the backup codes to an object. * fixed issue with backup codes not working * be able to disable 2FA with backup codes. * removed new env. replaced with JWT_SECRET * ✨ style: improved a11y and style for TwoFactorAuthentication * 🔒 fix: small types checks * ✨ feat: improve 2FA UI components * fix: remove unnecessary console log * add option to disable 2FA with backup codes * - add option to refresh backup codes - (optional) maybe show the user which backup codes have already been used? * removed text to be able to merge the main. * removed eng tx to be able to merge * fix: migrated lang to new format. * feat: rewrote whole 2FA UI + refactored 2FA backend * chore: resolving conflicts * chore: resolving conflicts * fix: missing packages, because of resolving conflicts. * fix: UI issue and improved a11y * fix: 2FA backup code not working * fix: update localization keys for UI consistency * fix: update button label to use localized text * fix: refactor backup codes regeneration and update localization keys * fix: remove outdated translation for shared links management * fix: remove outdated 2FA code prompts from translation.json * fix: add cursor styles for backup codes item based on usage state * fix: resolve conflict issue * fix: resolve conflict issue * fix: resolve conflict issue * fix: missing packages in package-lock.json * fix: add disabled opacity to the verify button in TwoFactorScreen * ⚙ fix: update 2FA logic to rely on backup codes instead of TOTP status * ⚙️ fix: Simplify user retrieval in 2FA logic by removing unnecessary TOTP secret query * ⚙️ test: Add unit tests for TwoFactorAuthController and twoFactorControllers * ⚙️ fix: Ensure backup codes are validated as an array before usage in 2FA components * ⚙️ fix: Update module path mappings in tests to use relative paths * ⚙️ fix: Update moduleNameMapper in jest.config.js to remove the caret from path mapping * ⚙️ refactor: Simplify import paths in TwoFactorAuthController and twoFactorControllers test files * ⚙️ test: Mock twoFactorService methods in twoFactorControllers tests * ⚙️ refactor: Comment out unused imports and mock setups in test files for two-factor authentication * ⚙️ refactor: removed files * refactor: Exclude totpSecret from user data retrieval in AuthController, LoginController, and jwtStrategy * refactor: Consolidate backup code verification to apply DRY and remove default array in user schema * refactor: Enhance two-factor authentication ux/flow with improved error handling and loading state management, prevent redirect to /login --------- Co-authored-by: Marco Beretta <81851188+berry-13@users.noreply.github.com> Co-authored-by: Danny Avila --- api/models/schema/userSchema.js | 13 +- api/server/controllers/AuthController.js | 2 +- api/server/controllers/TwoFactorController.js | 111 +++++++ api/server/controllers/UserController.js | 4 +- .../controllers/auth/LoginController.js | 8 +- .../auth/TwoFactorAuthController.js | 56 ++++ api/server/routes/auth.js | 14 + api/server/services/twoFactorService.js | 205 ++++++++++++ api/server/utils/crypto.js | 23 +- api/strategies/jwtStrategy.js | 2 +- client/package.json | 4 +- client/src/components/Auth/AuthLayout.tsx | 3 +- client/src/components/Auth/LoginForm.tsx | 4 +- .../src/components/Auth/TwoFactorScreen.tsx | 176 +++++++++++ client/src/components/Auth/index.ts | 1 + .../Chat/Input/Files/FileUpload.tsx | 6 +- .../Nav/SettingsTabs/Account/Account.tsx | 23 +- .../Nav/SettingsTabs/Account/Avatar.tsx | 10 +- .../SettingsTabs/Account/BackupCodesItem.tsx | 194 ++++++++++++ .../SettingsTabs/Account/DeleteAccount.tsx | 2 +- .../Account/DisableTwoFactorToggle.tsx | 36 +++ .../Account/TwoFactorAuthentication.tsx | 298 ++++++++++++++++++ .../Account/TwoFactorPhases/BackupPhase.tsx | 60 ++++ .../Account/TwoFactorPhases/DisablePhase.tsx | 88 ++++++ .../Account/TwoFactorPhases/QRPhase.tsx | 66 ++++ .../Account/TwoFactorPhases/SetupPhase.tsx | 42 +++ .../Account/TwoFactorPhases/VerifyPhase.tsx | 58 ++++ .../Account/TwoFactorPhases/index.ts | 5 + .../SettingsTabs/Data/ImportConversations.tsx | 4 +- .../Nav/SettingsTabs/Data/SharedLinks.tsx | 4 +- .../SettingsTabs/General/ArchivedChats.tsx | 2 +- client/src/components/ui/InputOTP.tsx | 68 ++++ client/src/components/ui/Progress.tsx | 22 ++ client/src/components/ui/index.ts | 2 + client/src/data-provider/Auth/mutations.ts | 90 +++++- client/src/hooks/AuthContext.tsx | 9 +- client/src/locales/ar/translation.json | 7 +- client/src/locales/de/translation.json | 7 +- client/src/locales/en/translation.json | 53 +++- client/src/locales/es/translation.json | 7 +- client/src/locales/et/translation.json | 3 +- client/src/locales/fi/translation.json | 7 +- client/src/locales/fr/translation.json | 7 +- client/src/locales/he/translation.json | 6 +- client/src/locales/id/translation.json | 7 +- client/src/locales/it/translation.json | 7 +- client/src/locales/ja/translation.json | 7 +- client/src/locales/ko/translation.json | 7 +- client/src/locales/nl/translation.json | 7 +- client/src/locales/pl/translation.json | 7 +- client/src/locales/ru/translation.json | 7 +- client/src/locales/sv/translation.json | 7 +- client/src/locales/tr/translation.json | 7 +- client/src/locales/vi/translation.json | 6 +- client/src/locales/zh-Hant/translation.json | 7 +- client/src/routes/Layouts/Startup.tsx | 1 + client/src/routes/index.tsx | 5 + package-lock.json | 107 +++++++ packages/data-provider/src/api-endpoints.ts | 8 + packages/data-provider/src/data-service.ts | 30 ++ packages/data-provider/src/keys.ts | 2 + packages/data-provider/src/request.ts | 3 + packages/data-provider/src/types.ts | 61 +++- 63 files changed, 1976 insertions(+), 129 deletions(-) create mode 100644 api/server/controllers/TwoFactorController.js create mode 100644 api/server/controllers/auth/TwoFactorAuthController.js create mode 100644 api/server/services/twoFactorService.js create mode 100644 client/src/components/Auth/TwoFactorScreen.tsx create mode 100644 client/src/components/Nav/SettingsTabs/Account/BackupCodesItem.tsx create mode 100644 client/src/components/Nav/SettingsTabs/Account/DisableTwoFactorToggle.tsx create mode 100644 client/src/components/Nav/SettingsTabs/Account/TwoFactorAuthentication.tsx create mode 100644 client/src/components/Nav/SettingsTabs/Account/TwoFactorPhases/BackupPhase.tsx create mode 100644 client/src/components/Nav/SettingsTabs/Account/TwoFactorPhases/DisablePhase.tsx create mode 100644 client/src/components/Nav/SettingsTabs/Account/TwoFactorPhases/QRPhase.tsx create mode 100644 client/src/components/Nav/SettingsTabs/Account/TwoFactorPhases/SetupPhase.tsx create mode 100644 client/src/components/Nav/SettingsTabs/Account/TwoFactorPhases/VerifyPhase.tsx create mode 100644 client/src/components/Nav/SettingsTabs/Account/TwoFactorPhases/index.ts create mode 100644 client/src/components/ui/InputOTP.tsx create mode 100644 client/src/components/ui/Progress.tsx diff --git a/api/models/schema/userSchema.js b/api/models/schema/userSchema.js index f586553367..bebc7fea1e 100644 --- a/api/models/schema/userSchema.js +++ b/api/models/schema/userSchema.js @@ -39,6 +39,12 @@ const Session = mongoose.Schema({ }, }); +const backupCodeSchema = mongoose.Schema({ + codeHash: { type: String, required: true }, + used: { type: Boolean, default: false }, + usedAt: { type: Date, default: null }, +}); + /** @type {MongooseSchema} */ const userSchema = mongoose.Schema( { @@ -119,7 +125,12 @@ const userSchema = mongoose.Schema( }, plugins: { type: Array, - default: [], + }, + totpSecret: { + type: String, + }, + backupCodes: { + type: [backupCodeSchema], }, refreshToken: { type: [Session], diff --git a/api/server/controllers/AuthController.js b/api/server/controllers/AuthController.js index 71551ea867..7cdfaa9aaf 100644 --- a/api/server/controllers/AuthController.js +++ b/api/server/controllers/AuthController.js @@ -61,7 +61,7 @@ const refreshController = async (req, res) => { try { const payload = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET); - const user = await getUserById(payload.id, '-password -__v'); + const user = await getUserById(payload.id, '-password -__v -totpSecret'); if (!user) { return res.status(401).redirect('/login'); } diff --git a/api/server/controllers/TwoFactorController.js b/api/server/controllers/TwoFactorController.js new file mode 100644 index 0000000000..3e8d38ac12 --- /dev/null +++ b/api/server/controllers/TwoFactorController.js @@ -0,0 +1,111 @@ +const { + verifyTOTP, + verifyBackupCode, + generateTOTPSecret, + generateBackupCodes, +} = require('~/server/services/twoFactorService'); +const { updateUser, getUserById } = require('~/models'); +const { logger } = require('~/config'); + +const enable2FAController = async (req, res) => { + const safeAppTitle = (process.env.APP_TITLE || 'LibreChat').replace(/\s+/g, ''); + + try { + const userId = req.user.id; + const secret = generateTOTPSecret(); + const { plainCodes, codeObjects } = await generateBackupCodes(); + + const user = await updateUser(userId, { totpSecret: secret, backupCodes: codeObjects }); + + const otpauthUrl = `otpauth://totp/${safeAppTitle}:${user.email}?secret=${secret}&issuer=${safeAppTitle}`; + + res.status(200).json({ + otpauthUrl, + backupCodes: plainCodes, + }); + } catch (err) { + logger.error('[enable2FAController]', err); + res.status(500).json({ message: err.message }); + } +}; + +const verify2FAController = async (req, res) => { + try { + const userId = req.user.id; + const { token, backupCode } = req.body; + const user = await getUserById(userId); + if (!user || !user.totpSecret) { + return res.status(400).json({ message: '2FA not initiated' }); + } + + let verified = false; + if (token && (await verifyTOTP(user.totpSecret, token))) { + return res.status(200).json(); + } else if (backupCode) { + verified = await verifyBackupCode({ user, backupCode }); + } + if (verified) { + return res.status(200).json(); + } + + return res.status(400).json({ message: 'Invalid token.' }); + } catch (err) { + logger.error('[verify2FAController]', err); + res.status(500).json({ message: err.message }); + } +}; + +const confirm2FAController = async (req, res) => { + try { + const userId = req.user.id; + const { token } = req.body; + const user = await getUserById(userId); + + if (!user || !user.totpSecret) { + return res.status(400).json({ message: '2FA not initiated' }); + } + + if (await verifyTOTP(user.totpSecret, token)) { + return res.status(200).json(); + } + + return res.status(400).json({ message: 'Invalid token.' }); + } catch (err) { + logger.error('[confirm2FAController]', err); + res.status(500).json({ message: err.message }); + } +}; + +const disable2FAController = async (req, res) => { + try { + const userId = req.user.id; + await updateUser(userId, { totpSecret: null, backupCodes: [] }); + res.status(200).json(); + } catch (err) { + logger.error('[disable2FAController]', err); + res.status(500).json({ message: err.message }); + } +}; + +const regenerateBackupCodesController = async (req, res) => { + try { + const userId = req.user.id; + const { plainCodes, codeObjects } = await generateBackupCodes(); + await updateUser(userId, { backupCodes: codeObjects }); + res.status(200).json({ + backupCodes: plainCodes, + backupCodesHash: codeObjects, + }); + } catch (err) { + logger.error('[regenerateBackupCodesController]', err); + res.status(500).json({ message: err.message }); + } +}; + +module.exports = { + enable2FAController, + verify2FAController, + confirm2FAController, + disable2FAController, + regenerateBackupCodesController, +}; diff --git a/api/server/controllers/UserController.js b/api/server/controllers/UserController.js index 17089e8fdc..a331b8daae 100644 --- a/api/server/controllers/UserController.js +++ b/api/server/controllers/UserController.js @@ -19,7 +19,9 @@ const { Transaction } = require('~/models/Transaction'); const { logger } = require('~/config'); const getUserController = async (req, res) => { - res.status(200).send(req.user); + const userData = req.user.toObject != null ? req.user.toObject() : { ...req.user }; + delete userData.totpSecret; + res.status(200).send(userData); }; const getTermsStatusController = async (req, res) => { diff --git a/api/server/controllers/auth/LoginController.js b/api/server/controllers/auth/LoginController.js index 1b543e9baf..8ab9a99ddb 100644 --- a/api/server/controllers/auth/LoginController.js +++ b/api/server/controllers/auth/LoginController.js @@ -1,3 +1,4 @@ +const { generate2FATempToken } = require('~/server/services/twoFactorService'); const { setAuthTokens } = require('~/server/services/AuthService'); const { logger } = require('~/config'); @@ -7,7 +8,12 @@ const loginController = async (req, res) => { return res.status(400).json({ message: 'Invalid credentials' }); } - const { password: _, __v, ...user } = req.user; + if (req.user.backupCodes != null && req.user.backupCodes.length > 0) { + const tempToken = generate2FATempToken(req.user._id); + return res.status(200).json({ twoFAPending: true, tempToken }); + } + + const { password: _p, totpSecret: _t, __v, ...user } = req.user; user.id = user._id.toString(); const token = await setAuthTokens(req.user._id, res); diff --git a/api/server/controllers/auth/TwoFactorAuthController.js b/api/server/controllers/auth/TwoFactorAuthController.js new file mode 100644 index 0000000000..37a8045829 --- /dev/null +++ b/api/server/controllers/auth/TwoFactorAuthController.js @@ -0,0 +1,56 @@ +const jwt = require('jsonwebtoken'); +const { verifyTOTP, verifyBackupCode } = require('~/server/services/twoFactorService'); +const { setAuthTokens } = require('~/server/services/AuthService'); +const { getUserById } = require('~/models/userMethods'); +const { logger } = require('~/config'); + +const verify2FA = async (req, res) => { + try { + const { tempToken, token, backupCode } = req.body; + if (!tempToken) { + return res.status(400).json({ message: 'Missing temporary token' }); + } + + let payload; + try { + payload = jwt.verify(tempToken, process.env.JWT_SECRET); + } catch (err) { + return res.status(401).json({ message: 'Invalid or expired temporary token' }); + } + + const user = await getUserById(payload.userId); + // Ensure that the user exists and has backup codes (i.e. 2FA enabled) + if (!user || !(user.backupCodes && user.backupCodes.length > 0)) { + return res.status(400).json({ message: '2FA is not enabled for this user' }); + } + + let verified = false; + + if (token && (await verifyTOTP(user.totpSecret, token))) { + verified = true; + } else if (backupCode) { + verified = await verifyBackupCode({ user, backupCode }); + } + + if (!verified) { + return res.status(401).json({ message: 'Invalid 2FA code or backup code' }); + } + + // Prepare user data for response. + // If the user is a plain object (from lean queries), we create a shallow copy. + const userData = user.toObject ? user.toObject() : { ...user }; + // Remove sensitive fields + delete userData.password; + delete userData.__v; + delete userData.totpSecret; + userData.id = user._id.toString(); + + const authToken = await setAuthTokens(user._id, res); + return res.status(200).json({ token: authToken, user: userData }); + } catch (err) { + logger.error('[verify2FA]', err); + return res.status(500).json({ message: 'Something went wrong' }); + } +}; + +module.exports = { verify2FA }; diff --git a/api/server/routes/auth.js b/api/server/routes/auth.js index 3e86ffd868..03046d903f 100644 --- a/api/server/routes/auth.js +++ b/api/server/routes/auth.js @@ -7,6 +7,13 @@ const { } = require('~/server/controllers/AuthController'); const { loginController } = require('~/server/controllers/auth/LoginController'); const { logoutController } = require('~/server/controllers/auth/LogoutController'); +const { verify2FA } = require('~/server/controllers/auth/TwoFactorAuthController'); +const { + enable2FAController, + verify2FAController, + disable2FAController, + regenerateBackupCodesController, confirm2FAController, +} = require('~/server/controllers/TwoFactorController'); const { checkBan, loginLimiter, @@ -50,4 +57,11 @@ router.post( ); router.post('/resetPassword', checkBan, validatePasswordReset, resetPasswordController); +router.get('/2fa/enable', requireJwtAuth, enable2FAController); +router.post('/2fa/verify', requireJwtAuth, verify2FAController); +router.post('/2fa/verify-temp', checkBan, verify2FA); +router.post('/2fa/confirm', requireJwtAuth, confirm2FAController); +router.post('/2fa/disable', requireJwtAuth, disable2FAController); +router.post('/2fa/backup/regenerate', requireJwtAuth, regenerateBackupCodesController); + module.exports = router; diff --git a/api/server/services/twoFactorService.js b/api/server/services/twoFactorService.js new file mode 100644 index 0000000000..ac7247409c --- /dev/null +++ b/api/server/services/twoFactorService.js @@ -0,0 +1,205 @@ +const { sign } = require('jsonwebtoken'); +const { webcrypto } = require('node:crypto'); +const { hashBackupCode } = require('~/server/utils/crypto'); +const { updateUser } = require('~/models/userMethods'); + +const BASE32_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; + +/** + * Encodes a Buffer into a Base32 string using RFC 4648 alphabet. + * @param {Buffer} buffer - The buffer to encode. + * @returns {string} - The Base32 encoded string. + */ +const encodeBase32 = (buffer) => { + let bits = 0; + let value = 0; + let output = ''; + for (const byte of buffer) { + value = (value << 8) | byte; + bits += 8; + while (bits >= 5) { + output += BASE32_ALPHABET[(value >>> (bits - 5)) & 31]; + bits -= 5; + } + } + if (bits > 0) { + output += BASE32_ALPHABET[(value << (5 - bits)) & 31]; + } + return output; +}; + +/** + * Decodes a Base32-encoded string back into a Buffer. + * @param {string} base32Str + * @returns {Buffer} + */ +const decodeBase32 = (base32Str) => { + const cleaned = base32Str.replace(/=+$/, '').toUpperCase(); + let bits = 0; + let value = 0; + const output = []; + for (const char of cleaned) { + const idx = BASE32_ALPHABET.indexOf(char); + if (idx === -1) { + continue; + } + value = (value << 5) | idx; + bits += 5; + if (bits >= 8) { + output.push((value >>> (bits - 8)) & 0xff); + bits -= 8; + } + } + return Buffer.from(output); +}; + +/** + * Generate a temporary token for 2FA verification. + * This token is signed with JWT_SECRET and expires in 5 minutes. + */ +const generate2FATempToken = (userId) => + sign({ userId, twoFAPending: true }, process.env.JWT_SECRET, { expiresIn: '5m' }); + +/** + * Generate a TOTP secret. + * Generates 10 random bytes using WebCrypto and encodes them into a Base32 string. + */ +const generateTOTPSecret = () => { + const randomArray = new Uint8Array(10); + webcrypto.getRandomValues(randomArray); + return encodeBase32(Buffer.from(randomArray)); +}; + +/** + * Generate a TOTP code based on the secret and current time. + * Uses a 30-second time step and generates a 6-digit code. + * + * @param {string} secret - Base32-encoded secret + * @param {number} [forTime=Date.now()] - Time in milliseconds + * @returns {Promise} - The 6-digit TOTP code. + */ +const generateTOTP = async (secret, forTime = Date.now()) => { + const timeStep = 30; // seconds + const counter = Math.floor(forTime / 1000 / timeStep); + const counterBuffer = new ArrayBuffer(8); + const counterView = new DataView(counterBuffer); + // Write counter into the last 4 bytes (big-endian) + counterView.setUint32(4, counter, false); + + // Decode the secret into an ArrayBuffer + const keyBuffer = decodeBase32(secret); + const keyArrayBuffer = keyBuffer.buffer.slice( + keyBuffer.byteOffset, + keyBuffer.byteOffset + keyBuffer.byteLength, + ); + + // Import the key for HMAC-SHA1 signing + const cryptoKey = await webcrypto.subtle.importKey( + 'raw', + keyArrayBuffer, + { name: 'HMAC', hash: 'SHA-1' }, + false, + ['sign'], + ); + + // Generate HMAC signature + const signatureBuffer = await webcrypto.subtle.sign('HMAC', cryptoKey, counterBuffer); + const hmac = new Uint8Array(signatureBuffer); + + const offset = hmac[hmac.length - 1] & 0xf; + const slice = hmac.slice(offset, offset + 4); + const view = new DataView(slice.buffer, slice.byteOffset, slice.byteLength); + const binaryCode = view.getUint32(0, false) & 0x7fffffff; + const code = (binaryCode % 1000000).toString().padStart(6, '0'); + return code; +}; + +/** + * Verify a provided TOTP token against the secret. + * Allows for a ±1 time-step window. + * + * @param {string} secret + * @param {string} token + * @returns {Promise} + */ +const verifyTOTP = async (secret, token) => { + const timeStepMS = 30 * 1000; + const currentTime = Date.now(); + for (let offset = -1; offset <= 1; offset++) { + const expected = await generateTOTP(secret, currentTime + offset * timeStepMS); + if (expected === token) { + return true; + } + } + return false; +}; + +/** + * Generate backup codes. + * Generates `count` backup code objects and returns an object with both plain codes + * (for one-time download) and their objects (for secure storage). Uses WebCrypto for randomness and hashing. + * + * @param {number} count - Number of backup codes to generate (default: 10). + * @returns {Promise} - Contains `plainCodes` (array of strings) and `codeObjects` (array of objects). + */ +const generateBackupCodes = async (count = 10) => { + const plainCodes = []; + const codeObjects = []; + const encoder = new TextEncoder(); + for (let i = 0; i < count; i++) { + const randomArray = new Uint8Array(4); + webcrypto.getRandomValues(randomArray); + const code = Array.from(randomArray) + .map((b) => b.toString(16).padStart(2, '0')) + .join(''); // 8-character hex code + plainCodes.push(code); + + // Compute SHA-256 hash of the code using WebCrypto + const codeBuffer = encoder.encode(code); + const hashBuffer = await webcrypto.subtle.digest('SHA-256', codeBuffer); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + const codeHash = hashArray.map((b) => b.toString(16).padStart(2, '0')).join(''); + codeObjects.push({ codeHash, used: false, usedAt: null }); + } + return { plainCodes, codeObjects }; +}; + +/** + * Verifies a backup code and updates the user's backup codes if valid + * @param {Object} params + * @param {TUser | undefined} [params.user] - The user object + * @param {string | undefined} [params.backupCode] - The backup code to verify + * @returns {Promise} - Whether the backup code was valid + */ +const verifyBackupCode = async ({ user, backupCode }) => { + if (!backupCode || !user || !Array.isArray(user.backupCodes)) { + return false; + } + + const hashedInput = await hashBackupCode(backupCode.trim()); + const matchingCode = user.backupCodes.find( + (codeObj) => codeObj.codeHash === hashedInput && !codeObj.used, + ); + + if (matchingCode) { + const updatedBackupCodes = user.backupCodes.map((codeObj) => + codeObj.codeHash === hashedInput && !codeObj.used + ? { ...codeObj, used: true, usedAt: new Date() } + : codeObj, + ); + + await updateUser(user._id, { backupCodes: updatedBackupCodes }); + return true; + } + + return false; +}; + +module.exports = { + verifyTOTP, + generateTOTP, + verifyBackupCode, + generateTOTPSecret, + generateBackupCodes, + generate2FATempToken, +}; diff --git a/api/server/utils/crypto.js b/api/server/utils/crypto.js index ea71df51ad..407fad62ac 100644 --- a/api/server/utils/crypto.js +++ b/api/server/utils/crypto.js @@ -112,4 +112,25 @@ async function getRandomValues(length) { return Buffer.from(randomValues).toString('hex'); } -module.exports = { encrypt, decrypt, encryptV2, decryptV2, hashToken, getRandomValues }; +/** + * Computes SHA-256 hash for the given input using WebCrypto + * @param {string} input + * @returns {Promise} - Hex hash string + */ +const hashBackupCode = async (input) => { + const encoder = new TextEncoder(); + const data = encoder.encode(input); + const hashBuffer = await webcrypto.subtle.digest('SHA-256', data); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + return hashArray.map((b) => b.toString(16).padStart(2, '0')).join(''); +}; + +module.exports = { + encrypt, + decrypt, + encryptV2, + decryptV2, + hashToken, + hashBackupCode, + getRandomValues, +}; diff --git a/api/strategies/jwtStrategy.js b/api/strategies/jwtStrategy.js index e65b284950..ac19e92ac3 100644 --- a/api/strategies/jwtStrategy.js +++ b/api/strategies/jwtStrategy.js @@ -12,7 +12,7 @@ const jwtLogin = async () => }, async (payload, done) => { try { - const user = await getUserById(payload?.id, '-password -__v'); + const user = await getUserById(payload?.id, '-password -__v -totpSecret'); if (user) { user.id = user._id.toString(); if (!user.role) { diff --git a/client/package.json b/client/package.json index 22e9b1dd03..917333ce25 100644 --- a/client/package.json +++ b/client/package.json @@ -44,6 +44,7 @@ "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.0.0", "@radix-ui/react-popover": "^1.0.7", + "@radix-ui/react-progress": "^1.1.2", "@radix-ui/react-radio-group": "^1.1.3", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-separator": "^1.0.3", @@ -66,6 +67,7 @@ "i18next": "^24.2.2", "i18next-browser-languagedetector": "^8.0.3", "js-cookie": "^3.0.5", + "input-otp": "^1.4.2", "librechat-data-provider": "*", "lodash": "^4.17.21", "lucide-react": "^0.394.0", @@ -142,4 +144,4 @@ "vite-plugin-node-polyfills": "^0.17.0", "vite-plugin-pwa": "^0.21.1" } -} +} \ No newline at end of file diff --git a/client/src/components/Auth/AuthLayout.tsx b/client/src/components/Auth/AuthLayout.tsx index a7e890517a..d90f0d3dfe 100644 --- a/client/src/components/Auth/AuthLayout.tsx +++ b/client/src/components/Auth/AuthLayout.tsx @@ -85,7 +85,8 @@ function AuthLayout({ )} {children} - {(pathname.includes('login') || pathname.includes('register')) && ( + {!pathname.includes('2fa') && + (pathname.includes('login') || pathname.includes('register')) && ( )} diff --git a/client/src/components/Auth/LoginForm.tsx b/client/src/components/Auth/LoginForm.tsx index 9f5bb46039..2cd62d08b9 100644 --- a/client/src/components/Auth/LoginForm.tsx +++ b/client/src/components/Auth/LoginForm.tsx @@ -166,9 +166,7 @@ const LoginForm: React.FC = ({ onSubmit, startupConfig, error, type="submit" className=" w-full rounded-2xl bg-green-600 px-4 py-3 text-sm font-medium text-white - transition-colors hover:bg-green-700 focus:outline-none focus:ring-2 - focus:ring-green-500 focus:ring-offset-2 disabled:opacity-50 - disabled:hover:bg-green-600 dark:bg-green-600 dark:hover:bg-green-700 + transition-colors hover:bg-green-700 dark:bg-green-600 dark:hover:bg-green-700 " > {localize('com_auth_continue')} diff --git a/client/src/components/Auth/TwoFactorScreen.tsx b/client/src/components/Auth/TwoFactorScreen.tsx new file mode 100644 index 0000000000..04f89d7cea --- /dev/null +++ b/client/src/components/Auth/TwoFactorScreen.tsx @@ -0,0 +1,176 @@ +import React, { useState, useCallback } from 'react'; +import { useSearchParams } from 'react-router-dom'; +import { useForm, Controller } from 'react-hook-form'; +import { REGEXP_ONLY_DIGITS, REGEXP_ONLY_DIGITS_AND_CHARS } from 'input-otp'; +import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, Label } from '~/components'; +import { useVerifyTwoFactorTempMutation } from '~/data-provider'; +import { useToastContext } from '~/Providers'; +import { useLocalize } from '~/hooks'; + +interface VerifyPayload { + tempToken: string; + token?: string; + backupCode?: string; +} + +type TwoFactorFormInputs = { + token?: string; + backupCode?: string; +}; + +const TwoFactorScreen: React.FC = React.memo(() => { + const [searchParams] = useSearchParams(); + const tempTokenRaw = searchParams.get('tempToken'); + const tempToken = tempTokenRaw !== null && tempTokenRaw !== '' ? tempTokenRaw : ''; + + const { + control, + handleSubmit, + formState: { errors }, + } = useForm(); + const localize = useLocalize(); + const { showToast } = useToastContext(); + const [useBackup, setUseBackup] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const { mutate: verifyTempMutate } = useVerifyTwoFactorTempMutation({ + onSuccess: (result) => { + if (result.token != null && result.token !== '') { + window.location.href = '/'; + } + }, + onMutate: () => { + setIsLoading(true); + }, + onError: (error: unknown) => { + setIsLoading(false); + const err = error as { response?: { data?: { message?: unknown } } }; + const errorMsg = + typeof err.response?.data?.message === 'string' + ? err.response.data.message + : 'Error verifying 2FA'; + showToast({ message: errorMsg, status: 'error' }); + }, + }); + + const onSubmit = useCallback( + (data: TwoFactorFormInputs) => { + const payload: VerifyPayload = { tempToken }; + if (useBackup && data.backupCode != null && data.backupCode !== '') { + payload.backupCode = data.backupCode; + } else if (data.token != null && data.token !== '') { + payload.token = data.token; + } + verifyTempMutate(payload); + }, + [tempToken, useBackup, verifyTempMutate], + ); + + const toggleBackupOn = useCallback(() => { + setUseBackup(true); + }, []); + + const toggleBackupOff = useCallback(() => { + setUseBackup(false); + }, []); + + return ( +
+
+ + {!useBackup && ( +
+ ( + + + + + + + + + + + + + + )} + /> + {errors.token && {errors.token.message}} +
+ )} + {useBackup && ( +
+ ( + + + + + + + + + + + + + )} + /> + {errors.backupCode && ( + {errors.backupCode.message} + )} +
+ )} +
+ +
+
+ {!useBackup ? ( + + ) : ( + + )} +
+
+
+ ); +}); + +export default TwoFactorScreen; diff --git a/client/src/components/Auth/index.ts b/client/src/components/Auth/index.ts index cd1ac1adce..afde148015 100644 --- a/client/src/components/Auth/index.ts +++ b/client/src/components/Auth/index.ts @@ -4,3 +4,4 @@ export { default as ResetPassword } from './ResetPassword'; export { default as VerifyEmail } from './VerifyEmail'; export { default as ApiErrorWatcher } from './ApiErrorWatcher'; export { default as RequestPasswordReset } from './RequestPasswordReset'; +export { default as TwoFactorScreen } from './TwoFactorScreen'; diff --git a/client/src/components/Chat/Input/Files/FileUpload.tsx b/client/src/components/Chat/Input/Files/FileUpload.tsx index 506f50c01d..723fa32e86 100644 --- a/client/src/components/Chat/Input/Files/FileUpload.tsx +++ b/client/src/components/Chat/Input/Files/FileUpload.tsx @@ -55,7 +55,7 @@ const FileUpload: React.FC = ({ let statusText: string; if (!status) { - statusText = text ?? localize('com_endpoint_import'); + statusText = text ?? localize('com_ui_import'); } else if (status === 'success') { statusText = successText ?? localize('com_ui_upload_success'); } else { @@ -72,12 +72,12 @@ const FileUpload: React.FC = ({ )} > - {statusText} + {statusText} diff --git a/client/src/components/Nav/SettingsTabs/Account/Account.tsx b/client/src/components/Nav/SettingsTabs/Account/Account.tsx index 374a6b996e..68168f7f72 100644 --- a/client/src/components/Nav/SettingsTabs/Account/Account.tsx +++ b/client/src/components/Nav/SettingsTabs/Account/Account.tsx @@ -2,19 +2,36 @@ import React from 'react'; import DisplayUsernameMessages from './DisplayUsernameMessages'; import DeleteAccount from './DeleteAccount'; import Avatar from './Avatar'; +import EnableTwoFactorItem from './TwoFactorAuthentication'; +import BackupCodesItem from './BackupCodesItem'; +import { useAuthContext } from '~/hooks'; function Account() { + const user = useAuthContext(); + return (
+
+ +
+ {user?.user?.provider === 'local' && ( + <> +
+ +
+ {Array.isArray(user.user?.backupCodes) && user.user?.backupCodes.length > 0 && ( +
+ +
+ )} + + )}
-
- -
); } diff --git a/client/src/components/Nav/SettingsTabs/Account/Avatar.tsx b/client/src/components/Nav/SettingsTabs/Account/Avatar.tsx index 48f585bdbb..5ecdb5a990 100644 --- a/client/src/components/Nav/SettingsTabs/Account/Avatar.tsx +++ b/client/src/components/Nav/SettingsTabs/Account/Avatar.tsx @@ -47,7 +47,7 @@ function Avatar() { const { mutate: uploadAvatar, isLoading: isUploading } = useUploadAvatarMutation({ onSuccess: (data) => { showToast({ message: localize('com_ui_upload_success') }); - setUser((prev) => ({ ...prev, avatar: data.url } as TUser)); + setUser((prev) => ({ ...prev, avatar: data.url }) as TUser); openButtonRef.current?.click(); }, onError: (error) => { @@ -133,9 +133,11 @@ function Avatar() { >
{localize('com_nav_profile_picture')} - - - {localize('com_nav_change_picture')} + +
diff --git a/client/src/components/Nav/SettingsTabs/Account/BackupCodesItem.tsx b/client/src/components/Nav/SettingsTabs/Account/BackupCodesItem.tsx new file mode 100644 index 0000000000..a034e2773a --- /dev/null +++ b/client/src/components/Nav/SettingsTabs/Account/BackupCodesItem.tsx @@ -0,0 +1,194 @@ +import React, { useState } from 'react'; +import { RefreshCcw, ShieldX } from 'lucide-react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { TBackupCode, TRegenerateBackupCodesResponse, type TUser } from 'librechat-data-provider'; +import { + OGDialog, + OGDialogContent, + OGDialogTitle, + OGDialogTrigger, + Button, + Label, + Spinner, + TooltipAnchor, +} from '~/components'; +import { useRegenerateBackupCodesMutation } from '~/data-provider'; +import { useAuthContext, useLocalize } from '~/hooks'; +import { useToastContext } from '~/Providers'; +import { useSetRecoilState } from 'recoil'; +import store from '~/store'; + +const BackupCodesItem: React.FC = () => { + const localize = useLocalize(); + const { user } = useAuthContext(); + const { showToast } = useToastContext(); + const setUser = useSetRecoilState(store.user); + const [isDialogOpen, setDialogOpen] = useState(false); + + const { mutate: regenerateBackupCodes, isLoading } = useRegenerateBackupCodesMutation(); + + const fetchBackupCodes = (auto: boolean = false) => { + regenerateBackupCodes(undefined, { + onSuccess: (data: TRegenerateBackupCodesResponse) => { + const newBackupCodes: TBackupCode[] = data.backupCodesHash.map((codeHash) => ({ + codeHash, + used: false, + usedAt: null, + })); + + setUser((prev) => ({ ...prev, backupCodes: newBackupCodes }) as TUser); + showToast({ + message: localize('com_ui_backup_codes_regenerated'), + status: 'success', + }); + + // Trigger file download only when user explicitly clicks the button. + if (!auto && newBackupCodes.length) { + const codesString = data.backupCodes.join('\n'); + const blob = new Blob([codesString], { type: 'text/plain;charset=utf-8' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'backup-codes.txt'; + a.click(); + URL.revokeObjectURL(url); + } + }, + onError: () => + showToast({ + message: localize('com_ui_backup_codes_regenerate_error'), + status: 'error', + }), + }); + }; + + const handleRegenerate = () => { + fetchBackupCodes(false); + }; + + return ( + +
+
+ +
+ + + +
+ + + + {localize('com_ui_backup_codes')} + + + + + {Array.isArray(user?.backupCodes) && user?.backupCodes.length > 0 ? ( + <> +
+ {user?.backupCodes.map((code, index) => { + const isUsed = code.used; + const description = `Backup code number ${index + 1}, ${ + isUsed + ? `used on ${code.usedAt ? new Date(code.usedAt).toLocaleDateString() : 'an unknown date'}` + : 'not used yet' + }`; + + return ( + { + const announcement = new CustomEvent('announce', { + detail: { message: description }, + }); + document.dispatchEvent(announcement); + }} + className={`flex flex-col rounded-xl border p-4 backdrop-blur-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary ${ + isUsed + ? 'border-red-200 bg-red-50/80 dark:border-red-800 dark:bg-red-900/20' + : 'border-green-200 bg-green-50/80 dark:border-green-800 dark:bg-green-900/20' + } `} + > + + + ); + })} +
+
+ +
+ + ) : ( +
+ +

{localize('com_ui_no_backup_codes')}

+ +
+ )} +
+
+
+
+ ); +}; + +export default React.memo(BackupCodesItem); diff --git a/client/src/components/Nav/SettingsTabs/Account/DeleteAccount.tsx b/client/src/components/Nav/SettingsTabs/Account/DeleteAccount.tsx index 1c1e207d58..b00e7498bc 100644 --- a/client/src/components/Nav/SettingsTabs/Account/DeleteAccount.tsx +++ b/client/src/components/Nav/SettingsTabs/Account/DeleteAccount.tsx @@ -57,7 +57,7 @@ const DeleteAccount = ({ disabled = false }: { title?: string; disabled?: boolea - + {localize('com_nav_delete_account_confirm')} diff --git a/client/src/components/Nav/SettingsTabs/Account/DisableTwoFactorToggle.tsx b/client/src/components/Nav/SettingsTabs/Account/DisableTwoFactorToggle.tsx new file mode 100644 index 0000000000..5dfad770d3 --- /dev/null +++ b/client/src/components/Nav/SettingsTabs/Account/DisableTwoFactorToggle.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { motion } from 'framer-motion'; +import { LockIcon, UnlockIcon } from 'lucide-react'; +import { Label, Button } from '~/components'; +import { useLocalize } from '~/hooks'; + +interface DisableTwoFactorToggleProps { + enabled: boolean; + onChange: () => void; + disabled?: boolean; +} + +export const DisableTwoFactorToggle: React.FC = ({ + enabled, + onChange, + disabled, +}) => { + const localize = useLocalize(); + + return ( +
+
+ +
+
+ +
+
+ ); +}; diff --git a/client/src/components/Nav/SettingsTabs/Account/TwoFactorAuthentication.tsx b/client/src/components/Nav/SettingsTabs/Account/TwoFactorAuthentication.tsx new file mode 100644 index 0000000000..bd46e80249 --- /dev/null +++ b/client/src/components/Nav/SettingsTabs/Account/TwoFactorAuthentication.tsx @@ -0,0 +1,298 @@ +import React, { useCallback, useState } from 'react'; +import { useSetRecoilState } from 'recoil'; +import { SmartphoneIcon } from 'lucide-react'; +import { motion, AnimatePresence } from 'framer-motion'; +import type { TUser, TVerify2FARequest } from 'librechat-data-provider'; +import { OGDialog, OGDialogContent, OGDialogHeader, OGDialogTitle, Progress } from '~/components'; +import { SetupPhase, QRPhase, VerifyPhase, BackupPhase, DisablePhase } from './TwoFactorPhases'; +import { DisableTwoFactorToggle } from './DisableTwoFactorToggle'; +import { useAuthContext, useLocalize } from '~/hooks'; +import { useToastContext } from '~/Providers'; +import store from '~/store'; +import { + useConfirmTwoFactorMutation, + useDisableTwoFactorMutation, + useEnableTwoFactorMutation, + useVerifyTwoFactorMutation, +} from '~/data-provider'; + +export type Phase = 'setup' | 'qr' | 'verify' | 'backup' | 'disable'; + +const phaseVariants = { + initial: { opacity: 0, scale: 0.95 }, + animate: { opacity: 1, scale: 1, transition: { duration: 0.3, ease: 'easeOut' } }, + exit: { opacity: 0, scale: 0.95, transition: { duration: 0.3, ease: 'easeIn' } }, +}; + +const TwoFactorAuthentication: React.FC = () => { + const localize = useLocalize(); + const { user } = useAuthContext(); + const setUser = useSetRecoilState(store.user); + const { showToast } = useToastContext(); + + const [secret, setSecret] = useState(''); + const [otpauthUrl, setOtpauthUrl] = useState(''); + const [downloaded, setDownloaded] = useState(false); + const [disableToken, setDisableToken] = useState(''); + const [backupCodes, setBackupCodes] = useState([]); + const [isDialogOpen, setDialogOpen] = useState(false); + const [verificationToken, setVerificationToken] = useState(''); + const [phase, setPhase] = useState(Array.isArray(user?.backupCodes) && user?.backupCodes.length > 0 ? 'disable' : 'setup'); + + const { mutate: confirm2FAMutate } = useConfirmTwoFactorMutation(); + const { mutate: enable2FAMutate, isLoading: isGenerating } = useEnableTwoFactorMutation(); + const { mutate: verify2FAMutate, isLoading: isVerifying } = useVerifyTwoFactorMutation(); + const { mutate: disable2FAMutate, isLoading: isDisabling } = useDisableTwoFactorMutation(); + + const steps = ['Setup', 'Scan QR', 'Verify', 'Backup']; + const phasesLabel: Record = { + setup: 'Setup', + qr: 'Scan QR', + verify: 'Verify', + backup: 'Backup', + disable: '', + }; + + const currentStep = steps.indexOf(phasesLabel[phase]); + + const resetState = useCallback(() => { + if (Array.isArray(user?.backupCodes) && user?.backupCodes.length > 0 && otpauthUrl) { + disable2FAMutate(undefined, { + onError: () => + showToast({ message: localize('com_ui_2fa_disable_error'), status: 'error' }), + }); + } + + setOtpauthUrl(''); + setSecret(''); + setBackupCodes([]); + setVerificationToken(''); + setDisableToken(''); + setPhase(Array.isArray(user?.backupCodes) && user?.backupCodes.length > 0 ? 'disable' : 'setup'); + setDownloaded(false); + }, [user, otpauthUrl, disable2FAMutate, localize, showToast]); + + const handleGenerateQRCode = useCallback(() => { + enable2FAMutate(undefined, { + onSuccess: ({ otpauthUrl, backupCodes }) => { + setOtpauthUrl(otpauthUrl); + setSecret(otpauthUrl.split('secret=')[1].split('&')[0]); + setBackupCodes(backupCodes); + setPhase('qr'); + }, + onError: () => showToast({ message: localize('com_ui_2fa_generate_error'), status: 'error' }), + }); + }, [enable2FAMutate, localize, showToast]); + + const handleVerify = useCallback(() => { + if (!verificationToken) { + return; + } + + verify2FAMutate( + { token: verificationToken }, + { + onSuccess: () => { + showToast({ message: localize('com_ui_2fa_verified') }); + confirm2FAMutate( + { token: verificationToken }, + { + onSuccess: () => setPhase('backup'), + onError: () => + showToast({ message: localize('com_ui_2fa_invalid'), status: 'error' }), + }, + ); + }, + onError: () => showToast({ message: localize('com_ui_2fa_invalid'), status: 'error' }), + }, + ); + }, [verificationToken, verify2FAMutate, confirm2FAMutate, localize, showToast]); + + const handleDownload = useCallback(() => { + if (!backupCodes.length) { + return; + } + const blob = new Blob([backupCodes.join('\n')], { type: 'text/plain;charset=utf-8' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'backup-codes.txt'; + a.click(); + URL.revokeObjectURL(url); + setDownloaded(true); + }, [backupCodes]); + + const handleConfirm = useCallback(() => { + setDialogOpen(false); + setPhase('disable'); + showToast({ message: localize('com_ui_2fa_enabled') }); + setUser( + (prev) => + ({ + ...prev, + backupCodes: backupCodes.map((code) => ({ + code, + codeHash: code, + used: false, + usedAt: null, + })), + }) as TUser, + ); + }, [setUser, localize, showToast, backupCodes]); + + const handleDisableVerify = useCallback( + (token: string, useBackup: boolean) => { + // Validate: if not using backup, ensure token has at least 6 digits; + // if using backup, ensure backup code has at least 8 characters. + if (!useBackup && token.trim().length < 6) { + return; + } + + if (useBackup && token.trim().length < 8) { + return; + } + + const payload: TVerify2FARequest = {}; + if (useBackup) { + payload.backupCode = token.trim(); + } else { + payload.token = token.trim(); + } + + verify2FAMutate(payload, { + onSuccess: () => { + disable2FAMutate(undefined, { + onSuccess: () => { + showToast({ message: localize('com_ui_2fa_disabled') }); + setDialogOpen(false); + setUser( + (prev) => + ({ + ...prev, + totpSecret: '', + backupCodes: [], + }) as TUser, + ); + setPhase('setup'); + setOtpauthUrl(''); + }, + onError: () => + showToast({ message: localize('com_ui_2fa_disable_error'), status: 'error' }), + }); + }, + onError: () => showToast({ message: localize('com_ui_2fa_invalid'), status: 'error' }), + }); + }, + [disableToken, verify2FAMutate, disable2FAMutate, showToast, localize, setUser], + ); + + return ( + { + setDialogOpen(open); + if (!open) { + resetState(); + } + }} + > + 0} + onChange={() => setDialogOpen(true)} + disabled={isVerifying || isDisabling || isGenerating} + /> + + + + + + + + {Array.isArray(user?.backupCodes) && user?.backupCodes.length > 0 ? localize('com_ui_2fa_disable') : localize('com_ui_2fa_setup')} + + {Array.isArray(user?.backupCodes) && user?.backupCodes.length > 0 && phase !== 'disable' && ( +
+ +
+ {steps.map((step, index) => ( + = index ? 'var(--text-primary)' : 'var(--text-tertiary)', + }} + className="font-medium" + > + {step} + + ))} +
+
+ )} +
+ + + {phase === 'setup' && ( + setPhase('qr')} + onError={(error) => showToast({ message: error.message, status: 'error' })} + /> + )} + + {phase === 'qr' && ( + setPhase('verify')} + onError={(error) => showToast({ message: error.message, status: 'error' })} + /> + )} + + {phase === 'verify' && ( + showToast({ message: error.message, status: 'error' })} + /> + )} + + {phase === 'backup' && ( + showToast({ message: error.message, status: 'error' })} + /> + )} + + {phase === 'disable' && ( + showToast({ message: error.message, status: 'error' })} + /> + )} + +
+
+
+
+ ); +}; + +export default React.memo(TwoFactorAuthentication); diff --git a/client/src/components/Nav/SettingsTabs/Account/TwoFactorPhases/BackupPhase.tsx b/client/src/components/Nav/SettingsTabs/Account/TwoFactorPhases/BackupPhase.tsx new file mode 100644 index 0000000000..67e05a1423 --- /dev/null +++ b/client/src/components/Nav/SettingsTabs/Account/TwoFactorPhases/BackupPhase.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { motion } from 'framer-motion'; +import { Download } from 'lucide-react'; +import { Button, Label } from '~/components'; +import { useLocalize } from '~/hooks'; + +const fadeAnimation = { + initial: { opacity: 0, y: 20 }, + animate: { opacity: 1, y: 0 }, + exit: { opacity: 0, y: -20 }, + transition: { duration: 0.2 }, +}; + +interface BackupPhaseProps { + onNext: () => void; + onError: (error: Error) => void; + backupCodes: string[]; + onDownload: () => void; + downloaded: boolean; +} + +export const BackupPhase: React.FC = ({ + backupCodes, + onDownload, + downloaded, + onNext, +}) => { + const localize = useLocalize(); + + return ( + + +
+ {backupCodes.map((code, index) => ( + +
+ #{index + 1} + {code} +
+
+ ))} +
+
+ + +
+
+ ); +}; diff --git a/client/src/components/Nav/SettingsTabs/Account/TwoFactorPhases/DisablePhase.tsx b/client/src/components/Nav/SettingsTabs/Account/TwoFactorPhases/DisablePhase.tsx new file mode 100644 index 0000000000..27422d26c3 --- /dev/null +++ b/client/src/components/Nav/SettingsTabs/Account/TwoFactorPhases/DisablePhase.tsx @@ -0,0 +1,88 @@ +import React, { useState } from 'react'; +import { motion } from 'framer-motion'; +import { REGEXP_ONLY_DIGITS, REGEXP_ONLY_DIGITS_AND_CHARS } from 'input-otp'; +import { + Button, + InputOTP, + InputOTPGroup, + InputOTPSlot, + InputOTPSeparator, + Spinner, +} from '~/components'; +import { useLocalize } from '~/hooks'; + +const fadeAnimation = { + initial: { opacity: 0, y: 20 }, + animate: { opacity: 1, y: 0 }, + exit: { opacity: 0, y: -20 }, + transition: { duration: 0.2 }, +}; + +interface DisablePhaseProps { + onSuccess?: () => void; + onError?: (error: Error) => void; + onDisable: (token: string, useBackup: boolean) => void; + isDisabling: boolean; +} + +export const DisablePhase: React.FC = ({ onDisable, isDisabling }) => { + const localize = useLocalize(); + const [token, setToken] = useState(''); + const [useBackup, setUseBackup] = useState(false); + + return ( + +
+ + {useBackup ? ( + + + + + + + + + + + ) : ( + <> + + + + + + + + + + + + + )} + +
+ + +
+ ); +}; diff --git a/client/src/components/Nav/SettingsTabs/Account/TwoFactorPhases/QRPhase.tsx b/client/src/components/Nav/SettingsTabs/Account/TwoFactorPhases/QRPhase.tsx new file mode 100644 index 0000000000..7a0eccae3f --- /dev/null +++ b/client/src/components/Nav/SettingsTabs/Account/TwoFactorPhases/QRPhase.tsx @@ -0,0 +1,66 @@ +import React, { useState } from 'react'; +import { motion } from 'framer-motion'; +import { QRCodeSVG } from 'qrcode.react'; +import { Copy, Check } from 'lucide-react'; +import { Input, Button, Label } from '~/components'; +import { useLocalize } from '~/hooks'; +import { cn } from '~/utils'; + +const fadeAnimation = { + initial: { opacity: 0, y: 20 }, + animate: { opacity: 1, y: 0 }, + exit: { opacity: 0, y: -20 }, + transition: { duration: 0.2 }, +}; + +interface QRPhaseProps { + secret: string; + otpauthUrl: string; + onNext: () => void; + onSuccess?: () => void; + onError?: (error: Error) => void; +} + +export const QRPhase: React.FC = ({ secret, otpauthUrl, onNext }) => { + const localize = useLocalize(); + const [isCopying, setIsCopying] = useState(false); + + const handleCopy = async () => { + await navigator.clipboard.writeText(secret); + setIsCopying(true); + setTimeout(() => setIsCopying(false), 2000); + }; + + return ( + +
+ + + +
+ +
+ + +
+
+
+ +
+ ); +}; diff --git a/client/src/components/Nav/SettingsTabs/Account/TwoFactorPhases/SetupPhase.tsx b/client/src/components/Nav/SettingsTabs/Account/TwoFactorPhases/SetupPhase.tsx new file mode 100644 index 0000000000..4fd2d1181d --- /dev/null +++ b/client/src/components/Nav/SettingsTabs/Account/TwoFactorPhases/SetupPhase.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { QrCode } from 'lucide-react'; +import { motion } from 'framer-motion'; +import { Button, Spinner } from '~/components'; +import { useLocalize } from '~/hooks'; + +const fadeAnimation = { + initial: { opacity: 0, y: 20 }, + animate: { opacity: 1, y: 0 }, + exit: { opacity: 0, y: -20 }, + transition: { duration: 0.2 }, +}; + +interface SetupPhaseProps { + onNext: () => void; + onError: (error: Error) => void; + isGenerating: boolean; + onGenerate: () => void; +} + +export const SetupPhase: React.FC = ({ isGenerating, onGenerate, onNext }) => { + const localize = useLocalize(); + + return ( + +
+

+ {localize('com_ui_2fa_account_security')} +

+ +
+
+ ); +}; diff --git a/client/src/components/Nav/SettingsTabs/Account/TwoFactorPhases/VerifyPhase.tsx b/client/src/components/Nav/SettingsTabs/Account/TwoFactorPhases/VerifyPhase.tsx new file mode 100644 index 0000000000..e872dfa0d2 --- /dev/null +++ b/client/src/components/Nav/SettingsTabs/Account/TwoFactorPhases/VerifyPhase.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { motion } from 'framer-motion'; +import { Button, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from '~/components'; +import { REGEXP_ONLY_DIGITS } from 'input-otp'; +import { useLocalize } from '~/hooks'; + +const fadeAnimation = { + initial: { opacity: 0, y: 20 }, + animate: { opacity: 1, y: 0 }, + exit: { opacity: 0, y: -20 }, + transition: { duration: 0.2 }, +}; + +interface VerifyPhaseProps { + token: string; + onTokenChange: (value: string) => void; + isVerifying: boolean; + onNext: () => void; + onError: (error: Error) => void; +} + +export const VerifyPhase: React.FC = ({ + token, + onTokenChange, + isVerifying, + onNext, +}) => { + const localize = useLocalize(); + + return ( + +
+ + + {Array.from({ length: 3 }).map((_, i) => ( + + ))} + + + + {Array.from({ length: 3 }).map((_, i) => ( + + ))} + + +
+ +
+ ); +}; diff --git a/client/src/components/Nav/SettingsTabs/Account/TwoFactorPhases/index.ts b/client/src/components/Nav/SettingsTabs/Account/TwoFactorPhases/index.ts new file mode 100644 index 0000000000..1cc474efef --- /dev/null +++ b/client/src/components/Nav/SettingsTabs/Account/TwoFactorPhases/index.ts @@ -0,0 +1,5 @@ +export * from './BackupPhase'; +export * from './QRPhase'; +export * from './VerifyPhase'; +export * from './SetupPhase'; +export * from './DisablePhase'; diff --git a/client/src/components/Nav/SettingsTabs/Data/ImportConversations.tsx b/client/src/components/Nav/SettingsTabs/Data/ImportConversations.tsx index c39e8351e8..e3bafd9152 100644 --- a/client/src/components/Nav/SettingsTabs/Data/ImportConversations.tsx +++ b/client/src/components/Nav/SettingsTabs/Data/ImportConversations.tsx @@ -82,7 +82,7 @@ function ImportConversations() { onClick={handleImportClick} onKeyDown={handleKeyDown} disabled={!allowImport} - aria-label={localize('com_ui_import_conversation')} + aria-label={localize('com_ui_import')} className="btn btn-neutral relative" > {allowImport ? ( @@ -90,7 +90,7 @@ function ImportConversations() { ) : ( )} - {localize('com_ui_import_conversation')} + {localize('com_ui_import')} setIsOpen(true)}> - + , + React.ComponentPropsWithoutRef +>(({ className, containerClassName, ...props }, ref) => ( + +)); +InputOTP.displayName = 'InputOTP'; + +const InputOTPGroup = React.forwardRef< + React.ElementRef<'div'>, + React.ComponentPropsWithoutRef<'div'> +>(({ className, ...props }, ref) => ( +
+)); +InputOTPGroup.displayName = 'InputOTPGroup'; + +const InputOTPSlot = React.forwardRef< + React.ElementRef<'div'>, + React.ComponentPropsWithoutRef<'div'> & { index: number } +>(({ index, className, ...props }, ref) => { + const inputOTPContext = React.useContext(OTPInputContext); + const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]; + + return ( +
+ {char} + {hasFakeCaret && ( +
+
+
+ )} +
+ ); +}); +InputOTPSlot.displayName = 'InputOTPSlot'; + +const InputOTPSeparator = React.forwardRef< + React.ElementRef<'div'>, + React.ComponentPropsWithoutRef<'div'> +>(({ ...props }, ref) => ( +
+ +
+)); +InputOTPSeparator.displayName = 'InputOTPSeparator'; + +export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }; diff --git a/client/src/components/ui/Progress.tsx b/client/src/components/ui/Progress.tsx new file mode 100644 index 0000000000..e8e0b0f6b2 --- /dev/null +++ b/client/src/components/ui/Progress.tsx @@ -0,0 +1,22 @@ +import * as React from 'react'; +import * as ProgressPrimitive from '@radix-ui/react-progress'; +import { cn } from '~/utils'; + +const Progress = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, value, ...props }, ref) => ( + + + +)); +Progress.displayName = ProgressPrimitive.Root.displayName; + +export { Progress }; diff --git a/client/src/components/ui/index.ts b/client/src/components/ui/index.ts index d81a8cad5d..b0025d2536 100644 --- a/client/src/components/ui/index.ts +++ b/client/src/components/ui/index.ts @@ -24,6 +24,8 @@ export * from './Textarea'; export * from './TextareaAutosize'; export * from './Tooltip'; export * from './Pagination'; +export * from './Progress'; +export * from './InputOTP'; export { default as Combobox } from './Combobox'; export { default as Dropdown } from './Dropdown'; export { default as FileUpload } from './FileUpload'; diff --git a/client/src/data-provider/Auth/mutations.ts b/client/src/data-provider/Auth/mutations.ts index 49d4fbe1d0..eb09868ec6 100644 --- a/client/src/data-provider/Auth/mutations.ts +++ b/client/src/data-provider/Auth/mutations.ts @@ -1,6 +1,6 @@ import { useResetRecoilState, useSetRecoilState } from 'recoil'; import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { MutationKeys, dataService, request } from 'librechat-data-provider'; +import { MutationKeys, QueryKeys, dataService, request } from 'librechat-data-provider'; import type { UseMutationResult } from '@tanstack/react-query'; import type * as t from 'librechat-data-provider'; import useClearStates from '~/hooks/Config/useClearStates'; @@ -84,3 +84,91 @@ export const useDeleteUserMutation = ( }, }); }; + +// Array.isArray(user?.backupCodes) && user?.backupCodes.length > 0 + +export const useEnableTwoFactorMutation = (): UseMutationResult< + t.TEnable2FAResponse, + unknown, + void, + unknown +> => { + const queryClient = useQueryClient(); + return useMutation(() => dataService.enableTwoFactor(), { + onSuccess: (data) => { + queryClient.setQueryData([QueryKeys.user, '2fa'], data); + }, + }); +}; + +export const useVerifyTwoFactorMutation = (): UseMutationResult< + t.TVerify2FAResponse, + unknown, + t.TVerify2FARequest, + unknown +> => { + const queryClient = useQueryClient(); + return useMutation((payload: t.TVerify2FARequest) => dataService.verifyTwoFactor(payload), { + onSuccess: (data) => { + queryClient.setQueryData([QueryKeys.user, '2fa'], data); + }, + }); +}; + +export const useConfirmTwoFactorMutation = (): UseMutationResult< + t.TVerify2FAResponse, + unknown, + t.TVerify2FARequest, + unknown +> => { + const queryClient = useQueryClient(); + return useMutation((payload: t.TVerify2FARequest) => dataService.confirmTwoFactor(payload), { + onSuccess: (data) => { + queryClient.setQueryData([QueryKeys.user, '2fa'], data); + }, + }); +}; + +export const useDisableTwoFactorMutation = (): UseMutationResult< + t.TDisable2FAResponse, + unknown, + void, + unknown +> => { + const queryClient = useQueryClient(); + return useMutation(() => dataService.disableTwoFactor(), { + onSuccess: (data) => { + queryClient.setQueryData([QueryKeys.user, '2fa'], null); + }, + }); +}; + +export const useRegenerateBackupCodesMutation = (): UseMutationResult< + t.TRegenerateBackupCodesResponse, + unknown, + void, + unknown +> => { + const queryClient = useQueryClient(); + return useMutation(() => dataService.regenerateBackupCodes(), { + onSuccess: (data) => { + queryClient.setQueryData([QueryKeys.user, '2fa', 'backup'], data); + }, + }); +}; + +export const useVerifyTwoFactorTempMutation = ( + options?: t.MutationOptions, +): UseMutationResult => { + const queryClient = useQueryClient(); + return useMutation( + (payload: t.TVerify2FATempRequest) => dataService.verifyTwoFactorTemp(payload), + { + ...(options || {}), + onSuccess: (data, ...args) => { + queryClient.setQueryData([QueryKeys.user, '2fa'], data); + options?.onSuccess?.(data, ...args); + }, + }, + ); +}; diff --git a/client/src/hooks/AuthContext.tsx b/client/src/hooks/AuthContext.tsx index 3a680f30b8..3d000ff78e 100644 --- a/client/src/hooks/AuthContext.tsx +++ b/client/src/hooks/AuthContext.tsx @@ -70,7 +70,12 @@ const AuthContextProvider = ({ const loginUser = useLoginUserMutation({ onSuccess: (data: t.TLoginResponse) => { - const { user, token } = data; + const { user, token, twoFAPending, tempToken } = data; + if (twoFAPending) { + // Redirect to the two-factor authentication route. + navigate(`/login/2fa?tempToken=${tempToken}`, { replace: true }); + return; + } setError(undefined); setUserContext({ token, isAuthenticated: true, user, redirect: '/c/new' }); }, @@ -212,4 +217,4 @@ const useAuthContext = () => { return context; }; -export { AuthContextProvider, useAuthContext }; +export { AuthContextProvider, useAuthContext }; \ No newline at end of file diff --git a/client/src/locales/ar/translation.json b/client/src/locales/ar/translation.json index 54824fd1a7..f0f9f25ebd 100644 --- a/client/src/locales/ar/translation.json +++ b/client/src/locales/ar/translation.json @@ -178,7 +178,6 @@ "com_endpoint_google_temp": "القيم الأعلى = أكثر عشوائية، بينما القيم الأقل = أكثر تركيزًا وحتمية. نوصي بتغيير هذا أو Top P ولكن ليس كلاهما.", "com_endpoint_google_topk": "Top-k يغير كيفية اختيار النموذج للرموز للإخراج. top-k من 1 يعني أن الرمز المحدد هو الأكثر احتمالية بين جميع الرموز في مفردات النموذج (يسمى أيضًا الترميز الجشعي)، بينما top-k من 3 يعني أن الرمز التالي يتم اختياره من بين الرموز الثلاثة الأكثر احتمالية (باستخدام الحرارة).", "com_endpoint_google_topp": "Top-p يغير كيفية اختيار النموذج للرموز للإخراج. يتم اختيار الرموز من الأكثر K (انظر معلمة topK) احتمالًا إلى الأقل حتى يصبح مجموع احتمالاتهم يساوي قيمة top-p.", - "com_endpoint_import": "استيراد", "com_endpoint_instructions_assistants": "تعليمات التجاوز", "com_endpoint_instructions_assistants_placeholder": "يتجاوز التعليمات الخاصة بالمساعد. هذا مفيد لتعديل السلوك على أساس كل مرة.", "com_endpoint_max_output_tokens": "الحد الأقصى لعدد الرموز المنتجة", @@ -262,7 +261,6 @@ "com_nav_archive_name": "الاسم", "com_nav_archived_chats": "الدردشات المؤرشفة", "com_nav_archived_chats_empty": "ليس لديك أي دردشات مؤرشفة.", - "com_nav_archived_chats_manage": "إدارة", "com_nav_at_command": "أمر-@", "com_nav_at_command_description": "تبديل الأمر \"@\" للتنقل بين نقاط النهاية والنماذج والإعدادات المسبقة وغيرها", "com_nav_audio_play_error": "خطأ في تشغيل الصوت: {{0}}", @@ -383,7 +381,6 @@ "com_nav_setting_speech": "الكلام", "com_nav_settings": "الإعدادات", "com_nav_shared_links": "روابط مشتركة", - "com_nav_shared_links_manage": "الإدارة", "com_nav_show_code": "إظهار الشفرة دائمًا عند استخدام مفسر الشفرة", "com_nav_slash_command": "/-الأمر", "com_nav_slash_command_description": "تبديل الأمر \"/\" لاختيار موجه عبر لوحة المفاتيح", @@ -587,7 +584,7 @@ "com_ui_happy_birthday": "إنه عيد ميلادي الأول!", "com_ui_host": "مُضيف", "com_ui_image_gen": "توليد الصور", - "com_ui_import_conversation": "استيراد", + "com_ui_import": "استيراد", "com_ui_import_conversation_error": "حدث خطأ أثناء استيراد محادثاتك", "com_ui_import_conversation_file_type_error": "نوع الملف غير مدعوم للاستيراد", "com_ui_import_conversation_info": "استيراد محادثات من ملف JSON", @@ -719,4 +716,4 @@ "com_ui_zoom": "تكبير", "com_user_message": "أنت", "com_warning_resubmit_unsupported": "إعادة إرسال رسالة الذكاء الاصطناعي غير مدعومة لنقطة النهاية هذه" -} \ No newline at end of file +} diff --git a/client/src/locales/de/translation.json b/client/src/locales/de/translation.json index e02657b4b4..98cd14b865 100644 --- a/client/src/locales/de/translation.json +++ b/client/src/locales/de/translation.json @@ -182,7 +182,6 @@ "com_endpoint_google_temp": "Höhere Werte = zufälliger, während niedrigere Werte = fokussierter und deterministischer. Wir empfehlen, entweder dies oder Top P zu ändern, aber nicht beides.", "com_endpoint_google_topk": "Top-k ändert, wie das Modell Token für die Antwort auswählt. Ein Top-k von 1 bedeutet, dass das ausgewählte Token das wahrscheinlichste unter allen Token im Vokabular des Modells ist (auch Greedy-Decoding genannt), während ein Top-k von 3 bedeutet, dass das nächste Token aus den 3 wahrscheinlichsten Token ausgewählt wird (unter Verwendung der Temperatur).", "com_endpoint_google_topp": "Top-p ändert, wie das Modell Token für die Antwort auswählt. Token werden von den wahrscheinlichsten K (siehe topK-Parameter) bis zu den am wenigsten wahrscheinlichen ausgewählt, bis die Summe ihrer Wahrscheinlichkeiten dem Top-p-Wert entspricht.", - "com_endpoint_import": "Importieren", "com_endpoint_instructions_assistants": "Anweisungen überschreiben", "com_endpoint_instructions_assistants_placeholder": "Überschreibt die Anweisungen des Assistenten. Dies ist nützlich, um das Verhalten auf Basis einzelner Ausführungen zu modifizieren.", "com_endpoint_max_output_tokens": "Max. Antwort-Token", @@ -268,7 +267,6 @@ "com_nav_archive_name": "Name", "com_nav_archived_chats": "Archivierte Chats", "com_nav_archived_chats_empty": "Du hast keine archivierten Chats.", - "com_nav_archived_chats_manage": "Verwalten", "com_nav_at_command": "@-Befehl", "com_nav_at_command_description": "Schaltet den Befehl \"@\" zum Wechseln von Endpunkten, Modellen, Voreinstellungen usw. um.", "com_nav_audio_play_error": "Fehler beim Abspielen des Audios: {{0}}", @@ -391,7 +389,6 @@ "com_nav_setting_speech": "Sprache", "com_nav_settings": "Einstellungen", "com_nav_shared_links": "Geteilte Links", - "com_nav_shared_links_manage": "Verwalten", "com_nav_show_code": "Code immer anzeigen, wenn der Code-Interpreter verwendet wird", "com_nav_show_thinking": "Denkprozess-Dropdowns standardmäßig öffnen", "com_nav_slash_command": "/-Befehl", @@ -617,7 +614,7 @@ "com_ui_hide_qr": "QR-Code ausblenden", "com_ui_host": "Host", "com_ui_image_gen": "Bildgenerierung", - "com_ui_import_conversation": "Importieren", + "com_ui_import": "Importieren", "com_ui_import_conversation_error": "Beim Importieren Ihrer Konversationen ist ein Fehler aufgetreten", "com_ui_import_conversation_file_type_error": "Nicht unterstützter Importtyp", "com_ui_import_conversation_info": "Konversationen aus einer JSON-Datei importieren", @@ -767,4 +764,4 @@ "com_ui_zoom": "Zoom", "com_user_message": "Du", "com_warning_resubmit_unsupported": "Das erneute Senden der KI-Nachricht wird für diesen Endpunkt nicht unterstützt." -} \ No newline at end of file +} diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index 1daa6794d6..63272a54f4 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -1,6 +1,6 @@ { - "chat_direction_left_to_right": "something needs to go here. was empty", - "chat_direction_right_to_left": "something needs to go here. was empty", + "chat_direction_left_to_right": "Chat direction is now left to right", + "chat_direction_right_to_left": "Chat direction is now right to left", "com_a11y_ai_composing": "The AI is still composing.", "com_a11y_end": "The AI has finished their reply.", "com_a11y_start": "The AI has started their reply.", @@ -87,6 +87,7 @@ "com_auth_email_verification_redirecting": "Redirecting in {{0}} seconds...", "com_auth_email_verification_resend_prompt": "Didn't receive the email?", "com_auth_email_verification_success": "Email verified successfully", + "com_auth_email_verifying_ellipsis": "Verifying...", "com_auth_error_create": "There was an error attempting to register your account. Please try again.", "com_auth_error_invalid_reset_token": "This password reset token is no longer valid.", "com_auth_error_login": "Unable to login with the information provided. Please check your credentials and try again.", @@ -184,7 +185,6 @@ "com_endpoint_google_temp": "Higher values = more random, while lower values = more focused and deterministic. We recommend altering this or Top P but not both.", "com_endpoint_google_topk": "Top-k changes how the model selects tokens for output. A top-k of 1 means the selected token is the most probable among all tokens in the model's vocabulary (also called greedy decoding), while a top-k of 3 means that the next token is selected from among the 3 most probable tokens (using temperature).", "com_endpoint_google_topp": "Top-p changes how the model selects tokens for output. Tokens are selected from most K (see topK parameter) probable to least until the sum of their probabilities equals the top-p value.", - "com_endpoint_import": "Import", "com_endpoint_instructions_assistants": "Override Instructions", "com_endpoint_instructions_assistants_placeholder": "Overrides the instructions of the assistant. This is useful for modifying the behavior on a per-run basis.", "com_endpoint_max_output_tokens": "Max Output Tokens", @@ -263,7 +263,7 @@ "com_files_filter": "Filter files...", "com_files_no_results": "No results.", "com_files_number_selected": "{{0}} of {{1}} items(s) selected", - "com_files_table": "something needs to go here. was empty", + "com_files_table": "Files Table", "com_generated_files": "Generated files:", "com_hide_examples": "Hide Examples", "com_nav_account_settings": "Account Settings", @@ -272,7 +272,6 @@ "com_nav_archive_name": "Name", "com_nav_archived_chats": "Archived chats", "com_nav_archived_chats_empty": "You have no archived conversations.", - "com_nav_archived_chats_manage": "Manage", "com_nav_at_command": "@-Command", "com_nav_at_command_description": "Toggle command \"@\" for switching endpoints, models, presets, etc.", "com_nav_audio_play_error": "Error playing audio: {{0}}", @@ -396,7 +395,6 @@ "com_nav_setting_speech": "Speech", "com_nav_settings": "Settings", "com_nav_shared_links": "Shared links", - "com_nav_shared_links_manage": "Manage", "com_nav_show_code": "Always show code when using code interpreter", "com_nav_show_thinking": "Open Thinking Dropdowns by Default", "com_nav_slash_command": "/-Command", @@ -437,6 +435,9 @@ "com_sidepanel_parameters": "Parameters", "com_sidepanel_select_agent": "Select an Agent", "com_sidepanel_select_assistant": "Select an Assistant", + "com_nav_2fa": "Two-Factor Authentication (2FA)", + "com_auth_verify_your_identity": "Verify Your Identity", + "com_auth_two_factor": "Check your preferred one-time password application for a code", "com_ui_accept": "I accept", "com_ui_add": "Add", "com_ui_add_model_preset": "Add a model or preset for an additional response", @@ -626,7 +627,7 @@ "com_ui_fork_split_target_setting": "Start fork from target message by default", "com_ui_fork_success": "Successfully forked conversation", "com_ui_fork_visible": "Visible messages only", - "com_ui_global_group": "something needs to go here. was empty", + "com_ui_global_group": "Global Group", "com_ui_go_back": "Go back", "com_ui_go_to_conversation": "Go to conversation", "com_ui_happy_birthday": "It's my 1st birthday!", @@ -634,7 +635,7 @@ "com_ui_host": "Host", "com_ui_idea": "Ideas", "com_ui_image_gen": "Image Gen", - "com_ui_import_conversation": "Import", + "com_ui_import": "Import", "com_ui_import_conversation_error": "There was an error importing your conversations", "com_ui_import_conversation_file_type_error": "Unsupported import type", "com_ui_import_conversation_info": "Import conversations from a JSON file", @@ -670,9 +671,9 @@ "com_ui_no_bookmarks": "it seems like you have no bookmarks yet. Click on a chat and add a new one", "com_ui_no_category": "No category", "com_ui_no_changes": "No changes to update", - "com_ui_no_data": "something needs to go here. was empty", + "com_ui_no_data": "No data", "com_ui_no_terms_content": "No terms and conditions content to display", - "com_ui_no_valid_items": "something needs to go here. was empty", + "com_ui_no_valid_items": "No valid items", "com_ui_none": "None", "com_ui_none_selected": "None selected", "com_ui_nothing_found": "Nothing found", @@ -795,6 +796,36 @@ "com_ui_write": "Writing", "com_ui_yes": "Yes", "com_ui_zoom": "Zoom", + "com_ui_secret_key": "Secret Key", + "com_ui_2fa_account_security": "Two-factor authentication adds an extra layer of security to your account", + "com_ui_2fa_generate_error": "There was an error generating two-factor authentication settings", + "com_ui_backup_codes": "Backup Codes", + "com_ui_2fa_invalid": "Invalid two-factor authentication code", + "com_ui_2fa_setup": "Setup 2FA", + "com_ui_2fa_enable": "Enable 2FA", + "com_ui_2fa_disable": "Disable 2FA", + "com_ui_disabling": "Disabling...", + "com_ui_2fa_enabled": "2FA has been enabled", + "com_ui_2fa_disabled": "2FA has been disabled", + "com_ui_download_backup": "Download Backup Codes", + "com_ui_use_backup_code": "Use Backup Code Instead", + "com_ui_use_2fa_code": "Use 2FA Code Instead", + "com_ui_verify": "Verify", + "com_ui_2fa_disable_error": "There was an error disabling two-factor authentication", + "com_ui_2fa_verified": "Successfully verified Two-Factor Authentication", + "com_ui_generate_backup": "Generate Backup Codes", + "com_ui_regenerate_backup": "Regenerate Backup Codes", + "com_ui_regenerating": "Regenerating...", + "com_ui_used": "Used", + "com_ui_not_used": "Not Used", + "com_ui_backup_codes_regenerated": "Backup codes have been regenerated successfully", + "com_ui_backup_codes_regenerate_error": "There was an error regenerating backup codes", + "com_ui_no_backup_codes": "No backup codes available. Please generate new ones", + "com_ui_generating": "Generating...", + "com_ui_generate_qrcode": "Generate QR Code", + "com_ui_complete_setup": "Complete Setup", + "com_ui_download_backup_tooltip": "Before you continue, download your backup codes. You will need them to regain access if you lose your authenticator device", + "com_ui_show": "Show", "com_user_message": "You", "com_warning_resubmit_unsupported": "Resubmitting the AI message is not supported for this endpoint." -} \ No newline at end of file +} diff --git a/client/src/locales/es/translation.json b/client/src/locales/es/translation.json index a662d76452..3355d79df4 100644 --- a/client/src/locales/es/translation.json +++ b/client/src/locales/es/translation.json @@ -178,7 +178,6 @@ "com_endpoint_google_temp": "Los valores más altos = más aleatorios, mientras que los valores más bajos = más enfocados y deterministas. Recomendamos alterar esto o Top P, pero no ambos.", "com_endpoint_google_topk": "Top-k cambia la forma en que el modelo selecciona tokens para la salida. Un top-k de 1 significa que el token seleccionado es el más probable entre todos los tokens en el vocabulario del modelo (también llamado decodificación codiciosa), mientras que un top-k de 3 significa que el siguiente token se selecciona entre los 3 tokens más probables (usando temperatura).", "com_endpoint_google_topp": "Top-p cambia la forma en que el modelo selecciona tokens para la salida. Los tokens se seleccionan desde los más K (ver parámetro topK) probables hasta los menos probables hasta que la suma de sus probabilidades sea igual al valor top-p.", - "com_endpoint_import": "Importar", "com_endpoint_instructions_assistants": "Anular instrucciones", "com_endpoint_instructions_assistants_placeholder": "Anula las instrucciones del asistente. Esto es útil para modificar el comportamiento por ejecución.", "com_endpoint_max_output_tokens": "Tokens de Salida Máximos", @@ -262,7 +261,6 @@ "com_nav_archive_name": "Nombre", "com_nav_archived_chats": "Archivadas", "com_nav_archived_chats_empty": "No tienes conversaciones archivadas.", - "com_nav_archived_chats_manage": "Gestionar", "com_nav_at_command": "Comando @", "com_nav_at_command_description": "Alternar comando \"@\" para cambiar entre puntos de conexión, modelos, ajustes predefinidos, etc.", "com_nav_audio_play_error": "Error al reproducir el audio: {{0}}", @@ -383,7 +381,6 @@ "com_nav_setting_speech": "Voz y habla", "com_nav_settings": "Configuración", "com_nav_shared_links": "Links Compartidos", - "com_nav_shared_links_manage": "Gerenciar", "com_nav_show_code": "Mostrar siempre el código cuando se use el intérprete de código", "com_nav_slash_command": "Comando /", "com_nav_slash_command_description": "Alternar comando '/' para seleccionar un mensaje predefinido mediante el teclado", @@ -587,7 +584,7 @@ "com_ui_happy_birthday": "¡Es mi primer cumpleaños!", "com_ui_host": "Host", "com_ui_image_gen": "Gen Imágenes", - "com_ui_import_conversation": "Importar", + "com_ui_import": "Importar", "com_ui_import_conversation_error": "Hubo un error al importar tus chats", "com_ui_import_conversation_file_type_error": "com_ui_import_conversation_file_type_error: Tipo de archivo no compatible para importar", "com_ui_import_conversation_info": "Importar chats de un archivo JSON", @@ -719,4 +716,4 @@ "com_ui_zoom": "Zoom", "com_user_message": "Usted", "com_warning_resubmit_unsupported": "No se admite el reenvío del mensaje de IA para este punto de conexión." -} \ No newline at end of file +} diff --git a/client/src/locales/et/translation.json b/client/src/locales/et/translation.json index cbf616cec1..84d40e80f6 100644 --- a/client/src/locales/et/translation.json +++ b/client/src/locales/et/translation.json @@ -396,7 +396,6 @@ "com_nav_setting_speech": "Kõne", "com_nav_settings": "Seaded", "com_nav_shared_links": "Jagatud lingid", - "com_nav_shared_links_manage": "Halda", "com_nav_show_code": "Näita koodi alati, kui kasutatakse koodiinterpreteerijat", "com_nav_show_thinking": "Ava mõtlemise rippmenüüd vaikimisi", "com_nav_slash_command": "/-käsk", @@ -787,4 +786,4 @@ "com_ui_zoom": "Suumi", "com_user_message": "Sina", "com_warning_resubmit_unsupported": "AI sõnumi uuesti esitamine pole selle otspunkti jaoks toetatud." -} \ No newline at end of file +} diff --git a/client/src/locales/fi/translation.json b/client/src/locales/fi/translation.json index 3005dcfb6b..190d344026 100644 --- a/client/src/locales/fi/translation.json +++ b/client/src/locales/fi/translation.json @@ -149,7 +149,6 @@ "com_endpoint_google_temp": "Korkeampi arvo = satunnaisempi; matalampi arvo = keskittyneempi ja deterministisempi. Suosittelemme, että muokkaat tätä tai Top P:tä, mutta ei molempia.", "com_endpoint_google_topk": "Top-k vaikuttaa siihen, miten malli valitsee tokeineita tulokseen. Jos Top-k on 1, valitaan se token, joka on kaikkien todennäköisen mallin sanastossa (tunnetaan myös nimellä ahne dekoodaus), kun taas top-k 3 tarkoittaisi, että seuraavat token valitaan 3 todennäköisimmän tokenin joukosta, lämpötilaa hyödyntäen.", "com_endpoint_google_topp": "Top-P vaikuttaa siihen kuinka malli valitsee tokeneita tulokseen. Tokenit valitaan top-k:sta (ks. Top-k -parametri) todennäköisimmistä vähiten todennäköseen, kunnes niiden todennäköisyyksien summa ylittää Top-P -arvon.", - "com_endpoint_import": "Tuo", "com_endpoint_instructions_assistants": "Yliaja ohjeet", "com_endpoint_instructions_assistants_placeholder": "Yliajaa Avustajan ohjeet. Tätä voi hyödyntää käytöksen muuttamiseen keskustelukohtaisesti.", "com_endpoint_max_output_tokens": "Tulos-tokeneiden maksimimäärä", @@ -220,7 +219,6 @@ "com_nav_archive_name": "Nimi", "com_nav_archived_chats": "Arkistoidut keskustelut", "com_nav_archived_chats_empty": "Sinulla ei ole arkistoituja keskusteluita.", - "com_nav_archived_chats_manage": "Hallinnoi", "com_nav_audio_play_error": "Virhe ääntä toistaessa: {{0}}", "com_nav_audio_process_error": "Virhe ääntä käsitellessä: {{0}}", "com_nav_auto_scroll": "Vieritä automaattisesti viimeisimpään viestiin keskustelua avatessa", @@ -315,7 +313,6 @@ "com_nav_setting_speech": "Puhe", "com_nav_settings": "Asetukset", "com_nav_shared_links": "Jaetut linkit", - "com_nav_shared_links_manage": "Hallinnoi", "com_nav_show_code": "Kooditulkkia käyttäessä näytä aina koodi", "com_nav_speech_to_text": "Puheesta tekstiksi", "com_nav_text_to_speech": "Tekstistä puheeksi", @@ -461,7 +458,7 @@ "com_ui_happy_birthday": "On 1. syntymäpäiväni!", "com_ui_host": "Host", "com_ui_image_gen": "Kuvanluonti", - "com_ui_import_conversation": "Tuo", + "com_ui_import": "Tuo", "com_ui_import_conversation_error": "Keskustelujesi tuonnissa tapahtui virhe", "com_ui_import_conversation_file_type_error": "Tiedostotyyppi ei ole tuettu tuonnissa", "com_ui_import_conversation_info": "Tuo keskusteluja JSON-tiedostosta", @@ -554,4 +551,4 @@ "com_ui_versions": "Versiot", "com_ui_yes": "Kyllä", "com_user_message": "Sinä" -} \ No newline at end of file +} diff --git a/client/src/locales/fr/translation.json b/client/src/locales/fr/translation.json index ccfae170d2..b1bd434c9d 100644 --- a/client/src/locales/fr/translation.json +++ b/client/src/locales/fr/translation.json @@ -179,7 +179,6 @@ "com_endpoint_google_temp": "Des valeurs plus élevées = plus aléatoires, tandis que des valeurs plus faibles = plus concentrées et déterministes. Nous vous recommandons de modifier ceci ou Top P mais pas les deux.", "com_endpoint_google_topk": "Top-k change la façon dont le modèle sélectionne les jetons pour la sortie. Un top-k de 1 signifie que le jeton sélectionné est le plus probable parmi tous les jetons du vocabulaire du modèle (également appelé décodage glouton), tandis qu'un top-k de 3 signifie que le jeton suivant est sélectionné parmi les 3 jetons les plus probables (en utilisant la température).", "com_endpoint_google_topp": "Top-p change la façon dont le modèle sélectionne les jetons pour la sortie. Les jetons sont sélectionnés du plus K (voir le paramètre topK) probable au moins jusqu'à ce que la somme de leurs probabilités égale la valeur top-p.", - "com_endpoint_import": "Importer", "com_endpoint_instructions_assistants": "Instructions de remplacement", "com_endpoint_instructions_assistants_placeholder": "Remplace les instructions de l'assistant. Cela est utile pour modifier le comportement au cas par cas.", "com_endpoint_max_output_tokens": "Nombre maximum de jetons en sortie", @@ -265,7 +264,6 @@ "com_nav_archive_name": "Nom", "com_nav_archived_chats": "Conversations archivées", "com_nav_archived_chats_empty": "Vous n'avez aucune conversation archivée.", - "com_nav_archived_chats_manage": "Gérer", "com_nav_at_command": "Commande-@", "com_nav_at_command_description": "Basculer la commande \"@\" pour changer d'endpoints, de modèles, de préréglages, etc.", "com_nav_audio_play_error": "Erreur de lecture audio : {{0}}", @@ -388,7 +386,6 @@ "com_nav_setting_speech": "Parole", "com_nav_settings": "Paramètres", "com_nav_shared_links": "Liens partagés", - "com_nav_shared_links_manage": "Gérer", "com_nav_show_code": "Toujours afficher le code lors de l'utilisation de l'interpréteur de code", "com_nav_show_thinking": "Ovrir les menus déroulants de réflexion par défaut", "com_nav_slash_command": "/-Commande", @@ -602,7 +599,7 @@ "com_ui_hide_qr": "Cacher le code QR", "com_ui_host": "Hôte", "com_ui_image_gen": "Génération d'image", - "com_ui_import_conversation": "Importer", + "com_ui_import": "Importer", "com_ui_import_conversation_error": "Une erreur s'est produite lors de l'importation de vos conversations", "com_ui_import_conversation_file_type_error": "Type de fichier non pris en charge pour l'importation", "com_ui_import_conversation_info": "Importer des conversations à partir d'un fichier JSON", @@ -736,4 +733,4 @@ "com_ui_zoom": "Zoom", "com_user_message": "Vous", "com_warning_resubmit_unsupported": "La resoumission du message IA n'est pas prise en charge pour ce point de terminaison." -} \ No newline at end of file +} diff --git a/client/src/locales/he/translation.json b/client/src/locales/he/translation.json index 3c2f1f6317..456d3d33e0 100644 --- a/client/src/locales/he/translation.json +++ b/client/src/locales/he/translation.json @@ -167,7 +167,6 @@ "com_nav_archive_name": "שם", "com_nav_archived_chats": "שיחות מארכיון", "com_nav_archived_chats_empty": "אין שיחות מארכיון.", - "com_nav_archived_chats_manage": "ניהול", "com_nav_auto_scroll": "Auto-s גלול אל הכי חדש בפתיחה", "com_nav_balance": "לְאַזֵן", "com_nav_change_picture": "שנה תמונה", @@ -231,7 +230,6 @@ "com_nav_setting_general": "כללי", "com_nav_settings": "הגדרות", "com_nav_shared_links": "קישורים משותפים", - "com_nav_shared_links_manage": "ניהול", "com_nav_theme": "נושא", "com_nav_theme_dark": "כהה", "com_nav_theme_light": "אור", @@ -293,7 +291,7 @@ "com_ui_error": "שגיאה", "com_ui_examples": "דוגמאות", "com_ui_happy_birthday": "זה יום ההולדת הראשון שלי!", - "com_ui_import_conversation": "יבוא", + "com_ui_import": "יבוא", "com_ui_import_conversation_error": "אירעה שגיאה בעת ייבוא השיחות שלך", "com_ui_import_conversation_info": "ייבא שיחות מקובץ JSON", "com_ui_import_conversation_success": "השיחות יובאו בהצלחה", @@ -333,4 +331,4 @@ "com_ui_upload_success": "קובץ שהועלה בהצלחה", "com_ui_use_prompt": "השתמש בהודעת", "com_user_message": "אתה" -} \ No newline at end of file +} diff --git a/client/src/locales/id/translation.json b/client/src/locales/id/translation.json index e5d1a23594..b6d7d57237 100644 --- a/client/src/locales/id/translation.json +++ b/client/src/locales/id/translation.json @@ -91,7 +91,6 @@ "com_endpoint_google_temp": "Nilai yang lebih tinggi = lebih acak, sedangkan nilai yang lebih rendah = lebih fokus dan deterministik. Kami merekomendasikan untuk mengubah ini atau Top P tetapi tidak keduanya.", "com_endpoint_google_topk": "Top-k mengubah cara model memilih token untuk output. Top-k 1 berarti token yang dipilih adalah yang paling mungkin di antara semua token dalam kosakata model (juga disebut decoding serakah), sedangkan top-k 3 berarti token berikutnya dipilih dari antara 3 token yang paling mungkin (menggunakan temperatur).", "com_endpoint_google_topp": "Top-p mengubah cara model memilih token untuk output. Token dipilih dari yang paling mungkin (lihat parameter topK) hingga yang paling tidak mungkin sampai jumlah probabilitas mereka sama dengan nilai top-p.", - "com_endpoint_import": "Impor", "com_endpoint_max_output_tokens": "Token Output Maks", "com_endpoint_message": "Pesan", "com_endpoint_message_not_appendable": "Edit pesan Anda atau Regenerasi.", @@ -142,7 +141,6 @@ "com_nav_archive_name": "Nama", "com_nav_archived_chats": "Percakapan Arsip", "com_nav_archived_chats_empty": "Tidak ada percakapan yang diarsipkan.", - "com_nav_archived_chats_manage": "Pengelolaan", "com_nav_auto_scroll": "Otomatis gulir ke Baru saat Buka", "com_nav_balance": "Keseimbangan", "com_nav_change_picture": "Ubah foto", @@ -205,7 +203,6 @@ "com_nav_setting_general": "Umum", "com_nav_settings": "Pengaturan", "com_nav_shared_links": "Link berbagi", - "com_nav_shared_links_manage": "Pengeluaran", "com_nav_theme": "Tema", "com_nav_theme_dark": "Gelap", "com_nav_theme_light": "Terang", @@ -250,7 +247,7 @@ "com_ui_enter": "Masuk", "com_ui_examples": "Contoh", "com_ui_happy_birthday": "Ini ulang tahun pertamaku!", - "com_ui_import_conversation": "Impor", + "com_ui_import": "Impor", "com_ui_import_conversation_error": "Terjadi kesalahan saat mengimpor percakapan Anda", "com_ui_import_conversation_info": "Impor percakapan dari file JSON", "com_ui_import_conversation_success": "Percakapan berhasil diimpor", @@ -287,4 +284,4 @@ "com_ui_upload_success": "Berhasil mengunggah file", "com_ui_use_prompt": "Gunakan petunjuk", "com_user_message": "Kamu" -} \ No newline at end of file +} diff --git a/client/src/locales/it/translation.json b/client/src/locales/it/translation.json index 45c26e11e2..d64fca28e5 100644 --- a/client/src/locales/it/translation.json +++ b/client/src/locales/it/translation.json @@ -179,7 +179,6 @@ "com_endpoint_google_temp": "Valori più alti = più casualità, mentre valori più bassi = più focalizzati e deterministici. Consigliamo di modificare questo o Top P ma non entrambi.", "com_endpoint_google_topk": "Top-k cambia il modo in cui il modello seleziona i token per l'output. Un top-k di 1 significa che il token selezionato è il più probabile tra tutti i token nel vocabolario del modello (anche chiamato greedy decoding), mentre un top-k di 3 significa che il prossimo token è selezionato tra i 3 più probabili (usando la temperatura).", "com_endpoint_google_topp": "Top-p cambia il modo in cui il modello seleziona i token per l'output. I token vengono selezionati dai più probabili K (vedi parametro topK) ai meno probabili fino a quando la somma delle loro probabilità eguaglia il valore top-p.", - "com_endpoint_import": "Importa", "com_endpoint_instructions_assistants": "Sovrascrivi istruzioni", "com_endpoint_instructions_assistants_placeholder": "Sovrascrive le istruzioni dell'assistente. Utile per modificare il comportamento su base singola.", "com_endpoint_max_output_tokens": "Token di output massimi", @@ -265,7 +264,6 @@ "com_nav_archive_name": "Nome", "com_nav_archived_chats": "Chat archiviate", "com_nav_archived_chats_empty": "Non hai chat archiviate.", - "com_nav_archived_chats_manage": "Gestisci", "com_nav_at_command": "Comando @", "com_nav_at_command_description": "Attiva il comando \"@\" per cambiare endpoint, modelli, preset e altro", "com_nav_audio_play_error": "Errore durante la riproduzione audio: {{0}}", @@ -388,7 +386,6 @@ "com_nav_setting_speech": "Voce", "com_nav_settings": "Impostazioni", "com_nav_shared_links": "Link condivisi", - "com_nav_shared_links_manage": "Gestisci", "com_nav_show_code": "Mostra sempre il codice quando si usa l'interprete di codice", "com_nav_show_thinking": "Apri i menu a tendina del ragionamento per impostazione predefinita", "com_nav_slash_command": "/-Comando", @@ -603,7 +600,7 @@ "com_ui_hide_qr": "Nascondi codice QR", "com_ui_host": "Host", "com_ui_image_gen": "Generazione immagine", - "com_ui_import_conversation": "Importa", + "com_ui_import": "Importa", "com_ui_import_conversation_error": "Si è verificato un errore durante l'importazione delle conversazioni", "com_ui_import_conversation_file_type_error": "Tipo di importazione non supportato", "com_ui_import_conversation_info": "Importa conversazioni da un file JSON", @@ -747,4 +744,4 @@ "com_ui_zoom": "Zoom", "com_user_message": "Mostra nome utente nei messaggi", "com_warning_resubmit_unsupported": "Il reinvio del messaggio AI non è supportato per questo endpoint." -} \ No newline at end of file +} diff --git a/client/src/locales/ja/translation.json b/client/src/locales/ja/translation.json index 52846cd59a..965ca5699d 100644 --- a/client/src/locales/ja/translation.json +++ b/client/src/locales/ja/translation.json @@ -178,7 +178,6 @@ "com_endpoint_google_temp": "大きい値 = ランダム性が増します。低い値 = より決定論的になります。この値を変更するか、Top P の変更をおすすめしますが、両方を変更はおすすめしません。", "com_endpoint_google_topk": "Top-k はモデルがトークンをどのように選択して出力するかを変更します。top-kが1の場合はモデルの語彙に含まれるすべてのトークンの中で最も確率が高い1つが選択されます(greedy decodingと呼ばれている)。top-kが3の場合は上位3つのトークンの中から選択されます。(temperatureを使用)", "com_endpoint_google_topp": "Top-p はモデルがトークンをどのように選択して出力するかを変更します。K(topKを参照)の確率の合計がtop-pの確率と等しくなるまでのトークンが選択されます。", - "com_endpoint_import": "インポート", "com_endpoint_instructions_assistants": "指示をオーバーライドする", "com_endpoint_instructions_assistants_placeholder": "アシスタントの指示を上書きします。これは、実行ごとに動作を変更する場合に便利です。", "com_endpoint_max_output_tokens": "最大出力トークン数", @@ -262,7 +261,6 @@ "com_nav_archive_name": "名前", "com_nav_archived_chats": "アーカイブされたチャット", "com_nav_archived_chats_empty": "アーカイブされたチャットはありません", - "com_nav_archived_chats_manage": "管理", "com_nav_at_command": "@-Command", "com_nav_at_command_description": "コマンド\"@\"でエンドポイント、モデル、プリセットを切り替える", "com_nav_audio_play_error": "オーディオの再生エラー: {{0}}", @@ -383,7 +381,6 @@ "com_nav_setting_speech": "スピーチ", "com_nav_settings": "設定", "com_nav_shared_links": "共有リンク", - "com_nav_shared_links_manage": "管理", "com_nav_show_code": "Code Interpreter を使用する際は常にコードを表示する", "com_nav_slash_command": "/-Command", "com_nav_slash_command_description": "コマンド\"/\"でキーボードでプロンプトを選択する", @@ -587,7 +584,7 @@ "com_ui_happy_birthday": "初めての誕生日です!", "com_ui_host": "ホスト", "com_ui_image_gen": "画像生成", - "com_ui_import_conversation": "インポート", + "com_ui_import": "インポート", "com_ui_import_conversation_error": "会話のインポート時にエラーが発生しました", "com_ui_import_conversation_file_type_error": "サポートされていないインポート形式です", "com_ui_import_conversation_info": "JSONファイルから会話をインポートする", @@ -719,4 +716,4 @@ "com_ui_zoom": "ズーム", "com_user_message": "あなた", "com_warning_resubmit_unsupported": "このエンドポイントではAIメッセージの再送信はサポートされていません" -} \ No newline at end of file +} diff --git a/client/src/locales/ko/translation.json b/client/src/locales/ko/translation.json index 61571435c3..46eda9f9de 100644 --- a/client/src/locales/ko/translation.json +++ b/client/src/locales/ko/translation.json @@ -178,7 +178,6 @@ "com_endpoint_google_temp": "높은 값 = 더 무작위, 낮은 값 = 더 집중적이고 결정적입니다. 이 값을 변경하거나 Top P 중 하나만 변경하는 것을 권장합니다.", "com_endpoint_google_topk": "Top-k는 모델이 출력에 사용할 토큰을 선택하는 방식을 변경합니다. top-k가 1인 경우 모델의 어휘 중 가장 확률이 높은 토큰이 선택됩니다(greedy decoding). top-k가 3인 경우 다음 토큰은 가장 확률이 높은 3개의 토큰 중에서 선택됩니다(temperature 사용).", "com_endpoint_google_topp": "Top-p는 모델이 출력에 사용할 토큰을 선택하는 방식을 변경합니다. 토큰은 가장 높은 확률부터 가장 낮은 확률까지 선택됩니다. 선택된 토큰의 확률의 합이 top-p 값과 같아질 때까지 선택됩니다.", - "com_endpoint_import": "가져오기", "com_endpoint_instructions_assistants": "에이전트 지침 재정의", "com_endpoint_instructions_assistants_placeholder": "어시스턴트의 지침을 재정의합니다. 이를 통해 실행마다 동작을 수정할 수 있습니다.", "com_endpoint_max_output_tokens": "최대 출력 토큰 수", @@ -262,7 +261,6 @@ "com_nav_archive_name": "이름", "com_nav_archived_chats": "아카이브된 채팅", "com_nav_archived_chats_empty": "아카이브된 채팅이 없습니다", - "com_nav_archived_chats_manage": "관리", "com_nav_at_command": "@ 명령어", "com_nav_at_command_description": "엔드포인트, 모델, 프리셋 등을 전환하는 \"@\" 명령어 토글", "com_nav_audio_play_error": "오디오 재생 오류: {{0}}", @@ -383,7 +381,6 @@ "com_nav_setting_speech": "음성", "com_nav_settings": "설정", "com_nav_shared_links": "공유 링크", - "com_nav_shared_links_manage": "관리", "com_nav_show_code": "코드 인터프리터 사용 시 항상 코드 표시", "com_nav_slash_command": "슬래시 명령어", "com_nav_slash_command_description": "키보드로 프롬프트를 선택하려면 \"/\" 명령어 토글", @@ -587,7 +584,7 @@ "com_ui_happy_birthday": "내 첫 생일이야!", "com_ui_host": "호스트", "com_ui_image_gen": "이미지 생성", - "com_ui_import_conversation": "가져오기", + "com_ui_import": "가져오기", "com_ui_import_conversation_error": "대화를 가져오는 동안 오류가 발생했습니다", "com_ui_import_conversation_file_type_error": "가져올 수 없는 파일 형식입니다", "com_ui_import_conversation_info": "JSON 파일에서 대화 가져오기", @@ -719,4 +716,4 @@ "com_ui_zoom": "확대/축소", "com_user_message": "당신", "com_warning_resubmit_unsupported": "이 엔드포인트에서는 AI 메시지 재전송이 지원되지 않습니다" -} \ No newline at end of file +} diff --git a/client/src/locales/nl/translation.json b/client/src/locales/nl/translation.json index 55d96bb096..f6c99423fb 100644 --- a/client/src/locales/nl/translation.json +++ b/client/src/locales/nl/translation.json @@ -93,7 +93,6 @@ "com_endpoint_google_temp": "Hogere waarden = meer willekeurig, terwijl lagere waarden = meer gericht en deterministisch. We raden aan dit of Top P te wijzigen, maar niet beide.", "com_endpoint_google_topk": "Top-k verandert hoe het model tokens selecteert voor uitvoer. Een top-k van 1 betekent dat het geselecteerde token het meest waarschijnlijk is van alle tokens in de vocabulaire van het model (ook wel 'greedy decoding' genoemd), terwijl een top-k van 3 betekent dat het volgende token wordt geselecteerd uit de 3 meest waarschijnlijke tokens (met behulp van temperatuur).", "com_endpoint_google_topp": "Top-p verandert hoe het model tokens selecteert voor uitvoer. Tokens worden geselecteerd van meest K (zie topK-parameter) waarschijnlijk tot minst waarschijnlijk totdat de som van hun kansen gelijk is aan de top-p-waarde.", - "com_endpoint_import": "Importeren", "com_endpoint_max_output_tokens": "Max. uitvoertokens", "com_endpoint_my_preset": "Mijn voorinstelling", "com_endpoint_no_presets": "Nog geen voorinstellingen, gebruik de instellingenknop om er een te maken", @@ -127,7 +126,6 @@ "com_nav_archive_name": "Naam", "com_nav_archived_chats": "Gearchiveerde chats", "com_nav_archived_chats_empty": "Geen gearchiveerde chats", - "com_nav_archived_chats_manage": "Beheren", "com_nav_auto_scroll": "Automatisch scrollen naar Nieuwste bij openen", "com_nav_balance": "Evenwicht", "com_nav_clear_all_chats": "Alle chats wissen", @@ -182,7 +180,6 @@ "com_nav_setting_general": "Algemeen", "com_nav_settings": "Instellingen", "com_nav_shared_links": "Gedeelde links", - "com_nav_shared_links_manage": "Beheren", "com_nav_theme": "Thema", "com_nav_theme_dark": "Donker", "com_nav_theme_light": "Licht", @@ -222,7 +219,7 @@ "com_ui_enter": "Invoeren", "com_ui_examples": "Voorbeelden", "com_ui_happy_birthday": "Het is mijn eerste verjaardag!", - "com_ui_import_conversation": "Importeren", + "com_ui_import": "Importeren", "com_ui_import_conversation_error": "Er is een fout opgetreden bij het importeren van je gesprekken", "com_ui_import_conversation_info": "Gesprekken importeren vanuit een JSON-bestand", "com_ui_import_conversation_success": "Gesprekken succesvol geïmporteerd", @@ -253,4 +250,4 @@ "com_ui_unarchive_error": "Kan conversatie niet uit archiveren", "com_ui_upload_success": "Bestand succesvol geüpload", "com_ui_use_prompt": "Gebruik prompt" -} \ No newline at end of file +} diff --git a/client/src/locales/pl/translation.json b/client/src/locales/pl/translation.json index e9ee3d2279..30483ba8b3 100644 --- a/client/src/locales/pl/translation.json +++ b/client/src/locales/pl/translation.json @@ -161,7 +161,6 @@ "com_endpoint_google_temp": "Wyższe wartości oznaczają większą losowość, natomiast niższe wartości prowadzą do bardziej skoncentrowanych i deterministycznych wyników. Zalecamy dostosowanie tej wartości lub Top P, ale nie obu jednocześnie.", "com_endpoint_google_topk": "Top-k wpływa na sposób, w jaki model wybiera tokeny do wygenerowania odpowiedzi. Top-k 1 oznacza, że wybrany token jest najbardziej prawdopodobny spośród wszystkich tokenów w słowniku modelu (nazywane też dekodowaniem zachłannym), podczas gdy top-k 3 oznacza, że następny token jest wybierany spośród 3 najbardziej prawdopodobnych tokenów (z uwzględnieniem temperatury).", "com_endpoint_google_topp": "Top-p wpływa na sposób, w jaki model wybiera tokeny do wygenerowania odpowiedzi. Tokeny są wybierane od najbardziej prawdopodobnych do najmniej, aż suma ich prawdopodobieństw osiągnie wartość top-p.", - "com_endpoint_import": "Importuj", "com_endpoint_instructions_assistants": "Nadpisz instrukcje", "com_endpoint_max_output_tokens": "Maksymalna liczba tokenów wyjściowych", "com_endpoint_message": "Wiadomość", @@ -238,7 +237,6 @@ "com_nav_archive_name": "Nazwa", "com_nav_archived_chats": "Zarchiwizowane rozmowy", "com_nav_archived_chats_empty": "Nie masz żadnych zarchiwizowanych rozmów.", - "com_nav_archived_chats_manage": "Zarządzaj", "com_nav_at_command": "Polecenie @", "com_nav_at_command_description": "Przełącz polecenie \"@\" do przełączania punktów końcowych, modeli, presetów, itp.", "com_nav_audio_play_error": "Błąd odtwarzania audio: {0}", @@ -355,7 +353,6 @@ "com_nav_setting_speech": "Mowa", "com_nav_settings": "Ustawienia", "com_nav_shared_links": "Linki udostępnione", - "com_nav_shared_links_manage": "Beheren", "com_nav_show_code": "Zawsze pokazuj kod podczas używania interpretera kodu", "com_nav_show_thinking": "Domyślnie otwieraj rozwijane menu myślenia", "com_nav_slash_command": "Polecenie /", @@ -565,7 +562,7 @@ "com_ui_hide_qr": "Ukryj kod QR", "com_ui_host": "Host", "com_ui_image_gen": "Generowanie obrazu", - "com_ui_import_conversation": "Importuj", + "com_ui_import": "Importuj", "com_ui_import_conversation_error": "Wystąpił błąd podczas importowania konwersacji", "com_ui_import_conversation_file_type_error": "Nieobsługiwany typ importu", "com_ui_import_conversation_info": "Importuj konwersacje z pliku JSON", @@ -707,4 +704,4 @@ "com_ui_zoom": "Powiększ", "com_user_message": "Ty", "com_warning_resubmit_unsupported": "Ponowne przesyłanie wiadomości AI nie jest obsługiwane dla tego punktu końcowego." -} \ No newline at end of file +} diff --git a/client/src/locales/ru/translation.json b/client/src/locales/ru/translation.json index 9580d54f54..ee5e4d0e29 100644 --- a/client/src/locales/ru/translation.json +++ b/client/src/locales/ru/translation.json @@ -178,7 +178,6 @@ "com_endpoint_google_temp": "Более высокие значения = более случайные результаты, более низкие значения = более фокусированные и детерминированные результаты. Мы рекомендуем изменять это или Top P, но не оба значения одновременно.", "com_endpoint_google_topk": "Top-k изменяет то, как модель выбирает токены для вывода. Top-k равное 1 означает, что выбирается наиболее вероятный токен из всего словаря модели (так называемое жадное декодирование), а Top-k равное 3 означает, что следующий токен выбирается из трех наиболее вероятных токенов (с использованием температуры).", "com_endpoint_google_topp": "Top-p изменяет то, как модель выбирает токены для вывода. Токены выбираются из наиболее вероятных K (см. параметр topK) до наименее вероятных, пока сумма их вероятностей не достигнет значения top-p.", - "com_endpoint_import": "Импорт", "com_endpoint_instructions_assistants": "Инструкции для ассистентов", "com_endpoint_instructions_assistants_placeholder": "Переопределяет инструкции для ассистента. Это полезно для изменения поведения для отдельного запуска.", "com_endpoint_max_output_tokens": "Максимальное количество выводимых токенов", @@ -262,7 +261,6 @@ "com_nav_archive_name": "Имя", "com_nav_archived_chats": "Архивированные чаты", "com_nav_archived_chats_empty": "У вас нет архивированных чатов.", - "com_nav_archived_chats_manage": "Управление", "com_nav_at_command": "@-команда", "com_nav_at_command_description": "Переключение команды \"@\" для выбора эндпоинтов, моделей, пресетов и др.", "com_nav_audio_play_error": "Ошибка воспроизведения аудио: {{0}}", @@ -383,7 +381,6 @@ "com_nav_setting_speech": "Голос", "com_nav_settings": "Настройки", "com_nav_shared_links": "Связываемые ссылки", - "com_nav_shared_links_manage": "Управление", "com_nav_show_code": "Всегда показывать код при использовании интерпретатора", "com_nav_slash_command": "/-Команда", "com_nav_slash_command_description": "Вызов командной строки клавишей '/' для выбора промта с клавиатуры", @@ -587,7 +584,7 @@ "com_ui_happy_birthday": "Это мой первый день рождения!", "com_ui_host": "Хост", "com_ui_image_gen": "Генератор изображений", - "com_ui_import_conversation": "Импортировать", + "com_ui_import": "Импортировать", "com_ui_import_conversation_error": "При импорте бесед произошла ошибка", "com_ui_import_conversation_file_type_error": "Неподдерживаемый тип импорта", "com_ui_import_conversation_info": "Импортировать беседы из файла JSON", @@ -719,4 +716,4 @@ "com_ui_zoom": "Масштаб", "com_user_message": "Вы", "com_warning_resubmit_unsupported": "Повторная отправка сообщения ИИ не поддерживается для данной конечной точки" -} \ No newline at end of file +} diff --git a/client/src/locales/sv/translation.json b/client/src/locales/sv/translation.json index 96e37de09e..26c8cc0c55 100644 --- a/client/src/locales/sv/translation.json +++ b/client/src/locales/sv/translation.json @@ -82,7 +82,6 @@ "com_endpoint_google_temp": "Högre värden = mer slumpmässigt, medan lägre värden = mer fokuserat och bestämt. Vi rekommenderar att ändra detta eller Top P men inte båda.", "com_endpoint_google_topk": "Top-k ändrar hur modellen väljer tokens för utdata. Ett top-k av 1 innebär att den valda token är den mest sannolika bland alla tokens i modellens vokabulär (kallas också girig avkodning), medan ett top-k av 3 innebär att nästa token väljs bland de 3 mest sannolika tokens (med temperatur).", "com_endpoint_google_topp": "Top-p ändrar hur modellen väljer tokens för utdata. Tokens väljs från de mest K (se topK-parameter) sannolika till de minst tills summan av deras sannolikheter når top-p-värdet.", - "com_endpoint_import": "Importera", "com_endpoint_max_output_tokens": "Max utdatatokens", "com_endpoint_my_preset": "Min förinställning", "com_endpoint_no_presets": "Ingen förinställning ännu", @@ -115,7 +114,6 @@ "com_nav_archive_name": "Namn", "com_nav_archived_chats": "Arkiverade chattar", "com_nav_archived_chats_empty": "Du har inga arkiverade chattar.", - "com_nav_archived_chats_manage": "Hantera", "com_nav_balance": "Balans", "com_nav_clear_all_chats": "Rensa alla chattar", "com_nav_clear_conversation": "Rensa konversationer", @@ -169,7 +167,6 @@ "com_nav_setting_general": "Allmänt", "com_nav_settings": "Inställningar", "com_nav_shared_links": "Delade länkar", - "com_nav_shared_links_manage": "Hantera", "com_nav_theme": "Tema", "com_nav_theme_dark": "Mörkt", "com_nav_theme_light": "Ljust", @@ -209,7 +206,7 @@ "com_ui_enter": "Ange", "com_ui_examples": "Exempel", "com_ui_happy_birthday": "Det är min första födelsedag!", - "com_ui_import_conversation": "Importera", + "com_ui_import": "Importera", "com_ui_import_conversation_error": "Det uppstod ett fel vid import av dina konversationer", "com_ui_import_conversation_info": "Importera konversationer från en JSON-fil", "com_ui_import_conversation_success": "Konversationer har importerats framgångsrikt", @@ -239,4 +236,4 @@ "com_ui_unarchive_error": "Kunde inte avarkivera chatt", "com_ui_upload_success": "Uppladdningen av filen lyckades", "com_ui_use_prompt": "Använd prompt" -} \ No newline at end of file +} diff --git a/client/src/locales/tr/translation.json b/client/src/locales/tr/translation.json index cbf93c3474..a75a5422ea 100644 --- a/client/src/locales/tr/translation.json +++ b/client/src/locales/tr/translation.json @@ -179,7 +179,6 @@ "com_endpoint_google_temp": "Yüksek değerler = daha rastgele, düşük değerler = daha odaklı ve belirleyici. Bu parametre ile Olasılık Kütüphanesini değiştirmeyi öneririz (ikisini birden değiştirmemek).", "com_endpoint_google_topk": "Top-k, modelin çıktı için token seçimini nasıl yaptığını değiştirir. 1 olan bir top-k, modelin kelime haznesindeki en olası tokenin seçildiği (açgözlü kod çözme olarak da adlandırılır) anlamına gelirken, 3 olan bir top-k, bir sonraki tokenin en olası üç token arasından (sıcaklık kullanılarak) seçileceği anlamına gelir.", "com_endpoint_google_topp": "Olasılık Kütüphanesi, modelin çıktı için token seçme şeklini değiştirir. Tokenler, en olasılıktan (bkz. topK parametresi) en az olasıya kadar seçilir ve olasılıkları toplamı, top-p değerine eşit olana kadar devam eder.", - "com_endpoint_import": "İthal et", "com_endpoint_instructions_assistants": "Talimatları Geçersiz Kıl", "com_endpoint_instructions_assistants_placeholder": "Asistanın talimatlarını geçersiz kılar. Bu, davranışı tek tek çalışma bazında değiştirmek için yararlıdır.", "com_endpoint_max_output_tokens": "Maksimum Çıktı Tokenleri", @@ -265,7 +264,6 @@ "com_nav_archive_name": "Ad", "com_nav_archived_chats": "Arşivlenmiş sohbetler", "com_nav_archived_chats_empty": "Arşivlenmiş konuşmanız yok.", - "com_nav_archived_chats_manage": "Yönet", "com_nav_at_command": "@-Komutu", "com_nav_at_command_description": "Uç noktaları, modelleri, ön ayarları vb. değiştirmek için \"@\" komutunu aç/kapat", "com_nav_audio_play_error": "Ses oynatma hatası: {{0}}", @@ -388,7 +386,6 @@ "com_nav_setting_speech": "Konuşma", "com_nav_settings": "Ayarlar", "com_nav_shared_links": "Paylaşılan bağlantılar", - "com_nav_shared_links_manage": "Yönet", "com_nav_show_code": "Kod yorumlayıcı kullanırken her zaman kodu göster", "com_nav_show_thinking": "Düşünme Açılır Menülerini Varsayılan Olarak Aç", "com_nav_slash_command": "/-Komutu", @@ -605,7 +602,7 @@ "com_ui_hide_qr": "QR Kodunu Gizle", "com_ui_host": "Host", "com_ui_image_gen": "Görüntü Oluştur", - "com_ui_import_conversation": "İçe Aktar", + "com_ui_import": "İçe Aktar", "com_ui_import_conversation_error": "Konuşmalarınızı içe aktarma sırasında bir hata oluştu", "com_ui_import_conversation_file_type_error": "Desteklenmeyen içe aktarma türü", "com_ui_import_conversation_info": "JSON dosyasından konuşmaları içe aktar", @@ -750,4 +747,4 @@ "com_ui_zoom": "Yakınlaştır", "com_user_message": "Sen", "com_warning_resubmit_unsupported": "Bu uç nokta için yapay zeka mesajını yeniden gönderme desteklenmiyor." -} \ No newline at end of file +} diff --git a/client/src/locales/vi/translation.json b/client/src/locales/vi/translation.json index b7b460d1ea..57f34eef0b 100644 --- a/client/src/locales/vi/translation.json +++ b/client/src/locales/vi/translation.json @@ -84,7 +84,6 @@ "com_endpoint_google_temp": "Giá trị cao = ngẫu nhiên hơn, trong khi giá trị thấp = tập trung và xác định hơn. Chúng tôi khuyến nghị thay đổi giá trị này hoặc Top P nhưng không phải cả hai.", "com_endpoint_google_topk": "Top-k thay đổi cách mô hình chọn mã thông báo để xuất. Top-k là 1 có nghĩa là mã thông báo được chọn là phổ biến nhất trong tất cả các mã thông báo trong bảng từ vựng của mô hình (còn được gọi là giải mã tham lam), trong khi top-k là 3 có nghĩa là mã thông báo tiếp theo được chọn từ giữa 3 mã thông báo phổ biến nhất (sử dụng nhiệt độ).", "com_endpoint_google_topp": "Top-p thay đổi cách mô hình chọn mã thông báo để xuất. Mã thông báo được chọn từ căn cứ có xác suất cao nhất đến thấp nhất cho đến khi tổng xác suất của chúng bằng giá trị top-p.", - "com_endpoint_import": "Nhập", "com_endpoint_max_output_tokens": "Số mã thông báo tối đa", "com_endpoint_my_preset": "Đặt sẵn của tôi", "com_endpoint_no_presets": "Chưa có đặt sẵn", @@ -167,7 +166,6 @@ "com_nav_setting_general": "Chung", "com_nav_settings": "Cài đặt", "com_nav_shared_links": "Liên kết được chia sẻ", - "com_nav_shared_links_manage": "Quản l", "com_nav_theme": "Chủ đề", "com_nav_theme_dark": "Tối", "com_nav_theme_light": "Sáng", @@ -207,7 +205,7 @@ "com_ui_enter": "Nhập", "com_ui_examples": "Ví dụ", "com_ui_happy_birthday": "Đây là sinh nhật đầu tiên của tôi!", - "com_ui_import_conversation": "Nhập khẩu", + "com_ui_import": "Nhập khẩu", "com_ui_import_conversation_error": "Đã xảy ra lỗi khi nhập khẩu cuộc trò chuyện của bạn", "com_ui_import_conversation_info": "Nhập khẩu cuộc trò chuyện từ một tệp JSON", "com_ui_import_conversation_success": "Đã nhập khẩu cuộc trò chuyện thành công", @@ -237,4 +235,4 @@ "com_ui_unarchive_error": "Không thể bỏ lưu trữ cuộc trò chuyện", "com_ui_upload_success": "Tải tệp thành công", "com_ui_use_prompt": "Sử dụng gợi ý" -} \ No newline at end of file +} diff --git a/client/src/locales/zh-Hant/translation.json b/client/src/locales/zh-Hant/translation.json index 1913f05f56..f46d94af2d 100644 --- a/client/src/locales/zh-Hant/translation.json +++ b/client/src/locales/zh-Hant/translation.json @@ -178,7 +178,6 @@ "com_endpoint_google_temp": "較高的值表示更隨機,而較低的值表示更集中和確定。我們建議修改這個或 Top P,但不建議兩者都修改。", "com_endpoint_google_topk": "Top-k 調整模型如何選取輸出的 token。當 Top-k 設為 1 時,模型會選取在其詞彙庫中機率最高的 token 進行輸出(這也被稱為貪婪解碼)。相對地,當 Top-k 設為 3 時,模型會從機率最高的三個 token 中選取下一個輸出 token(這會涉及到所謂的「溫度」調整)", "com_endpoint_google_topp": "Top-p 調整模型在輸出 token 時的選擇機制。從最可能的 K(見 topK 參數)開始選擇 token,直到它們的機率之和達到 top-p 值。", - "com_endpoint_import": "匯入", "com_endpoint_instructions_assistants": "覆寫提示指令", "com_endpoint_instructions_assistants_placeholder": "覆寫助理的提示指令。這對於在每次執行時修改行為很有用。", "com_endpoint_max_output_tokens": "最大輸出 token 數", @@ -262,7 +261,6 @@ "com_nav_archive_name": "名稱", "com_nav_archived_chats": "封存的對話", "com_nav_archived_chats_empty": "您沒有任何封存的對話。", - "com_nav_archived_chats_manage": "管理", "com_nav_at_command": "@-指令", "com_nav_at_command_description": "使用「@」指令切換端點、模型和預設值等", "com_nav_audio_play_error": "播放音訊時發生錯誤:{{0}}", @@ -383,7 +381,6 @@ "com_nav_setting_speech": "語音", "com_nav_settings": "設定", "com_nav_shared_links": "共享連結", - "com_nav_shared_links_manage": "管理", "com_nav_show_code": "一律顯示使用程式碼解譯器時的程式碼", "com_nav_slash_command": "/指令", "com_nav_slash_command_description": "使用鍵盤按下 \"/\" 快速選擇提示詞", @@ -587,7 +584,7 @@ "com_ui_happy_birthday": "這是我的第一個生日!", "com_ui_host": "主機", "com_ui_image_gen": "影像生成", - "com_ui_import_conversation": "匯入", + "com_ui_import": "匯入", "com_ui_import_conversation_error": "匯入對話時發生錯誤", "com_ui_import_conversation_file_type_error": "不支援的匯入檔案類型", "com_ui_import_conversation_info": "從 JSON 文件匯入對話", @@ -719,4 +716,4 @@ "com_ui_zoom": "縮放", "com_user_message": "您", "com_warning_resubmit_unsupported": "此端點不支援重新送出 AI 訊息。" -} \ No newline at end of file +} diff --git a/client/src/routes/Layouts/Startup.tsx b/client/src/routes/Layouts/Startup.tsx index b00c8bcdc0..9c9e0952dd 100644 --- a/client/src/routes/Layouts/Startup.tsx +++ b/client/src/routes/Layouts/Startup.tsx @@ -10,6 +10,7 @@ const headerMap: Record = { '/register': 'com_auth_create_account', '/forgot-password': 'com_auth_reset_password', '/reset-password': 'com_auth_reset_password', + '/login/2fa': 'com_auth_verify_your_identity', }; export default function StartupLayout({ isAuthenticated }: { isAuthenticated?: boolean }) { diff --git a/client/src/routes/index.tsx b/client/src/routes/index.tsx index 3cdfe3c46e..c8bc382a42 100644 --- a/client/src/routes/index.tsx +++ b/client/src/routes/index.tsx @@ -6,6 +6,7 @@ import { ResetPassword, VerifyEmail, ApiErrorWatcher, + TwoFactorScreen, } from '~/components/Auth'; import { AuthContextProvider } from '~/hooks/AuthContext'; import RouteErrorBoundary from './RouteErrorBoundary'; @@ -66,6 +67,10 @@ export const router = createBrowserRouter([ path: 'login', element: , }, + { + path: 'login/2fa', + element: , + }, ], }, dashboardRoutes, diff --git a/package-lock.json b/package-lock.json index dfae230c57..802cedd19e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1053,6 +1053,7 @@ "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.0.0", "@radix-ui/react-popover": "^1.0.7", + "@radix-ui/react-progress": "^1.1.2", "@radix-ui/react-radio-group": "^1.1.3", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-separator": "^1.0.3", @@ -1074,6 +1075,7 @@ "html-to-image": "^1.11.11", "i18next": "^24.2.2", "i18next-browser-languagedetector": "^8.0.3", + "input-otp": "^1.4.2", "js-cookie": "^3.0.5", "librechat-data-provider": "*", "lodash": "^4.17.21", @@ -13744,6 +13746,101 @@ } } }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.2.tgz", + "integrity": "sha512-u1IgJFQ4zNAUTjGdDL5dcl/U8ntOR6jsnhxKb5RKp5Ozwl88xKR9EqRZOe/Mk8tnx0x5tNUe2F+MzsyjqMg0MA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-primitive": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz", + "integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", + "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-radio-group": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.1.3.tgz", @@ -23618,6 +23715,16 @@ "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.3.tgz", "integrity": "sha512-qlD8YNDqyTKTyuITrDOffsl6Tdhv+UC4hcdAVuQsK4IMQ99nSgd1MIA/Q+jQYoh9r3hVUXhYh7urSRmXPkW04g==" }, + "node_modules/input-otp": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.4.2.tgz", + "integrity": "sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", diff --git a/packages/data-provider/src/api-endpoints.ts b/packages/data-provider/src/api-endpoints.ts index 27cc221d72..142ed9ba20 100644 --- a/packages/data-provider/src/api-endpoints.ts +++ b/packages/data-provider/src/api-endpoints.ts @@ -237,3 +237,11 @@ export const addTagToConversation = (conversationId: string) => export const userTerms = () => '/api/user/terms'; export const acceptUserTerms = () => '/api/user/terms/accept'; export const banner = () => '/api/banner'; + +// Two-Factor Endpoints +export const enableTwoFactor = () => '/api/auth/2fa/enable'; +export const verifyTwoFactor = () => '/api/auth/2fa/verify'; +export const confirmTwoFactor = () => '/api/auth/2fa/confirm'; +export const disableTwoFactor = () => '/api/auth/2fa/disable'; +export const regenerateBackupCodes = () => '/api/auth/2fa/backup/regenerate'; +export const verifyTwoFactorTemp = () => '/api/auth/2fa/verify-temp'; \ No newline at end of file diff --git a/packages/data-provider/src/data-service.ts b/packages/data-provider/src/data-service.ts index 5af00fdcb9..78700e7419 100644 --- a/packages/data-provider/src/data-service.ts +++ b/packages/data-provider/src/data-service.ts @@ -774,3 +774,33 @@ export function acceptTerms(): Promise { export function getBanner(): Promise { return request.get(endpoints.banner()); } + +export function enableTwoFactor(): Promise { + return request.get(endpoints.enableTwoFactor()); +} + +export function verifyTwoFactor( + payload: t.TVerify2FARequest, +): Promise { + return request.post(endpoints.verifyTwoFactor(), payload); +} + +export function confirmTwoFactor( + payload: t.TVerify2FARequest, +): Promise { + return request.post(endpoints.confirmTwoFactor(), payload); +} + +export function disableTwoFactor(): Promise { + return request.post(endpoints.disableTwoFactor()); +} + +export function regenerateBackupCodes(): Promise { + return request.post(endpoints.regenerateBackupCodes()); +} + +export function verifyTwoFactorTemp( + payload: t.TVerify2FATempRequest, +): Promise { + return request.post(endpoints.verifyTwoFactorTemp(), payload); +} \ No newline at end of file diff --git a/packages/data-provider/src/keys.ts b/packages/data-provider/src/keys.ts index c1e0c24557..fd5ee95087 100644 --- a/packages/data-provider/src/keys.ts +++ b/packages/data-provider/src/keys.ts @@ -67,4 +67,6 @@ export enum MutationKeys { deleteAgentAction = 'deleteAgentAction', deleteUser = 'deleteUser', updateRole = 'updateRole', + enableTwoFactor = 'enableTwoFactor', + verifyTwoFactor = 'verifyTwoFactor', } diff --git a/packages/data-provider/src/request.ts b/packages/data-provider/src/request.ts index 740e9cbe6c..e4dd53847b 100644 --- a/packages/data-provider/src/request.ts +++ b/packages/data-provider/src/request.ts @@ -91,6 +91,9 @@ axios.interceptors.response.use( return Promise.reject(error); } + if (originalRequest.url?.includes('/api/auth/2fa') === true) { + return Promise.reject(error); + } if (originalRequest.url?.includes('/api/auth/logout') === true) { return Promise.reject(error); } diff --git a/packages/data-provider/src/types.ts b/packages/data-provider/src/types.ts index bf31a48cc0..6771901267 100644 --- a/packages/data-provider/src/types.ts +++ b/packages/data-provider/src/types.ts @@ -100,6 +100,12 @@ export type TError = { }; }; +export type TBackupCode = { + codeHash: string; + used: boolean; + usedAt: Date | null; +}; + export type TUser = { id: string; username: string; @@ -109,6 +115,7 @@ export type TUser = { role: string; provider: string; plugins?: string[]; + backupCodes?: TBackupCode[]; createdAt: string; updatedAt: string; }; @@ -285,11 +292,61 @@ export type TRegisterUser = { export type TLoginUser = { email: string; password: string; + token?: string; + backupCode?: string; }; export type TLoginResponse = { - token: string; - user: TUser; + token?: string; + user?: TUser; + twoFAPending?: boolean; + tempToken?: string; +}; + +export type TEnable2FAResponse = { + otpauthUrl: string; + backupCodes: string[]; + message?: string; +}; + +export type TVerify2FARequest = { + token?: string; + backupCode?: string; +}; + +export type TVerify2FAResponse = { + message: string; +}; + +/** + * For verifying 2FA during login with a temporary token. + */ +export type TVerify2FATempRequest = { + tempToken: string; + token?: string; + backupCode?: string; +}; + +export type TVerify2FATempResponse = { + token?: string; + user?: TUser; + message?: string; +}; + +/** + * Response from disabling 2FA. + */ +export type TDisable2FAResponse = { + message: string; +}; + +/** + * Response from regenerating backup codes. + */ +export type TRegenerateBackupCodesResponse = { + message: string; + backupCodes: string[]; + backupCodesHash: string[]; }; export type TRequestPasswordReset = { From 964a74c73bb76c04da63a146190bf280b3c07d5e Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Mon, 17 Feb 2025 19:37:03 -0500 Subject: [PATCH 14/23] =?UTF-8?q?=F0=9F=9B=A0=20refactor:=20Ensure=20File?= =?UTF-8?q?=20Deletions,=20File=20Naming,=20and=20Agent=20Resource=20Updat?= =?UTF-8?q?es=20(#5928)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: Improve error logging for file upload and processing functions to prevent verbosity * refactor: Add uploads directory to Docker Compose to persist file uploads * refactor: `addAgentResourceFile` to handle edge case of non-existing `tool_resource` array * refactor: Remove version specification from deploy-compose.yml * refactor: Prefix filenames with file_id to ensure uniqueness in file uploads * refactor: Enhance error handling in deleteVectors to log warnings for non-404 errors * refactor: Limit file search results to top 5 based on relevance score * 🌍 i18n: Update translation.json with latest translations --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- api/app/clients/tools/util/fileSearch.js | 3 +- api/models/Agent.js | 16 +- api/models/Agent.spec.js | 160 ++++++++++++++++++++ api/server/services/Files/Code/crud.js | 7 +- api/server/services/Files/Code/process.js | 11 +- api/server/services/Files/VectorDB/crud.js | 9 +- api/server/services/Files/process.js | 7 +- client/src/locales/ar/translation.json | 3 +- client/src/locales/de/translation.json | 3 +- client/src/locales/en/translation.json | 80 +++++----- client/src/locales/es/translation.json | 3 +- client/src/locales/et/translation.json | 5 +- client/src/locales/fi/translation.json | 3 +- client/src/locales/fr/translation.json | 3 +- client/src/locales/he/translation.json | 3 +- client/src/locales/id/translation.json | 3 +- client/src/locales/it/translation.json | 3 +- client/src/locales/ja/translation.json | 3 +- client/src/locales/ko/translation.json | 3 +- client/src/locales/nl/translation.json | 3 +- client/src/locales/pl/translation.json | 3 +- client/src/locales/pt-BR/translation.json | 4 - client/src/locales/pt-PT/translation.json | 4 - client/src/locales/ru/translation.json | 3 +- client/src/locales/sv/translation.json | 3 +- client/src/locales/tr/translation.json | 3 +- client/src/locales/vi/translation.json | 3 +- client/src/locales/zh-Hans/translation.json | 4 - client/src/locales/zh-Hant/translation.json | 3 +- deploy-compose.yml | 2 +- docker-compose.yml | 1 + 31 files changed, 262 insertions(+), 102 deletions(-) create mode 100644 api/models/Agent.spec.js diff --git a/api/app/clients/tools/util/fileSearch.js b/api/app/clients/tools/util/fileSearch.js index 23ba58bb5a..c48adc2eb4 100644 --- a/api/app/clients/tools/util/fileSearch.js +++ b/api/app/clients/tools/util/fileSearch.js @@ -112,7 +112,8 @@ const createFileSearchTool = async ({ req, files, entity_id }) => { relevanceScore, })), ) - .sort((a, b) => b.relevanceScore - a.relevanceScore); + .sort((a, b) => b.relevanceScore - a.relevanceScore) + .slice(0, 5); const formattedString = formattedResults .map( diff --git a/api/models/Agent.js b/api/models/Agent.js index 6fa00f56bc..6ea203113c 100644 --- a/api/models/Agent.js +++ b/api/models/Agent.js @@ -97,11 +97,22 @@ const updateAgent = async (searchParameter, updateData) => { const addAgentResourceFile = async ({ agent_id, tool_resource, file_id }) => { const searchParameter = { id: agent_id }; - // build the update to push or create the file ids set const fileIdsPath = `tool_resources.${tool_resource}.file_ids`; + + await Agent.updateOne( + { + id: agent_id, + [`${fileIdsPath}`]: { $exists: false }, + }, + { + $set: { + [`${fileIdsPath}`]: [], + }, + }, + ); + const updateData = { $addToSet: { [fileIdsPath]: file_id } }; - // return the updated agent or throw if no agent matches const updatedAgent = await updateAgent(searchParameter, updateData); if (updatedAgent) { return updatedAgent; @@ -290,6 +301,7 @@ const updateAgentProjects = async ({ user, agentId, projectIds, removeProjectIds }; module.exports = { + Agent, getAgent, loadAgent, createAgent, diff --git a/api/models/Agent.spec.js b/api/models/Agent.spec.js new file mode 100644 index 0000000000..769eda2bb7 --- /dev/null +++ b/api/models/Agent.spec.js @@ -0,0 +1,160 @@ +const mongoose = require('mongoose'); +const { v4: uuidv4 } = require('uuid'); +const { MongoMemoryServer } = require('mongodb-memory-server'); +const { Agent, addAgentResourceFile, removeAgentResourceFiles } = require('./Agent'); + +describe('Agent Resource File Operations', () => { + let mongoServer; + + beforeAll(async () => { + mongoServer = await MongoMemoryServer.create(); + const mongoUri = mongoServer.getUri(); + await mongoose.connect(mongoUri); + }); + + afterAll(async () => { + await mongoose.disconnect(); + await mongoServer.stop(); + }); + + beforeEach(async () => { + await Agent.deleteMany({}); + }); + + const createBasicAgent = async () => { + const agentId = `agent_${uuidv4()}`; + const agent = await Agent.create({ + id: agentId, + name: 'Test Agent', + provider: 'test', + model: 'test-model', + author: new mongoose.Types.ObjectId(), + }); + return agent; + }; + + test('should handle concurrent file additions', async () => { + const agent = await createBasicAgent(); + const fileIds = Array.from({ length: 10 }, () => uuidv4()); + + // Concurrent additions + const additionPromises = fileIds.map((fileId) => + addAgentResourceFile({ + agent_id: agent.id, + tool_resource: 'test_tool', + file_id: fileId, + }), + ); + + await Promise.all(additionPromises); + + const updatedAgent = await Agent.findOne({ id: agent.id }); + expect(updatedAgent.tool_resources.test_tool.file_ids).toBeDefined(); + expect(updatedAgent.tool_resources.test_tool.file_ids).toHaveLength(10); + expect(new Set(updatedAgent.tool_resources.test_tool.file_ids).size).toBe(10); + }); + + test('should handle concurrent additions and removals', async () => { + const agent = await createBasicAgent(); + const initialFileIds = Array.from({ length: 5 }, () => uuidv4()); + + await Promise.all( + initialFileIds.map((fileId) => + addAgentResourceFile({ + agent_id: agent.id, + tool_resource: 'test_tool', + file_id: fileId, + }), + ), + ); + + const newFileIds = Array.from({ length: 5 }, () => uuidv4()); + const operations = [ + ...newFileIds.map((fileId) => + addAgentResourceFile({ + agent_id: agent.id, + tool_resource: 'test_tool', + file_id: fileId, + }), + ), + ...initialFileIds.map((fileId) => + removeAgentResourceFiles({ + agent_id: agent.id, + files: [{ tool_resource: 'test_tool', file_id: fileId }], + }), + ), + ]; + + await Promise.all(operations); + + const updatedAgent = await Agent.findOne({ id: agent.id }); + expect(updatedAgent.tool_resources.test_tool.file_ids).toBeDefined(); + expect(updatedAgent.tool_resources.test_tool.file_ids).toHaveLength(5); + }); + + test('should initialize array when adding to non-existent tool resource', async () => { + const agent = await createBasicAgent(); + const fileId = uuidv4(); + + const updatedAgent = await addAgentResourceFile({ + agent_id: agent.id, + tool_resource: 'new_tool', + file_id: fileId, + }); + + expect(updatedAgent.tool_resources.new_tool.file_ids).toBeDefined(); + expect(updatedAgent.tool_resources.new_tool.file_ids).toHaveLength(1); + expect(updatedAgent.tool_resources.new_tool.file_ids[0]).toBe(fileId); + }); + + test('should handle rapid sequential modifications to same tool resource', async () => { + const agent = await createBasicAgent(); + const fileId = uuidv4(); + + for (let i = 0; i < 10; i++) { + await addAgentResourceFile({ + agent_id: agent.id, + tool_resource: 'test_tool', + file_id: `${fileId}_${i}`, + }); + + if (i % 2 === 0) { + await removeAgentResourceFiles({ + agent_id: agent.id, + files: [{ tool_resource: 'test_tool', file_id: `${fileId}_${i}` }], + }); + } + } + + const updatedAgent = await Agent.findOne({ id: agent.id }); + expect(updatedAgent.tool_resources.test_tool.file_ids).toBeDefined(); + expect(Array.isArray(updatedAgent.tool_resources.test_tool.file_ids)).toBe(true); + }); + + test('should handle multiple tool resources concurrently', async () => { + const agent = await createBasicAgent(); + const toolResources = ['tool1', 'tool2', 'tool3']; + const operations = []; + + toolResources.forEach((tool) => { + const fileIds = Array.from({ length: 5 }, () => uuidv4()); + fileIds.forEach((fileId) => { + operations.push( + addAgentResourceFile({ + agent_id: agent.id, + tool_resource: tool, + file_id: fileId, + }), + ); + }); + }); + + await Promise.all(operations); + + const updatedAgent = await Agent.findOne({ id: agent.id }); + toolResources.forEach((tool) => { + expect(updatedAgent.tool_resources[tool].file_ids).toBeDefined(); + expect(updatedAgent.tool_resources[tool].file_ids).toHaveLength(5); + }); + }); +}); diff --git a/api/server/services/Files/Code/crud.js b/api/server/services/Files/Code/crud.js index 076a4d9f13..7b26093d62 100644 --- a/api/server/services/Files/Code/crud.js +++ b/api/server/services/Files/Code/crud.js @@ -2,6 +2,7 @@ const axios = require('axios'); const FormData = require('form-data'); const { getCodeBaseURL } = require('@librechat/agents'); +const { logAxiosError } = require('~/utils'); const MAX_FILE_SIZE = 150 * 1024 * 1024; @@ -78,7 +79,11 @@ async function uploadCodeEnvFile({ req, stream, filename, apiKey, entity_id = '' return `${fileIdentifier}?entity_id=${entity_id}`; } catch (error) { - throw new Error(`Error uploading file: ${error.message}`); + logAxiosError({ + message: `Error uploading code environment file: ${error.message}`, + error, + }); + throw new Error(`Error uploading code environment file: ${error.message}`); } } diff --git a/api/server/services/Files/Code/process.js b/api/server/services/Files/Code/process.js index 2a941a4647..ce8acf4ad3 100644 --- a/api/server/services/Files/Code/process.js +++ b/api/server/services/Files/Code/process.js @@ -12,6 +12,7 @@ const { const { getStrategyFunctions } = require('~/server/services/Files/strategies'); const { convertImage } = require('~/server/services/Files/images/convert'); const { createFile, getFiles, updateFile } = require('~/models/File'); +const { logAxiosError } = require('~/utils'); const { logger } = require('~/config'); /** @@ -85,7 +86,10 @@ const processCodeOutput = async ({ /** Note: `messageId` & `toolCallId` are not part of file DB schema; message object records associated file ID */ return Object.assign(file, { messageId, toolCallId }); } catch (error) { - logger.error('Error downloading file:', error); + logAxiosError({ + message: 'Error downloading code environment file', + error, + }); } }; @@ -135,7 +139,10 @@ async function getSessionInfo(fileIdentifier, apiKey) { return response.data.find((file) => file.name.startsWith(path))?.lastModified; } catch (error) { - logger.error(`Error fetching session info: ${error.message}`, error); + logAxiosError({ + message: `Error fetching session info: ${error.message}`, + error, + }); return null; } } diff --git a/api/server/services/Files/VectorDB/crud.js b/api/server/services/Files/VectorDB/crud.js index d290eea4b1..37a1e81487 100644 --- a/api/server/services/Files/VectorDB/crud.js +++ b/api/server/services/Files/VectorDB/crud.js @@ -37,7 +37,14 @@ const deleteVectors = async (req, file) => { error, message: 'Error deleting vectors', }); - throw new Error(error.message || 'An error occurred during file deletion.'); + if ( + error.response && + error.response.status !== 404 && + (error.response.status < 200 || error.response.status >= 300) + ) { + logger.warn('Error deleting vectors, file will not be deleted'); + throw new Error(error.message || 'An error occurred during file deletion.'); + } } }; diff --git a/api/server/services/Files/process.js b/api/server/services/Files/process.js index a5d9c8c1e0..8744eb409b 100644 --- a/api/server/services/Files/process.js +++ b/api/server/services/Files/process.js @@ -347,8 +347,8 @@ const uploadImageBuffer = async ({ req, context, metadata = {}, resize = true }) req.app.locals.imageOutputType }`; } - - const filepath = await saveBuffer({ userId: req.user.id, fileName: filename, buffer }); + const fileName = `${file_id}-${filename}`; + const filepath = await saveBuffer({ userId: req.user.id, fileName, buffer }); return await createFile( { user: req.user.id, @@ -801,8 +801,7 @@ async function saveBase64Image( { req, file_id: _file_id, filename: _filename, endpoint, context, resolution = 'high' }, ) { const file_id = _file_id ?? v4(); - - let filename = _filename; + let filename = `${file_id}-${_filename}`; const { buffer: inputBuffer, type } = base64ToBuffer(url); if (!path.extname(_filename)) { const extension = mime.getExtension(type); diff --git a/client/src/locales/ar/translation.json b/client/src/locales/ar/translation.json index f0f9f25ebd..cfee892483 100644 --- a/client/src/locales/ar/translation.json +++ b/client/src/locales/ar/translation.json @@ -584,7 +584,6 @@ "com_ui_happy_birthday": "إنه عيد ميلادي الأول!", "com_ui_host": "مُضيف", "com_ui_image_gen": "توليد الصور", - "com_ui_import": "استيراد", "com_ui_import_conversation_error": "حدث خطأ أثناء استيراد محادثاتك", "com_ui_import_conversation_file_type_error": "نوع الملف غير مدعوم للاستيراد", "com_ui_import_conversation_info": "استيراد محادثات من ملف JSON", @@ -716,4 +715,4 @@ "com_ui_zoom": "تكبير", "com_user_message": "أنت", "com_warning_resubmit_unsupported": "إعادة إرسال رسالة الذكاء الاصطناعي غير مدعومة لنقطة النهاية هذه" -} +} \ No newline at end of file diff --git a/client/src/locales/de/translation.json b/client/src/locales/de/translation.json index 98cd14b865..de986df0b0 100644 --- a/client/src/locales/de/translation.json +++ b/client/src/locales/de/translation.json @@ -614,7 +614,6 @@ "com_ui_hide_qr": "QR-Code ausblenden", "com_ui_host": "Host", "com_ui_image_gen": "Bildgenerierung", - "com_ui_import": "Importieren", "com_ui_import_conversation_error": "Beim Importieren Ihrer Konversationen ist ein Fehler aufgetreten", "com_ui_import_conversation_file_type_error": "Nicht unterstützter Importtyp", "com_ui_import_conversation_info": "Konversationen aus einer JSON-Datei importieren", @@ -764,4 +763,4 @@ "com_ui_zoom": "Zoom", "com_user_message": "Du", "com_warning_resubmit_unsupported": "Das erneute Senden der KI-Nachricht wird für diesen Endpunkt nicht unterstützt." -} +} \ No newline at end of file diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index 63272a54f4..caa1496969 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -1,6 +1,6 @@ { - "chat_direction_left_to_right": "Chat direction is now left to right", - "chat_direction_right_to_left": "Chat direction is now right to left", + "chat_direction_left_to_right": "something needs to go here. was empty", + "chat_direction_right_to_left": "something needs to go here. was empty", "com_a11y_ai_composing": "The AI is still composing.", "com_a11y_end": "The AI has finished their reply.", "com_a11y_start": "The AI has started their reply.", @@ -124,9 +124,11 @@ "com_auth_submit_registration": "Submit registration", "com_auth_to_reset_your_password": "to reset your password.", "com_auth_to_try_again": "to try again.", + "com_auth_two_factor": "Check your preferred one-time password application for a code", "com_auth_username": "Username (optional)", "com_auth_username_max_length": "Username must be less than 20 characters", "com_auth_username_min_length": "Username must be at least 2 characters", + "com_auth_verify_your_identity": "Verify Your Identity", "com_auth_welcome_back": "Welcome back", "com_click_to_download": "(click here to download)", "com_download_expired": "(download expired)", @@ -263,9 +265,10 @@ "com_files_filter": "Filter files...", "com_files_no_results": "No results.", "com_files_number_selected": "{{0}} of {{1}} items(s) selected", - "com_files_table": "Files Table", + "com_files_table": "something needs to go here. was empty", "com_generated_files": "Generated files:", "com_hide_examples": "Hide Examples", + "com_nav_2fa": "Two-Factor Authentication (2FA)", "com_nav_account_settings": "Account Settings", "com_nav_always_make_prod": "Always make new versions production", "com_nav_archive_created_at": "Date Archived", @@ -435,9 +438,16 @@ "com_sidepanel_parameters": "Parameters", "com_sidepanel_select_agent": "Select an Agent", "com_sidepanel_select_assistant": "Select an Assistant", - "com_nav_2fa": "Two-Factor Authentication (2FA)", - "com_auth_verify_your_identity": "Verify Your Identity", - "com_auth_two_factor": "Check your preferred one-time password application for a code", + "com_ui_2fa_account_security": "Two-factor authentication adds an extra layer of security to your account", + "com_ui_2fa_disable": "Disable 2FA", + "com_ui_2fa_disable_error": "There was an error disabling two-factor authentication", + "com_ui_2fa_disabled": "2FA has been disabled", + "com_ui_2fa_enable": "Enable 2FA", + "com_ui_2fa_enabled": "2FA has been enabled", + "com_ui_2fa_generate_error": "There was an error generating two-factor authentication settings", + "com_ui_2fa_invalid": "Invalid two-factor authentication code", + "com_ui_2fa_setup": "Setup 2FA", + "com_ui_2fa_verified": "Successfully verified Two-Factor Authentication", "com_ui_accept": "I accept", "com_ui_add": "Add", "com_ui_add_model_preset": "Add a model or preset for an additional response", @@ -488,6 +498,9 @@ "com_ui_azure": "Azure", "com_ui_back_to_chat": "Back to Chat", "com_ui_back_to_prompts": "Back to Prompts", + "com_ui_backup_codes": "Backup Codes", + "com_ui_backup_codes_regenerate_error": "There was an error regenerating backup codes", + "com_ui_backup_codes_regenerated": "Backup codes have been regenerated successfully", "com_ui_basic": "Basic", "com_ui_basic_auth_header": "Basic authorization header", "com_ui_bearer": "Bearer", @@ -524,6 +537,7 @@ "com_ui_collapse_chat": "Collapse Chat", "com_ui_command_placeholder": "Optional: Enter a command for the prompt or name will be used", "com_ui_command_usage_placeholder": "Select a Prompt by command or name", + "com_ui_complete_setup": "Complete Setup", "com_ui_confirm_action": "Confirm Action", "com_ui_confirm_admin_use_change": "Changing this setting will block access for admins, including yourself. Are you sure you want to proceed?", "com_ui_confirm_change": "Confirm Change", @@ -577,8 +591,11 @@ "com_ui_descending": "Desc", "com_ui_description": "Description", "com_ui_description_placeholder": "Optional: Enter a description to display for the prompt", + "com_ui_disabling": "Disabling...", "com_ui_download": "Download", "com_ui_download_artifact": "Download Artifact", + "com_ui_download_backup": "Download Backup Codes", + "com_ui_download_backup_tooltip": "Before you continue, download your backup codes. You will need them to regain access if you lose your authenticator device", "com_ui_download_error": "Error downloading file. The file may have been deleted.", "com_ui_drag_drop": "something needs to go here. was empty", "com_ui_dropdown_variables": "Dropdown variables:", @@ -627,7 +644,10 @@ "com_ui_fork_split_target_setting": "Start fork from target message by default", "com_ui_fork_success": "Successfully forked conversation", "com_ui_fork_visible": "Visible messages only", - "com_ui_global_group": "Global Group", + "com_ui_generate_backup": "Generate Backup Codes", + "com_ui_generate_qrcode": "Generate QR Code", + "com_ui_generating": "Generating...", + "com_ui_global_group": "something needs to go here. was empty", "com_ui_go_back": "Go back", "com_ui_go_to_conversation": "Go to conversation", "com_ui_happy_birthday": "It's my 1st birthday!", @@ -668,14 +688,16 @@ "com_ui_new_chat": "New chat", "com_ui_next": "Next", "com_ui_no": "No", + "com_ui_no_backup_codes": "No backup codes available. Please generate new ones", "com_ui_no_bookmarks": "it seems like you have no bookmarks yet. Click on a chat and add a new one", "com_ui_no_category": "No category", "com_ui_no_changes": "No changes to update", - "com_ui_no_data": "No data", + "com_ui_no_data": "something needs to go here. was empty", "com_ui_no_terms_content": "No terms and conditions content to display", - "com_ui_no_valid_items": "No valid items", + "com_ui_no_valid_items": "something needs to go here. was empty", "com_ui_none": "None", "com_ui_none_selected": "None selected", + "com_ui_not_used": "Not Used", "com_ui_nothing_found": "Nothing found", "com_ui_oauth": "OAuth", "com_ui_of": "of", @@ -703,6 +725,8 @@ "com_ui_read_aloud": "Read aloud", "com_ui_refresh_link": "Refresh link", "com_ui_regenerate": "Regenerate", + "com_ui_regenerate_backup": "Regenerate Backup Codes", + "com_ui_regenerating": "Regenerating...", "com_ui_region": "Region", "com_ui_rename": "Rename", "com_ui_rename_prompt": "Rename Prompt", @@ -725,6 +749,7 @@ "com_ui_schema": "Schema", "com_ui_scope": "Scope", "com_ui_search": "Search", + "com_ui_secret_key": "Secret Key", "com_ui_select": "Select", "com_ui_select_file": "Select a file", "com_ui_select_model": "Select a model", @@ -749,6 +774,7 @@ "com_ui_shared_link_not_found": "Shared link not found", "com_ui_shared_prompts": "Shared Prompts", "com_ui_shop": "Shopping", + "com_ui_show": "Show", "com_ui_show_all": "Show All", "com_ui_show_qr": "Show QR Code", "com_ui_sign_in_to_domain": "Sign-in to {{0}}", @@ -786,46 +812,20 @@ "com_ui_upload_invalid_var": "Invalid file for upload. Must be an image not exceeding {{0}} MB", "com_ui_upload_success": "Successfully uploaded file", "com_ui_upload_type": "Select Upload Type", + "com_ui_use_2fa_code": "Use 2FA Code Instead", + "com_ui_use_backup_code": "Use Backup Code Instead", "com_ui_use_micrphone": "Use microphone", "com_ui_use_prompt": "Use prompt", + "com_ui_used": "Used", "com_ui_variables": "Variables", "com_ui_variables_info": "Use double braces in your text to create variables, e.g. `{{example variable}}`, to later fill when using the prompt.", + "com_ui_verify": "Verify", "com_ui_version_var": "Version {{0}}", "com_ui_versions": "Versions", "com_ui_view_source": "View source chat", "com_ui_write": "Writing", "com_ui_yes": "Yes", "com_ui_zoom": "Zoom", - "com_ui_secret_key": "Secret Key", - "com_ui_2fa_account_security": "Two-factor authentication adds an extra layer of security to your account", - "com_ui_2fa_generate_error": "There was an error generating two-factor authentication settings", - "com_ui_backup_codes": "Backup Codes", - "com_ui_2fa_invalid": "Invalid two-factor authentication code", - "com_ui_2fa_setup": "Setup 2FA", - "com_ui_2fa_enable": "Enable 2FA", - "com_ui_2fa_disable": "Disable 2FA", - "com_ui_disabling": "Disabling...", - "com_ui_2fa_enabled": "2FA has been enabled", - "com_ui_2fa_disabled": "2FA has been disabled", - "com_ui_download_backup": "Download Backup Codes", - "com_ui_use_backup_code": "Use Backup Code Instead", - "com_ui_use_2fa_code": "Use 2FA Code Instead", - "com_ui_verify": "Verify", - "com_ui_2fa_disable_error": "There was an error disabling two-factor authentication", - "com_ui_2fa_verified": "Successfully verified Two-Factor Authentication", - "com_ui_generate_backup": "Generate Backup Codes", - "com_ui_regenerate_backup": "Regenerate Backup Codes", - "com_ui_regenerating": "Regenerating...", - "com_ui_used": "Used", - "com_ui_not_used": "Not Used", - "com_ui_backup_codes_regenerated": "Backup codes have been regenerated successfully", - "com_ui_backup_codes_regenerate_error": "There was an error regenerating backup codes", - "com_ui_no_backup_codes": "No backup codes available. Please generate new ones", - "com_ui_generating": "Generating...", - "com_ui_generate_qrcode": "Generate QR Code", - "com_ui_complete_setup": "Complete Setup", - "com_ui_download_backup_tooltip": "Before you continue, download your backup codes. You will need them to regain access if you lose your authenticator device", - "com_ui_show": "Show", "com_user_message": "You", "com_warning_resubmit_unsupported": "Resubmitting the AI message is not supported for this endpoint." -} +} \ No newline at end of file diff --git a/client/src/locales/es/translation.json b/client/src/locales/es/translation.json index 3355d79df4..fb1e0220d3 100644 --- a/client/src/locales/es/translation.json +++ b/client/src/locales/es/translation.json @@ -584,7 +584,6 @@ "com_ui_happy_birthday": "¡Es mi primer cumpleaños!", "com_ui_host": "Host", "com_ui_image_gen": "Gen Imágenes", - "com_ui_import": "Importar", "com_ui_import_conversation_error": "Hubo un error al importar tus chats", "com_ui_import_conversation_file_type_error": "com_ui_import_conversation_file_type_error: Tipo de archivo no compatible para importar", "com_ui_import_conversation_info": "Importar chats de un archivo JSON", @@ -716,4 +715,4 @@ "com_ui_zoom": "Zoom", "com_user_message": "Usted", "com_warning_resubmit_unsupported": "No se admite el reenvío del mensaje de IA para este punto de conexión." -} +} \ No newline at end of file diff --git a/client/src/locales/et/translation.json b/client/src/locales/et/translation.json index 84d40e80f6..6eec2e8956 100644 --- a/client/src/locales/et/translation.json +++ b/client/src/locales/et/translation.json @@ -184,7 +184,6 @@ "com_endpoint_google_temp": "Kõrgemad väärtused = juhuslikum, samas kui madalamad väärtused = keskendunum ja deterministlikum. Soovitame muuta kas seda või Top P-d, aga mitte mõlemat.", "com_endpoint_google_topk": "Top-k muudab seda, kuidas mudel valib väljundi jaoks märgid. Top-k väärtus 1 tähendab, et valitud märk on kõige tõenäolisem kõigi mudeli sõnavaras olevate märkide seas (nimetatakse ka ahneks dekodeerimiseks), samas kui top-k väärtus 3 tähendab, et järgmine märk valitakse 3 kõige tõenäolisema märgi seast (kasutades temperatuuri).", "com_endpoint_google_topp": "Top-p muudab seda, kuidas mudel valib väljundi jaoks märgid. Märgid valitakse kõige tõenäolisemast K (vt parameetrit topK) kuni vähim tõenäoliseni, kuni nende tõenäosuste summa on võrdne top-p väärtusega.", - "com_endpoint_import": "Impordi", "com_endpoint_instructions_assistants": "Tühista juhised", "com_endpoint_instructions_assistants_placeholder": "Tühistab assistendi juhised. See on kasulik käitumise muutmiseks käivituse kohta.", "com_endpoint_max_output_tokens": "Maksimaalsed väljundmärgid", @@ -272,7 +271,6 @@ "com_nav_archive_name": "Nimi", "com_nav_archived_chats": "Arhiveeritud vestlused", "com_nav_archived_chats_empty": "Sul ei ole arhiveeritud vestlusi.", - "com_nav_archived_chats_manage": "Halda", "com_nav_at_command": "@-käsk", "com_nav_at_command_description": "Lülita käsk \"@\" sisse/välja lõpp-punktide, mudelite, eelseadistuste jms vahetamiseks.", "com_nav_audio_play_error": "Viga heli esitamisel: {{0}}", @@ -630,7 +628,6 @@ "com_ui_hide_qr": "Peida QR-kood", "com_ui_host": "Host", "com_ui_image_gen": "Pildi genereerimine", - "com_ui_import_conversation": "Impordi", "com_ui_import_conversation_error": "Vestluste importimisel tekkis viga", "com_ui_import_conversation_file_type_error": "Toetamatu imporditüüp", "com_ui_import_conversation_info": "Impordi vestlused JSON-failist", @@ -786,4 +783,4 @@ "com_ui_zoom": "Suumi", "com_user_message": "Sina", "com_warning_resubmit_unsupported": "AI sõnumi uuesti esitamine pole selle otspunkti jaoks toetatud." -} +} \ No newline at end of file diff --git a/client/src/locales/fi/translation.json b/client/src/locales/fi/translation.json index 190d344026..270672475b 100644 --- a/client/src/locales/fi/translation.json +++ b/client/src/locales/fi/translation.json @@ -458,7 +458,6 @@ "com_ui_happy_birthday": "On 1. syntymäpäiväni!", "com_ui_host": "Host", "com_ui_image_gen": "Kuvanluonti", - "com_ui_import": "Tuo", "com_ui_import_conversation_error": "Keskustelujesi tuonnissa tapahtui virhe", "com_ui_import_conversation_file_type_error": "Tiedostotyyppi ei ole tuettu tuonnissa", "com_ui_import_conversation_info": "Tuo keskusteluja JSON-tiedostosta", @@ -551,4 +550,4 @@ "com_ui_versions": "Versiot", "com_ui_yes": "Kyllä", "com_user_message": "Sinä" -} +} \ No newline at end of file diff --git a/client/src/locales/fr/translation.json b/client/src/locales/fr/translation.json index b1bd434c9d..b06cb11e50 100644 --- a/client/src/locales/fr/translation.json +++ b/client/src/locales/fr/translation.json @@ -599,7 +599,6 @@ "com_ui_hide_qr": "Cacher le code QR", "com_ui_host": "Hôte", "com_ui_image_gen": "Génération d'image", - "com_ui_import": "Importer", "com_ui_import_conversation_error": "Une erreur s'est produite lors de l'importation de vos conversations", "com_ui_import_conversation_file_type_error": "Type de fichier non pris en charge pour l'importation", "com_ui_import_conversation_info": "Importer des conversations à partir d'un fichier JSON", @@ -733,4 +732,4 @@ "com_ui_zoom": "Zoom", "com_user_message": "Vous", "com_warning_resubmit_unsupported": "La resoumission du message IA n'est pas prise en charge pour ce point de terminaison." -} +} \ No newline at end of file diff --git a/client/src/locales/he/translation.json b/client/src/locales/he/translation.json index 456d3d33e0..4f8afc7b1d 100644 --- a/client/src/locales/he/translation.json +++ b/client/src/locales/he/translation.json @@ -291,7 +291,6 @@ "com_ui_error": "שגיאה", "com_ui_examples": "דוגמאות", "com_ui_happy_birthday": "זה יום ההולדת הראשון שלי!", - "com_ui_import": "יבוא", "com_ui_import_conversation_error": "אירעה שגיאה בעת ייבוא השיחות שלך", "com_ui_import_conversation_info": "ייבא שיחות מקובץ JSON", "com_ui_import_conversation_success": "השיחות יובאו בהצלחה", @@ -331,4 +330,4 @@ "com_ui_upload_success": "קובץ שהועלה בהצלחה", "com_ui_use_prompt": "השתמש בהודעת", "com_user_message": "אתה" -} +} \ No newline at end of file diff --git a/client/src/locales/id/translation.json b/client/src/locales/id/translation.json index b6d7d57237..7caba4d0ae 100644 --- a/client/src/locales/id/translation.json +++ b/client/src/locales/id/translation.json @@ -247,7 +247,6 @@ "com_ui_enter": "Masuk", "com_ui_examples": "Contoh", "com_ui_happy_birthday": "Ini ulang tahun pertamaku!", - "com_ui_import": "Impor", "com_ui_import_conversation_error": "Terjadi kesalahan saat mengimpor percakapan Anda", "com_ui_import_conversation_info": "Impor percakapan dari file JSON", "com_ui_import_conversation_success": "Percakapan berhasil diimpor", @@ -284,4 +283,4 @@ "com_ui_upload_success": "Berhasil mengunggah file", "com_ui_use_prompt": "Gunakan petunjuk", "com_user_message": "Kamu" -} +} \ No newline at end of file diff --git a/client/src/locales/it/translation.json b/client/src/locales/it/translation.json index d64fca28e5..062209cce9 100644 --- a/client/src/locales/it/translation.json +++ b/client/src/locales/it/translation.json @@ -600,7 +600,6 @@ "com_ui_hide_qr": "Nascondi codice QR", "com_ui_host": "Host", "com_ui_image_gen": "Generazione immagine", - "com_ui_import": "Importa", "com_ui_import_conversation_error": "Si è verificato un errore durante l'importazione delle conversazioni", "com_ui_import_conversation_file_type_error": "Tipo di importazione non supportato", "com_ui_import_conversation_info": "Importa conversazioni da un file JSON", @@ -744,4 +743,4 @@ "com_ui_zoom": "Zoom", "com_user_message": "Mostra nome utente nei messaggi", "com_warning_resubmit_unsupported": "Il reinvio del messaggio AI non è supportato per questo endpoint." -} +} \ No newline at end of file diff --git a/client/src/locales/ja/translation.json b/client/src/locales/ja/translation.json index 965ca5699d..a63fc80254 100644 --- a/client/src/locales/ja/translation.json +++ b/client/src/locales/ja/translation.json @@ -584,7 +584,6 @@ "com_ui_happy_birthday": "初めての誕生日です!", "com_ui_host": "ホスト", "com_ui_image_gen": "画像生成", - "com_ui_import": "インポート", "com_ui_import_conversation_error": "会話のインポート時にエラーが発生しました", "com_ui_import_conversation_file_type_error": "サポートされていないインポート形式です", "com_ui_import_conversation_info": "JSONファイルから会話をインポートする", @@ -716,4 +715,4 @@ "com_ui_zoom": "ズーム", "com_user_message": "あなた", "com_warning_resubmit_unsupported": "このエンドポイントではAIメッセージの再送信はサポートされていません" -} +} \ No newline at end of file diff --git a/client/src/locales/ko/translation.json b/client/src/locales/ko/translation.json index 46eda9f9de..0bfb21452e 100644 --- a/client/src/locales/ko/translation.json +++ b/client/src/locales/ko/translation.json @@ -584,7 +584,6 @@ "com_ui_happy_birthday": "내 첫 생일이야!", "com_ui_host": "호스트", "com_ui_image_gen": "이미지 생성", - "com_ui_import": "가져오기", "com_ui_import_conversation_error": "대화를 가져오는 동안 오류가 발생했습니다", "com_ui_import_conversation_file_type_error": "가져올 수 없는 파일 형식입니다", "com_ui_import_conversation_info": "JSON 파일에서 대화 가져오기", @@ -716,4 +715,4 @@ "com_ui_zoom": "확대/축소", "com_user_message": "당신", "com_warning_resubmit_unsupported": "이 엔드포인트에서는 AI 메시지 재전송이 지원되지 않습니다" -} +} \ No newline at end of file diff --git a/client/src/locales/nl/translation.json b/client/src/locales/nl/translation.json index f6c99423fb..35fec8640e 100644 --- a/client/src/locales/nl/translation.json +++ b/client/src/locales/nl/translation.json @@ -219,7 +219,6 @@ "com_ui_enter": "Invoeren", "com_ui_examples": "Voorbeelden", "com_ui_happy_birthday": "Het is mijn eerste verjaardag!", - "com_ui_import": "Importeren", "com_ui_import_conversation_error": "Er is een fout opgetreden bij het importeren van je gesprekken", "com_ui_import_conversation_info": "Gesprekken importeren vanuit een JSON-bestand", "com_ui_import_conversation_success": "Gesprekken succesvol geïmporteerd", @@ -250,4 +249,4 @@ "com_ui_unarchive_error": "Kan conversatie niet uit archiveren", "com_ui_upload_success": "Bestand succesvol geüpload", "com_ui_use_prompt": "Gebruik prompt" -} +} \ No newline at end of file diff --git a/client/src/locales/pl/translation.json b/client/src/locales/pl/translation.json index 30483ba8b3..24fd96a70c 100644 --- a/client/src/locales/pl/translation.json +++ b/client/src/locales/pl/translation.json @@ -562,7 +562,6 @@ "com_ui_hide_qr": "Ukryj kod QR", "com_ui_host": "Host", "com_ui_image_gen": "Generowanie obrazu", - "com_ui_import": "Importuj", "com_ui_import_conversation_error": "Wystąpił błąd podczas importowania konwersacji", "com_ui_import_conversation_file_type_error": "Nieobsługiwany typ importu", "com_ui_import_conversation_info": "Importuj konwersacje z pliku JSON", @@ -704,4 +703,4 @@ "com_ui_zoom": "Powiększ", "com_user_message": "Ty", "com_warning_resubmit_unsupported": "Ponowne przesyłanie wiadomości AI nie jest obsługiwane dla tego punktu końcowego." -} +} \ No newline at end of file diff --git a/client/src/locales/pt-BR/translation.json b/client/src/locales/pt-BR/translation.json index 06a22184c5..a1e37608ff 100644 --- a/client/src/locales/pt-BR/translation.json +++ b/client/src/locales/pt-BR/translation.json @@ -163,7 +163,6 @@ "com_endpoint_google_temp": "Valores mais altos = mais aleatório, enquanto valores mais baixos = mais focado e determinístico. Recomendamos alterar isso ou Top P, mas não ambos.", "com_endpoint_google_topk": "Top-k altera como o modelo seleciona tokens para saída. Um top-k de 1 significa que o token selecionado é o mais provável entre todos os tokens no vocabulário do modelo (também chamado de decodificação gananciosa), enquanto um top-k de 3 significa que o próximo token é selecionado entre os 3 tokens mais prováveis (usando temperatura).", "com_endpoint_google_topp": "Top-p altera como o modelo seleciona tokens para saída. Os tokens são selecionados dos mais prováveis (veja o parâmetro topK) até os menos prováveis até que a soma de suas probabilidades atinja o valor top-p.", - "com_endpoint_import": "Importar", "com_endpoint_instructions_assistants": "Substituir Instruções", "com_endpoint_instructions_assistants_placeholder": "Substitui as instruções do assistente. Isso é útil para modificar o comportamento em uma base por execução.", "com_endpoint_max_output_tokens": "Máximo de Tokens de Saída", @@ -237,7 +236,6 @@ "com_nav_archive_name": "Nome", "com_nav_archived_chats": "Chats Arquivados", "com_nav_archived_chats_empty": "Você não tem conversas arquivadas.", - "com_nav_archived_chats_manage": "Gerenciar", "com_nav_at_command": "Comando @", "com_nav_at_command_description": "Alternar comando \"@\" para alternar endpoints, modelos, predefinições, etc.", "com_nav_audio_play_error": "Erro ao reproduzir áudio: {{0}}", @@ -353,7 +351,6 @@ "com_nav_setting_speech": "Fala", "com_nav_settings": "Configurações", "com_nav_shared_links": "Links compartilhados", - "com_nav_shared_links_manage": "Gerenciar", "com_nav_show_code": "Sempre mostrar código ao usar o interpretador de código", "com_nav_slash_command": "Comando /", "com_nav_slash_command_description": "Alternar comando \"/\" para selecionar um prompt via teclado", @@ -530,7 +527,6 @@ "com_ui_happy_birthday": "É meu 1º aniversário!", "com_ui_host": "Host", "com_ui_image_gen": "Geração de Imagem", - "com_ui_import_conversation": "Importar", "com_ui_import_conversation_error": "Houve um erro ao importar suas conversas", "com_ui_import_conversation_file_type_error": "Tipo de importação não suportado", "com_ui_import_conversation_info": "Importar conversas de um arquivo JSON", diff --git a/client/src/locales/pt-PT/translation.json b/client/src/locales/pt-PT/translation.json index c687f7b2e0..730ce2ac2f 100644 --- a/client/src/locales/pt-PT/translation.json +++ b/client/src/locales/pt-PT/translation.json @@ -182,7 +182,6 @@ "com_endpoint_google_temp": "Valores mais altos = mais aleatório, enquanto valores mais baixos = mais focado e determinístico. Recomendamos alterar isso ou Top P, mas não ambos.", "com_endpoint_google_topk": "Top-k altera como o modelo seleciona tokens para saída. Um top-k de 1 significa que o token selecionado é o mais provável entre todos os tokens no vocabulário do modelo (também chamado de decodificação gananciosa), enquanto um top-k de 3 significa que o próximo token é selecionado entre os 3 tokens mais prováveis (usando temperatura).", "com_endpoint_google_topp": "Top-p altera como o modelo seleciona tokens para saída. Os tokens são selecionados dos mais prováveis (veja o parâmetro topK) até os menos prováveis até que a soma de suas probabilidades atinja o valor top-p.", - "com_endpoint_import": "Importar", "com_endpoint_instructions_assistants": "Substituir Instruções", "com_endpoint_instructions_assistants_placeholder": "Substitui as instruções do assistente. Isso é útil para modificar o comportamento em uma base por execução.", "com_endpoint_max_output_tokens": "Máximo de Tokens de Saída", @@ -268,7 +267,6 @@ "com_nav_archive_name": "Nome", "com_nav_archived_chats": "Chats Arquivados", "com_nav_archived_chats_empty": "Você não tem conversas arquivadas.", - "com_nav_archived_chats_manage": "Gerenciar", "com_nav_at_command": "Comando @", "com_nav_at_command_description": "Alternar comando \"@\" para alternar endpoints, modelos, predefinições, etc.", "com_nav_audio_play_error": "Erro ao reproduzir áudio: {{0}}", @@ -391,7 +389,6 @@ "com_nav_setting_speech": "Fala", "com_nav_settings": "Configurações", "com_nav_shared_links": "Links compartilhados", - "com_nav_shared_links_manage": "Gerenciar", "com_nav_show_code": "Sempre mostrar código ao usar o interpretador de código", "com_nav_show_thinking": "Abrir Dropdown de lógica por defeito.", "com_nav_slash_command": "Comando /", @@ -626,7 +623,6 @@ "com_ui_host": "Host", "com_ui_idea": "Ideias", "com_ui_image_gen": "Geração de Imagem", - "com_ui_import_conversation": "Importar", "com_ui_import_conversation_error": "Houve um erro ao importar suas conversas", "com_ui_import_conversation_file_type_error": "Tipo de importação não suportado", "com_ui_import_conversation_info": "Importar conversas de um arquivo JSON", diff --git a/client/src/locales/ru/translation.json b/client/src/locales/ru/translation.json index ee5e4d0e29..76962c09b2 100644 --- a/client/src/locales/ru/translation.json +++ b/client/src/locales/ru/translation.json @@ -584,7 +584,6 @@ "com_ui_happy_birthday": "Это мой первый день рождения!", "com_ui_host": "Хост", "com_ui_image_gen": "Генератор изображений", - "com_ui_import": "Импортировать", "com_ui_import_conversation_error": "При импорте бесед произошла ошибка", "com_ui_import_conversation_file_type_error": "Неподдерживаемый тип импорта", "com_ui_import_conversation_info": "Импортировать беседы из файла JSON", @@ -716,4 +715,4 @@ "com_ui_zoom": "Масштаб", "com_user_message": "Вы", "com_warning_resubmit_unsupported": "Повторная отправка сообщения ИИ не поддерживается для данной конечной точки" -} +} \ No newline at end of file diff --git a/client/src/locales/sv/translation.json b/client/src/locales/sv/translation.json index 26c8cc0c55..4497ecc857 100644 --- a/client/src/locales/sv/translation.json +++ b/client/src/locales/sv/translation.json @@ -206,7 +206,6 @@ "com_ui_enter": "Ange", "com_ui_examples": "Exempel", "com_ui_happy_birthday": "Det är min första födelsedag!", - "com_ui_import": "Importera", "com_ui_import_conversation_error": "Det uppstod ett fel vid import av dina konversationer", "com_ui_import_conversation_info": "Importera konversationer från en JSON-fil", "com_ui_import_conversation_success": "Konversationer har importerats framgångsrikt", @@ -236,4 +235,4 @@ "com_ui_unarchive_error": "Kunde inte avarkivera chatt", "com_ui_upload_success": "Uppladdningen av filen lyckades", "com_ui_use_prompt": "Använd prompt" -} +} \ No newline at end of file diff --git a/client/src/locales/tr/translation.json b/client/src/locales/tr/translation.json index a75a5422ea..e4b7bd8ce5 100644 --- a/client/src/locales/tr/translation.json +++ b/client/src/locales/tr/translation.json @@ -602,7 +602,6 @@ "com_ui_hide_qr": "QR Kodunu Gizle", "com_ui_host": "Host", "com_ui_image_gen": "Görüntü Oluştur", - "com_ui_import": "İçe Aktar", "com_ui_import_conversation_error": "Konuşmalarınızı içe aktarma sırasında bir hata oluştu", "com_ui_import_conversation_file_type_error": "Desteklenmeyen içe aktarma türü", "com_ui_import_conversation_info": "JSON dosyasından konuşmaları içe aktar", @@ -747,4 +746,4 @@ "com_ui_zoom": "Yakınlaştır", "com_user_message": "Sen", "com_warning_resubmit_unsupported": "Bu uç nokta için yapay zeka mesajını yeniden gönderme desteklenmiyor." -} +} \ No newline at end of file diff --git a/client/src/locales/vi/translation.json b/client/src/locales/vi/translation.json index 57f34eef0b..d4cff9b40e 100644 --- a/client/src/locales/vi/translation.json +++ b/client/src/locales/vi/translation.json @@ -205,7 +205,6 @@ "com_ui_enter": "Nhập", "com_ui_examples": "Ví dụ", "com_ui_happy_birthday": "Đây là sinh nhật đầu tiên của tôi!", - "com_ui_import": "Nhập khẩu", "com_ui_import_conversation_error": "Đã xảy ra lỗi khi nhập khẩu cuộc trò chuyện của bạn", "com_ui_import_conversation_info": "Nhập khẩu cuộc trò chuyện từ một tệp JSON", "com_ui_import_conversation_success": "Đã nhập khẩu cuộc trò chuyện thành công", @@ -235,4 +234,4 @@ "com_ui_unarchive_error": "Không thể bỏ lưu trữ cuộc trò chuyện", "com_ui_upload_success": "Tải tệp thành công", "com_ui_use_prompt": "Sử dụng gợi ý" -} +} \ No newline at end of file diff --git a/client/src/locales/zh-Hans/translation.json b/client/src/locales/zh-Hans/translation.json index 6570bf1ae5..cca6ac9b8e 100644 --- a/client/src/locales/zh-Hans/translation.json +++ b/client/src/locales/zh-Hans/translation.json @@ -182,7 +182,6 @@ "com_endpoint_google_temp": "值越高表示输出越随机,值越低表示输出越确定。建议不要同时改变此值和 Top-p。", "com_endpoint_google_topk": "top-k 会改变模型选择输出词的方式。top-k 为 1 意味着所选词是模型词汇中概率最大的(也称为贪心解码),而 top-k 为 3 意味着下一个词是从 3 个概率最大的词中选出的(使用随机性)。", "com_endpoint_google_topp": "top-p(核采样)会改变模型选择输出词的方式。从概率最大的 K(参见topK参数)向最小的 K 选择,直到它们的概率之和等于 top-p 值。", - "com_endpoint_import": "导入", "com_endpoint_instructions_assistants": "覆写指令", "com_endpoint_instructions_assistants_placeholder": "覆盖助手的指令。这对于需要逐次修改行为非常有用。", "com_endpoint_max_output_tokens": "最大输出词元数", @@ -267,7 +266,6 @@ "com_nav_archive_name": "名称", "com_nav_archived_chats": "归档的对话", "com_nav_archived_chats_empty": "您没有归档的对话。", - "com_nav_archived_chats_manage": "管理", "com_nav_at_command": "@-命令", "com_nav_at_command_description": "切换至命令 “@” 以更改端点、模型、预设等", "com_nav_audio_play_error": "播放音频时发生错误:{{0}}", @@ -390,7 +388,6 @@ "com_nav_setting_speech": "语音", "com_nav_settings": "设置", "com_nav_shared_links": "共享链接", - "com_nav_shared_links_manage": "管理", "com_nav_show_code": "使用代码解释器时始终显示代码", "com_nav_slash_command": "/-命令", "com_nav_slash_command_description": "切换至命令 “/” 以通过键盘选择提示词", @@ -611,7 +608,6 @@ "com_ui_hide_qr": "隐藏二维码", "com_ui_host": "主机", "com_ui_image_gen": "图片生成", - "com_ui_import_conversation": "导入", "com_ui_import_conversation_error": "导入对话时发生错误", "com_ui_import_conversation_file_type_error": "不支持的导入类型", "com_ui_import_conversation_info": "从 JSON 文件导入对话", diff --git a/client/src/locales/zh-Hant/translation.json b/client/src/locales/zh-Hant/translation.json index f46d94af2d..0aaf22594d 100644 --- a/client/src/locales/zh-Hant/translation.json +++ b/client/src/locales/zh-Hant/translation.json @@ -584,7 +584,6 @@ "com_ui_happy_birthday": "這是我的第一個生日!", "com_ui_host": "主機", "com_ui_image_gen": "影像生成", - "com_ui_import": "匯入", "com_ui_import_conversation_error": "匯入對話時發生錯誤", "com_ui_import_conversation_file_type_error": "不支援的匯入檔案類型", "com_ui_import_conversation_info": "從 JSON 文件匯入對話", @@ -716,4 +715,4 @@ "com_ui_zoom": "縮放", "com_user_message": "您", "com_warning_resubmit_unsupported": "此端點不支援重新送出 AI 訊息。" -} +} \ No newline at end of file diff --git a/deploy-compose.yml b/deploy-compose.yml index 3f1e14ee54..ae61265a05 100644 --- a/deploy-compose.yml +++ b/deploy-compose.yml @@ -1,4 +1,3 @@ -version: "3.8" services: api: # build: @@ -29,6 +28,7 @@ services: source: ./librechat.yaml target: /app/librechat.yaml - ./images:/app/client/public/images + - ./uploads:/app/uploads - ./logs:/app/api/logs client: diff --git a/docker-compose.yml b/docker-compose.yml index e863965342..e16f93f4c0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,6 +25,7 @@ services: source: ./.env target: /app/.env - ./images:/app/client/public/images + - ./uploads:/app/uploads - ./logs:/app/api/logs mongodb: container_name: chat-mongodb From ecddffa7b241502c0a4342f4b1c1c65467e754dd Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Tue, 18 Feb 2025 08:14:19 -0500 Subject: [PATCH 15/23] =?UTF-8?q?=F0=9F=90=9B=20fix:=20RAG=20Results=20Sor?= =?UTF-8?q?ted=20By=20Distance=20(#5931)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: Extract file unlinking logic into a separate function and don't throw error * fix: RAG results are actually in distance, not score --- api/app/clients/tools/util/fileSearch.js | 12 +++++++----- api/server/services/Files/Local/crud.js | 15 +++++++++++++-- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/api/app/clients/tools/util/fileSearch.js b/api/app/clients/tools/util/fileSearch.js index c48adc2eb4..54da483362 100644 --- a/api/app/clients/tools/util/fileSearch.js +++ b/api/app/clients/tools/util/fileSearch.js @@ -106,19 +106,21 @@ const createFileSearchTool = async ({ req, files, entity_id }) => { const formattedResults = validResults .flatMap((result) => - result.data.map(([docInfo, relevanceScore]) => ({ + result.data.map(([docInfo, distance]) => ({ filename: docInfo.metadata.source.split('/').pop(), content: docInfo.page_content, - relevanceScore, + distance, })), ) - .sort((a, b) => b.relevanceScore - a.relevanceScore) - .slice(0, 5); + // TODO: results should be sorted by relevance, not distance + .sort((a, b) => a.distance - b.distance) + // TODO: make this configurable + .slice(0, 10); const formattedString = formattedResults .map( (result) => - `File: ${result.filename}\nRelevance: ${result.relevanceScore.toFixed(4)}\nContent: ${ + `File: ${result.filename}\nRelevance: ${1.0 - result.distance.toFixed(4)}\nContent: ${ result.content }\n`, ) diff --git a/api/server/services/Files/Local/crud.js b/api/server/services/Files/Local/crud.js index e004eab79e..97a067d794 100644 --- a/api/server/services/Files/Local/crud.js +++ b/api/server/services/Files/Local/crud.js @@ -175,6 +175,17 @@ const isValidPath = (req, base, subfolder, filepath) => { return normalizedFilepath.startsWith(normalizedBase); }; +/** + * @param {string} filepath + */ +const unlinkFile = async (filepath) => { + try { + await fs.promises.unlink(filepath); + } catch (error) { + logger.error('Error deleting file:', error); + } +}; + /** * Deletes a file from the filesystem. This function takes a file object, constructs the full path, and * verifies the path's validity before deleting the file. If the path is invalid, an error is thrown. @@ -217,7 +228,7 @@ const deleteLocalFile = async (req, file) => { throw new Error(`Invalid file path: ${file.filepath}`); } - await fs.promises.unlink(filepath); + await unlinkFile(filepath); return; } @@ -233,7 +244,7 @@ const deleteLocalFile = async (req, file) => { throw new Error('Invalid file path'); } - await fs.promises.unlink(filepath); + await unlinkFile(filepath); }; /** From 06282b584f0f960221220affd3db09d75cb782a3 Mon Sep 17 00:00:00 2001 From: Ruben Talstra Date: Tue, 18 Feb 2025 14:35:43 +0100 Subject: [PATCH 16/23] =?UTF-8?q?=F0=9F=93=9C=20ci:=20Automate`CHANGELOG.m?= =?UTF-8?q?d`=20(#5838)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: started with automated CHANGELOG.md * fix: no `configuration.json` found * refactor: `configuration.json` * fix: missing label `configuration.json` * fix: missing label `configuration.json` * fix: missing label `configuration.json` * fix: missing label `configuration.json` * fix: missing label `configuration.json` * ci: test new workflow action * ci: test new workflow action * ci: test new workflow action * feat: working CHANGELOG.md generation * feat: working CHANGELOG.md generation * feat: working CHANGELOG.md generation * feat: working CHANGELOG.md generation * feat: working CHANGELOG.md generation * feat: working CHANGELOG.md generation * feat: working CHANGELOG.md generation * fix: separate release and Unreleased workflows CHANGELOG.md generation * fix: separate release and Unreleased workflows CHANGELOG.md generation * fix: separate release and Unreleased workflows CHANGELOG.md generation * fix: separate release and Unreleased workflows CHANGELOG.md generation * fix: separate release and Unreleased workflows CHANGELOG.md generation * fix: separate release and Unreleased workflows CHANGELOG.md generation * fix: separate release and Unreleased workflows CHANGELOG.md generation * fix: separate release and Unreleased workflows CHANGELOG.md generation * fix: separate release and Unreleased workflows CHANGELOG.md generation * fix: separate release and Unreleased workflows CHANGELOG.md generation * fix: separate release and Unreleased workflows CHANGELOG.md generation * fix: separate release and Unreleased workflows CHANGELOG.md generation * fix: separate release and Unreleased workflows CHANGELOG.md generation * fix: separate release and Unreleased workflows CHANGELOG.md generation * fix: separate release and Unreleased workflows CHANGELOG.md generation * fix: separate release and Unreleased workflows CHANGELOG.md generation * fix: separate release and Unreleased workflows CHANGELOG.md generation * fix: separate release and Unreleased workflows CHANGELOG.md generation * fix: separate release and Unreleased workflows CHANGELOG.md generation * fix: separate release and Unreleased workflows CHANGELOG.md generation * fix: separate release and Unreleased workflows CHANGELOG.md generation * fix: separate release and Unreleased workflows CHANGELOG.md generation * fix: separate release and Unreleased workflows CHANGELOG.md generation * refactor: only trigger the `unreleased-changelog` action on push to `main` and `generate-release-changelog` only when pushing a tag with `v*.*.*` * refactor: Runs only every Monday at 00:00 UTC --- .github/configuration-release.json | 60 ++++++++++ .github/configuration-unreleased.json | 68 +++++++++++ .../generate-release-changelog-pr.yml | 94 ++++++++++++++++ .../generate-unreleased-changelog-pr.yml | 106 ++++++++++++++++++ 4 files changed, 328 insertions(+) create mode 100644 .github/configuration-release.json create mode 100644 .github/configuration-unreleased.json create mode 100644 .github/workflows/generate-release-changelog-pr.yml create mode 100644 .github/workflows/generate-unreleased-changelog-pr.yml diff --git a/.github/configuration-release.json b/.github/configuration-release.json new file mode 100644 index 0000000000..68fe80ed8f --- /dev/null +++ b/.github/configuration-release.json @@ -0,0 +1,60 @@ +{ + "categories": [ + { + "title": "### ✨ New Features", + "labels": ["feat"] + }, + { + "title": "### 🌍 Internationalization", + "labels": ["i18n"] + }, + { + "title": "### 👐 Accessibility", + "labels": ["a11y"] + }, + { + "title": "### 🔧 Fixes", + "labels": ["Fix", "fix"] + }, + { + "title": "### ⚙️ Other Changes", + "labels": ["ci", "style", "docs", "refactor", "chore"] + } + ], + "ignore_labels": [ + "🔁 duplicate", + "📊 analytics", + "🌱 good first issue", + "🔍 investigation", + "🙏 help wanted", + "❌ invalid", + "❓ question", + "🚫 wontfix", + "🚀 release", + "version" + ], + "base_branches": ["main"], + "sort": { + "order": "ASC", + "on_property": "mergedAt" + }, + "label_extractor": [ + { + "pattern": "^(?:[^A-Za-z0-9]*)(feat|fix|chore|docs|refactor|ci|style|a11y|i18n)\\s*:", + "target": "$1", + "flags": "i", + "on_property": "title", + "method": "match" + }, + { + "pattern": "^(?:[^A-Za-z0-9]*)(v\\d+\\.\\d+\\.\\d+(?:-rc\\d+)?).*", + "target": "version", + "flags": "i", + "on_property": "title", + "method": "match" + } + ], + "template": "## [#{{TO_TAG}}] - #{{TO_TAG_DATE}}\n\nChanges from #{{FROM_TAG}} to #{{TO_TAG}}.\n\n#{{CHANGELOG}}\n\n[See full release details][release-#{{TO_TAG}}]\n\n[release-#{{TO_TAG}}]: https://github.com/#{{OWNER}}/#{{REPO}}/releases/tag/#{{TO_TAG}}\n\n---", + "pr_template": "- #{{TITLE}} by **@#{{AUTHOR}}** in [##{{NUMBER}}](#{{URL}})", + "empty_template": "- no changes" +} \ No newline at end of file diff --git a/.github/configuration-unreleased.json b/.github/configuration-unreleased.json new file mode 100644 index 0000000000..29eaf5e13b --- /dev/null +++ b/.github/configuration-unreleased.json @@ -0,0 +1,68 @@ +{ + "categories": [ + { + "title": "### ✨ New Features", + "labels": ["feat"] + }, + { + "title": "### 🌍 Internationalization", + "labels": ["i18n"] + }, + { + "title": "### 👐 Accessibility", + "labels": ["a11y"] + }, + { + "title": "### 🔧 Fixes", + "labels": ["Fix", "fix"] + }, + { + "title": "### ⚙️ Other Changes", + "labels": ["ci", "style", "docs", "refactor", "chore"] + } + ], + "ignore_labels": [ + "🔁 duplicate", + "📊 analytics", + "🌱 good first issue", + "🔍 investigation", + "🙏 help wanted", + "❌ invalid", + "❓ question", + "🚫 wontfix", + "🚀 release", + "version", + "action" + ], + "base_branches": ["main"], + "sort": { + "order": "ASC", + "on_property": "mergedAt" + }, + "label_extractor": [ + { + "pattern": "^(?:[^A-Za-z0-9]*)(feat|fix|chore|docs|refactor|ci|style|a11y|i18n)\\s*:", + "target": "$1", + "flags": "i", + "on_property": "title", + "method": "match" + }, + { + "pattern": "^(?:[^A-Za-z0-9]*)(v\\d+\\.\\d+\\.\\d+(?:-rc\\d+)?).*", + "target": "version", + "flags": "i", + "on_property": "title", + "method": "match" + }, + { + "pattern": "^(?:[^A-Za-z0-9]*)(action)\\b.*", + "target": "action", + "flags": "i", + "on_property": "title", + "method": "match" + } + ], + "template": "## [Unreleased]\n\n#{{CHANGELOG}}\n\n---", + "pr_template": "- #{{TITLE}} by **@#{{AUTHOR}}** in [##{{NUMBER}}](#{{URL}})", + "empty_template": "- no changes" +} \ No newline at end of file diff --git a/.github/workflows/generate-release-changelog-pr.yml b/.github/workflows/generate-release-changelog-pr.yml new file mode 100644 index 0000000000..c3bceae9de --- /dev/null +++ b/.github/workflows/generate-release-changelog-pr.yml @@ -0,0 +1,94 @@ +name: Generate Release Changelog PR + +on: + push: + tags: + - 'v*.*.*' + +jobs: + generate-release-changelog-pr: + permissions: + contents: write # Needed for pushing commits and creating branches. + pull-requests: write + runs-on: ubuntu-latest + steps: + # 1. Checkout the repository (with full history). + - name: Checkout Repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # 2. Generate the release changelog using our custom configuration. + - name: Generate Release Changelog + id: generate_release + uses: mikepenz/release-changelog-builder-action@v5.1.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + configuration: ".github/configuration-release.json" + owner: ${{ github.repository_owner }} + repo: ${{ github.event.repository.name }} + outputFile: CHANGELOG-release.md + + # 3. Update the main CHANGELOG.md: + # - If it doesn't exist, create it with a basic header. + # - Remove the "Unreleased" section (if present). + # - Prepend the new release changelog above previous releases. + # - Remove all temporary files before committing. + - name: Update CHANGELOG.md + run: | + # Determine the release tag, e.g. "v1.2.3" + TAG=${GITHUB_REF##*/} + echo "Using release tag: $TAG" + + # Ensure CHANGELOG.md exists; if not, create a basic header. + if [ ! -f CHANGELOG.md ]; then + echo "# Changelog" > CHANGELOG.md + echo "" >> CHANGELOG.md + echo "All notable changes to this project will be documented in this file." >> CHANGELOG.md + echo "" >> CHANGELOG.md + fi + + echo "Updating CHANGELOG.md…" + + # Remove the "Unreleased" section (from "## [Unreleased]" until the first occurrence of '---') if it exists. + if grep -q "^## \[Unreleased\]" CHANGELOG.md; then + awk '/^## \[Unreleased\]/{flag=1} flag && /^---/{flag=0; next} !flag' CHANGELOG.md > CHANGELOG.cleaned + else + cp CHANGELOG.md CHANGELOG.cleaned + fi + + # Split the cleaned file into: + # - header.md: content before the first release header ("## [v..."). + # - tail.md: content from the first release header onward. + awk '/^## \[v/{exit} {print}' CHANGELOG.cleaned > header.md + awk 'f{print} /^## \[v/{f=1; print}' CHANGELOG.cleaned > tail.md + + # Combine header, the new release changelog, and the tail. + echo "Combining updated changelog parts..." + cat header.md CHANGELOG-release.md > CHANGELOG.md.new + echo "" >> CHANGELOG.md.new + cat tail.md >> CHANGELOG.md.new + + mv CHANGELOG.md.new CHANGELOG.md + + # Remove temporary files. + rm -f CHANGELOG.cleaned header.md tail.md CHANGELOG-release.md + + echo "Final CHANGELOG.md content:" + cat CHANGELOG.md + + # 4. Create (or update) the Pull Request with the updated CHANGELOG.md. + - name: Create Pull Request + uses: peter-evans/create-pull-request@v7 + with: + token: ${{ secrets.GITHUB_TOKEN }} + sign-commits: true + commit-message: "chore: update CHANGELOG for release ${GITHUB_REF##*/}" + base: main + branch: "changelog/${GITHUB_REF##*/}" + reviewers: danny-avila + title: "chore: update CHANGELOG for release ${GITHUB_REF##*/}" + body: | + **Description**: + - This PR updates the CHANGELOG.md by removing the "Unreleased" section and adding new release notes for release ${GITHUB_REF##*/} above previous releases. \ No newline at end of file diff --git a/.github/workflows/generate-unreleased-changelog-pr.yml b/.github/workflows/generate-unreleased-changelog-pr.yml new file mode 100644 index 0000000000..b130e4fb33 --- /dev/null +++ b/.github/workflows/generate-unreleased-changelog-pr.yml @@ -0,0 +1,106 @@ +name: Generate Unreleased Changelog PR + +on: + schedule: + - cron: "0 0 * * 1" # Runs every Monday at 00:00 UTC + +jobs: + generate-unreleased-changelog-pr: + permissions: + contents: write # Needed for pushing commits and creating branches. + pull-requests: write + runs-on: ubuntu-latest + steps: + # 1. Checkout the repository on main. + - name: Checkout Repository on Main + uses: actions/checkout@v4 + with: + ref: main + fetch-depth: 0 + + # 4. Get the latest version tag. + - name: Get Latest Tag + id: get_latest_tag + run: | + LATEST_TAG=$(git describe --tags $(git rev-list --tags --max-count=1) || echo "none") + echo "Latest tag: $LATEST_TAG" + echo "tag=$LATEST_TAG" >> $GITHUB_OUTPUT + + # 5. Generate the Unreleased changelog. + - name: Generate Unreleased Changelog + id: generate_unreleased + uses: mikepenz/release-changelog-builder-action@v5.1.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + configuration: ".github/configuration-unreleased.json" + owner: ${{ github.repository_owner }} + repo: ${{ github.event.repository.name }} + outputFile: CHANGELOG-unreleased.md + fromTag: ${{ steps.get_latest_tag.outputs.tag }} + toTag: main + + # 7. Update CHANGELOG.md with the new Unreleased section. + - name: Update CHANGELOG.md + id: update_changelog + run: | + # Create CHANGELOG.md if it doesn't exist. + if [ ! -f CHANGELOG.md ]; then + echo "# Changelog" > CHANGELOG.md + echo "" >> CHANGELOG.md + echo "All notable changes to this project will be documented in this file." >> CHANGELOG.md + echo "" >> CHANGELOG.md + fi + + echo "Updating CHANGELOG.md…" + + # Extract content before the "## [Unreleased]" (or first version header if missing). + if grep -q "^## \[Unreleased\]" CHANGELOG.md; then + awk '/^## \[Unreleased\]/{exit} {print}' CHANGELOG.md > CHANGELOG_TMP.md + else + awk '/^## \[v/{exit} {print}' CHANGELOG.md > CHANGELOG_TMP.md + fi + + # Append the generated Unreleased changelog. + echo "" >> CHANGELOG_TMP.md + cat CHANGELOG-unreleased.md >> CHANGELOG_TMP.md + echo "" >> CHANGELOG_TMP.md + + # Append the remainder of the original changelog (starting from the first version header). + awk 'f{print} /^## \[v/{f=1; print}' CHANGELOG.md >> CHANGELOG_TMP.md + + # Replace the old file with the updated file. + mv CHANGELOG_TMP.md CHANGELOG.md + + # Remove the temporary generated file. + rm -f CHANGELOG-unreleased.md + + echo "Final CHANGELOG.md:" + cat CHANGELOG.md + + # 8. Check if CHANGELOG.md has any updates. + - name: Check for CHANGELOG.md changes + id: changelog_changes + run: | + if git diff --quiet CHANGELOG.md; then + echo "has_changes=false" >> $GITHUB_OUTPUT + else + echo "has_changes=true" >> $GITHUB_OUTPUT + fi + + # 9. Create (or update) the Pull Request only if there are changes. + - name: Create Pull Request + if: steps.changelog_changes.outputs.has_changes == 'true' + uses: peter-evans/create-pull-request@v7 + with: + token: ${{ secrets.GITHUB_TOKEN }} + base: main + branch: "changelog/unreleased-update" + sign-commits: true + commit-message: "action: update Unreleased changelog" + title: "action: update Unreleased changelog" + body: | + **Description**: + - This PR updates the Unreleased section in CHANGELOG.md. + - It compares the current main branch with the latest version tag (determined as ${{ steps.get_latest_tag.outputs.tag }}), + regenerates the Unreleased changelog, removes any old Unreleased block, and inserts the new content. \ No newline at end of file From 538a2a144a7075c89574e2c87234ddc86ca80e4d Mon Sep 17 00:00:00 2001 From: Ruben Talstra Date: Wed, 19 Feb 2025 19:33:29 +0100 Subject: [PATCH 17/23] =?UTF-8?q?=F0=9F=94=92=20fix:=202FA=20Encrypt=20TOT?= =?UTF-8?q?P=20Secrets=20&=20Improve=20Docs=20(#5933)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🔒 fix: Integrate TOTP secret retrieval and encryption in Two-Factor Authentication * 🔒 refactor: Simplify TOTP verification by removing commented-out code --- api/server/controllers/TwoFactorController.js | 24 +++-- .../auth/TwoFactorAuthController.js | 10 +- api/server/services/twoFactorService.js | 91 +++++++++++++------ 3 files changed, 84 insertions(+), 41 deletions(-) diff --git a/api/server/controllers/TwoFactorController.js b/api/server/controllers/TwoFactorController.js index 3e8d38ac12..f145d69d92 100644 --- a/api/server/controllers/TwoFactorController.js +++ b/api/server/controllers/TwoFactorController.js @@ -3,9 +3,11 @@ const { verifyBackupCode, generateTOTPSecret, generateBackupCodes, + getTOTPSecret, } = require('~/server/services/twoFactorService'); const { updateUser, getUserById } = require('~/models'); const { logger } = require('~/config'); +const { encryptV2 } = require('~/server/utils/crypto'); const enable2FAController = async (req, res) => { const safeAppTitle = (process.env.APP_TITLE || 'LibreChat').replace(/\s+/g, ''); @@ -15,7 +17,8 @@ const enable2FAController = async (req, res) => { const secret = generateTOTPSecret(); const { plainCodes, codeObjects } = await generateBackupCodes(); - const user = await updateUser(userId, { totpSecret: secret, backupCodes: codeObjects }); + const encryptedSecret = await encryptV2(secret); + const user = await updateUser(userId, { totpSecret: encryptedSecret, backupCodes: codeObjects }); const otpauthUrl = `otpauth://totp/${safeAppTitle}:${user.email}?secret=${secret}&issuer=${safeAppTitle}`; @@ -38,14 +41,16 @@ const verify2FAController = async (req, res) => { return res.status(400).json({ message: '2FA not initiated' }); } - let verified = false; - if (token && (await verifyTOTP(user.totpSecret, token))) { + // Retrieve the plain TOTP secret using getTOTPSecret. + const secret = await getTOTPSecret(user.totpSecret); + + if (token && (await verifyTOTP(secret, token))) { return res.status(200).json(); } else if (backupCode) { - verified = await verifyBackupCode({ user, backupCode }); - } - if (verified) { - return res.status(200).json(); + const verified = await verifyBackupCode({ user, backupCode }); + if (verified) { + return res.status(200).json(); + } } return res.status(400).json({ message: 'Invalid token.' }); @@ -65,7 +70,10 @@ const confirm2FAController = async (req, res) => { return res.status(400).json({ message: '2FA not initiated' }); } - if (await verifyTOTP(user.totpSecret, token)) { + // Retrieve the plain TOTP secret using getTOTPSecret. + const secret = await getTOTPSecret(user.totpSecret); + + if (await verifyTOTP(secret, token)) { return res.status(200).json(); } diff --git a/api/server/controllers/auth/TwoFactorAuthController.js b/api/server/controllers/auth/TwoFactorAuthController.js index 37a8045829..78c5c0314e 100644 --- a/api/server/controllers/auth/TwoFactorAuthController.js +++ b/api/server/controllers/auth/TwoFactorAuthController.js @@ -1,5 +1,5 @@ const jwt = require('jsonwebtoken'); -const { verifyTOTP, verifyBackupCode } = require('~/server/services/twoFactorService'); +const { verifyTOTP, verifyBackupCode, getTOTPSecret } = require('~/server/services/twoFactorService'); const { setAuthTokens } = require('~/server/services/AuthService'); const { getUserById } = require('~/models/userMethods'); const { logger } = require('~/config'); @@ -24,9 +24,11 @@ const verify2FA = async (req, res) => { return res.status(400).json({ message: '2FA is not enabled for this user' }); } - let verified = false; + // Use the new getTOTPSecret function to retrieve (and decrypt if necessary) the TOTP secret. + const secret = await getTOTPSecret(user.totpSecret); - if (token && (await verifyTOTP(user.totpSecret, token))) { + let verified = false; + if (token && (await verifyTOTP(secret, token))) { verified = true; } else if (backupCode) { verified = await verifyBackupCode({ user, backupCode }); @@ -39,7 +41,7 @@ const verify2FA = async (req, res) => { // Prepare user data for response. // If the user is a plain object (from lean queries), we create a shallow copy. const userData = user.toObject ? user.toObject() : { ...user }; - // Remove sensitive fields + // Remove sensitive fields. delete userData.password; delete userData.__v; delete userData.totpSecret; diff --git a/api/server/services/twoFactorService.js b/api/server/services/twoFactorService.js index ac7247409c..e48b2ac938 100644 --- a/api/server/services/twoFactorService.js +++ b/api/server/services/twoFactorService.js @@ -1,14 +1,15 @@ const { sign } = require('jsonwebtoken'); const { webcrypto } = require('node:crypto'); -const { hashBackupCode } = require('~/server/utils/crypto'); +const { hashBackupCode, decryptV2 } = require('~/server/utils/crypto'); const { updateUser } = require('~/models/userMethods'); const BASE32_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; /** - * Encodes a Buffer into a Base32 string using RFC 4648 alphabet. + * Encodes a Buffer into a Base32 string using the RFC 4648 alphabet. + * * @param {Buffer} buffer - The buffer to encode. - * @returns {string} - The Base32 encoded string. + * @returns {string} The Base32 encoded string. */ const encodeBase32 = (buffer) => { let bits = 0; @@ -30,8 +31,9 @@ const encodeBase32 = (buffer) => { /** * Decodes a Base32-encoded string back into a Buffer. - * @param {string} base32Str - * @returns {Buffer} + * + * @param {string} base32Str - The Base32-encoded string. + * @returns {Buffer} The decoded buffer. */ const decodeBase32 = (base32Str) => { const cleaned = base32Str.replace(/=+$/, '').toUpperCase(); @@ -54,15 +56,20 @@ const decodeBase32 = (base32Str) => { }; /** - * Generate a temporary token for 2FA verification. - * This token is signed with JWT_SECRET and expires in 5 minutes. + * Generates a temporary token for 2FA verification. + * The token is signed with the JWT_SECRET and expires in 5 minutes. + * + * @param {string} userId - The unique identifier of the user. + * @returns {string} The signed JWT token. */ const generate2FATempToken = (userId) => sign({ userId, twoFAPending: true }, process.env.JWT_SECRET, { expiresIn: '5m' }); /** - * Generate a TOTP secret. - * Generates 10 random bytes using WebCrypto and encodes them into a Base32 string. + * Generates a TOTP secret. + * Creates 10 random bytes using WebCrypto and encodes them into a Base32 string. + * + * @returns {string} A Base32-encoded secret for TOTP. */ const generateTOTPSecret = () => { const randomArray = new Uint8Array(10); @@ -71,12 +78,12 @@ const generateTOTPSecret = () => { }; /** - * Generate a TOTP code based on the secret and current time. - * Uses a 30-second time step and generates a 6-digit code. + * Generates a Time-based One-Time Password (TOTP) based on the provided secret and time. + * This implementation uses a 30-second time step and produces a 6-digit code. * - * @param {string} secret - Base32-encoded secret - * @param {number} [forTime=Date.now()] - Time in milliseconds - * @returns {Promise} - The 6-digit TOTP code. + * @param {string} secret - The Base32-encoded TOTP secret. + * @param {number} [forTime=Date.now()] - The time (in milliseconds) for which to generate the TOTP. + * @returns {Promise} A promise that resolves to the 6-digit TOTP code. */ const generateTOTP = async (secret, forTime = Date.now()) => { const timeStep = 30; // seconds @@ -106,6 +113,7 @@ const generateTOTP = async (secret, forTime = Date.now()) => { const signatureBuffer = await webcrypto.subtle.sign('HMAC', cryptoKey, counterBuffer); const hmac = new Uint8Array(signatureBuffer); + // Dynamic truncation as per RFC 4226 const offset = hmac[hmac.length - 1] & 0xf; const slice = hmac.slice(offset, offset + 4); const view = new DataView(slice.buffer, slice.byteOffset, slice.byteLength); @@ -115,12 +123,12 @@ const generateTOTP = async (secret, forTime = Date.now()) => { }; /** - * Verify a provided TOTP token against the secret. - * Allows for a ±1 time-step window. + * Verifies a provided TOTP token against the secret. + * It allows for a ±1 time-step window to account for slight clock discrepancies. * - * @param {string} secret - * @param {string} token - * @returns {Promise} + * @param {string} secret - The Base32-encoded TOTP secret. + * @param {string} token - The TOTP token provided by the user. + * @returns {Promise} A promise that resolves to true if the token is valid; otherwise, false. */ const verifyTOTP = async (secret, token) => { const timeStepMS = 30 * 1000; @@ -135,12 +143,13 @@ const verifyTOTP = async (secret, token) => { }; /** - * Generate backup codes. - * Generates `count` backup code objects and returns an object with both plain codes - * (for one-time download) and their objects (for secure storage). Uses WebCrypto for randomness and hashing. + * Generates backup codes for two-factor authentication. + * Each backup code is an 8-character hexadecimal string along with its SHA-256 hash. + * The plain codes are returned for one-time download, while the hashed objects are meant for secure storage. * - * @param {number} count - Number of backup codes to generate (default: 10). - * @returns {Promise} - Contains `plainCodes` (array of strings) and `codeObjects` (array of objects). + * @param {number} [count=10] - The number of backup codes to generate. + * @returns {Promise<{ plainCodes: string[], codeObjects: Array<{ codeHash: string, used: boolean, usedAt: Date | null }> }>} + * A promise that resolves to an object containing both plain backup codes and their corresponding code objects. */ const generateBackupCodes = async (count = 10) => { const plainCodes = []; @@ -165,11 +174,12 @@ const generateBackupCodes = async (count = 10) => { }; /** - * Verifies a backup code and updates the user's backup codes if valid - * @param {Object} params - * @param {TUser | undefined} [params.user] - The user object - * @param {string | undefined} [params.backupCode] - The backup code to verify - * @returns {Promise} - Whether the backup code was valid + * Verifies a backup code for a user and updates its status as used if valid. + * + * @param {Object} params - The parameters object. + * @param {TUser | undefined} [params.user] - The user object containing backup codes. + * @param {string | undefined} [params.backupCode] - The backup code to verify. + * @returns {Promise} A promise that resolves to true if the backup code is valid and updated; otherwise, false. */ const verifyBackupCode = async ({ user, backupCode }) => { if (!backupCode || !user || !Array.isArray(user.backupCodes)) { @@ -195,9 +205,32 @@ const verifyBackupCode = async ({ user, backupCode }) => { return false; }; +/** + * Retrieves and, if necessary, decrypts a stored TOTP secret. + * If the secret contains a colon, it is assumed to be in the format "iv:encryptedData" and will be decrypted. + * If the secret is exactly 16 characters long, it is assumed to be a legacy plain secret. + * + * @param {string|null} storedSecret - The stored TOTP secret (which may be encrypted). + * @returns {Promise} A promise that resolves to the plain TOTP secret, or null if none is provided. + */ +const getTOTPSecret = async (storedSecret) => { + if (!storedSecret) { return null; } + // Check for a colon marker (encrypted secrets are stored as "iv:encryptedData") + if (storedSecret.includes(':')) { + return await decryptV2(storedSecret); + } + // If it's exactly 16 characters, assume it's already plain (legacy secret) + if (storedSecret.length === 16) { + return storedSecret; + } + // Fallback in case it doesn't meet our criteria. + return storedSecret; +}; + module.exports = { verifyTOTP, generateTOTP, + getTOTPSecret, verifyBackupCode, generateTOTPSecret, generateBackupCodes, From fdb3cf3f583c63c7bef67c267878d61eeec0553e Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Wed, 19 Feb 2025 14:53:22 -0500 Subject: [PATCH 18/23] =?UTF-8?q?=F0=9F=94=A7=20fix:=20Resizable=20Panel?= =?UTF-8?q?=20Unmount=20Error=20&=20Code=20Env.=20File=20Re-Upload=20(#594?= =?UTF-8?q?7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🔧 refactor: handle full path for code env. file re-upload * fix: update react-resizable-panels to version 2.1.7 to resolve error thrown on unmount of artifacts; ref: https://github.com/bvaughn/react-resizable-panels/issues/372 * refactor: replace promptPrefix with systemMessage in GoogleClient for improved clarity, and to prevent saving LibreChat feature-specific instructions to the user's custom instructions --- api/app/clients/GoogleClient.js | 20 +++++++++++--------- api/server/services/Files/Code/process.js | 2 +- api/server/services/Files/Firebase/crud.js | 3 ++- api/server/services/Files/Local/crud.js | 22 +++++++++++++++++++++- client/package.json | 6 +++--- package-lock.json | 20 ++++++++++---------- 6 files changed, 48 insertions(+), 25 deletions(-) diff --git a/api/app/clients/GoogleClient.js b/api/app/clients/GoogleClient.js index 03461a6796..a2ca02558b 100644 --- a/api/app/clients/GoogleClient.js +++ b/api/app/clients/GoogleClient.js @@ -51,7 +51,7 @@ class GoogleClient extends BaseClient { const serviceKey = creds[AuthKeys.GOOGLE_SERVICE_KEY] ?? {}; this.serviceKey = - serviceKey && typeof serviceKey === 'string' ? JSON.parse(serviceKey) : serviceKey ?? {}; + serviceKey && typeof serviceKey === 'string' ? JSON.parse(serviceKey) : (serviceKey ?? {}); /** @type {string | null | undefined} */ this.project_id = this.serviceKey.project_id; this.client_email = this.serviceKey.client_email; @@ -73,6 +73,8 @@ class GoogleClient extends BaseClient { * @type {string} */ this.outputTokensKey = 'output_tokens'; this.visionMode = VisionModes.generative; + /** @type {string} */ + this.systemMessage; if (options.skipSetOptions) { return; } @@ -184,7 +186,7 @@ class GoogleClient extends BaseClient { if (typeof this.options.artifactsPrompt === 'string' && this.options.artifactsPrompt) { promptPrefix = `${promptPrefix ?? ''}\n${this.options.artifactsPrompt}`.trim(); } - this.options.promptPrefix = promptPrefix; + this.systemMessage = promptPrefix; this.initializeClient(); return this; } @@ -314,7 +316,7 @@ class GoogleClient extends BaseClient { } this.augmentedPrompt = await this.contextHandlers.createContext(); - this.options.promptPrefix = this.augmentedPrompt + this.options.promptPrefix; + this.systemMessage = this.augmentedPrompt + this.systemMessage; } } @@ -361,8 +363,8 @@ class GoogleClient extends BaseClient { throw new Error('[GoogleClient] PaLM 2 and Codey models are no longer supported.'); } - if (this.options.promptPrefix) { - const instructionsTokenCount = this.getTokenCount(this.options.promptPrefix); + if (this.systemMessage) { + const instructionsTokenCount = this.getTokenCount(this.systemMessage); this.maxContextTokens = this.maxContextTokens - instructionsTokenCount; if (this.maxContextTokens < 0) { @@ -417,8 +419,8 @@ class GoogleClient extends BaseClient { ], }; - if (this.options.promptPrefix) { - payload.instances[0].context = this.options.promptPrefix; + if (this.systemMessage) { + payload.instances[0].context = this.systemMessage; } logger.debug('[GoogleClient] buildMessages', payload); @@ -464,7 +466,7 @@ class GoogleClient extends BaseClient { identityPrefix = `${identityPrefix}\nYou are ${this.options.modelLabel}`; } - let promptPrefix = (this.options.promptPrefix ?? '').trim(); + let promptPrefix = (this.systemMessage ?? '').trim(); if (identityPrefix) { promptPrefix = `${identityPrefix}${promptPrefix}`; @@ -648,7 +650,7 @@ class GoogleClient extends BaseClient { generationConfig: googleGenConfigSchema.parse(this.modelOptions), }; - const promptPrefix = (this.options.promptPrefix ?? '').trim(); + const promptPrefix = (this.systemMessage ?? '').trim(); if (promptPrefix.length) { requestOptions.systemInstruction = { parts: [ diff --git a/api/server/services/Files/Code/process.js b/api/server/services/Files/Code/process.js index ce8acf4ad3..c92e628589 100644 --- a/api/server/services/Files/Code/process.js +++ b/api/server/services/Files/Code/process.js @@ -209,7 +209,7 @@ const primeFiles = async (options, apiKey) => { const { handleFileUpload: uploadCodeEnvFile } = getStrategyFunctions( FileSources.execute_code, ); - const stream = await getDownloadStream(file.filepath); + const stream = await getDownloadStream(options.req, file.filepath); const fileIdentifier = await uploadCodeEnvFile({ req: options.req, stream, diff --git a/api/server/services/Files/Firebase/crud.js b/api/server/services/Files/Firebase/crud.js index 76a6c1d8d4..8319f908ef 100644 --- a/api/server/services/Files/Firebase/crud.js +++ b/api/server/services/Files/Firebase/crud.js @@ -224,10 +224,11 @@ async function uploadFileToFirebase({ req, file, file_id }) { /** * Retrieves a readable stream for a file from Firebase storage. * + * @param {ServerRequest} _req * @param {string} filepath - The filepath. * @returns {Promise} A readable stream of the file. */ -async function getFirebaseFileStream(filepath) { +async function getFirebaseFileStream(_req, filepath) { try { const storage = getFirebaseStorage(); if (!storage) { diff --git a/api/server/services/Files/Local/crud.js b/api/server/services/Files/Local/crud.js index 97a067d794..c2bb75c125 100644 --- a/api/server/services/Files/Local/crud.js +++ b/api/server/services/Files/Local/crud.js @@ -286,11 +286,31 @@ async function uploadLocalFile({ req, file, file_id }) { /** * Retrieves a readable stream for a file from local storage. * + * @param {ServerRequest} req - The request object from Express * @param {string} filepath - The filepath. * @returns {ReadableStream} A readable stream of the file. */ -function getLocalFileStream(filepath) { +function getLocalFileStream(req, filepath) { try { + if (filepath.includes('/uploads/')) { + const basePath = filepath.split('/uploads/')[1]; + + if (!basePath) { + logger.warn(`Invalid base path: ${filepath}`); + throw new Error(`Invalid file path: ${filepath}`); + } + + const fullPath = path.join(req.app.locals.paths.uploads, basePath); + const uploadsDir = req.app.locals.paths.uploads; + + const rel = path.relative(uploadsDir, fullPath); + if (rel.startsWith('..') || path.isAbsolute(rel) || rel.includes(`..${path.sep}`)) { + logger.warn(`Invalid relative file path: ${filepath}`); + throw new Error(`Invalid file path: ${filepath}`); + } + + return fs.createReadStream(fullPath); + } return fs.createReadStream(filepath); } catch (error) { logger.error('Error getting local file stream:', error); diff --git a/client/package.json b/client/package.json index 917333ce25..df85c2521c 100644 --- a/client/package.json +++ b/client/package.json @@ -66,8 +66,8 @@ "html-to-image": "^1.11.11", "i18next": "^24.2.2", "i18next-browser-languagedetector": "^8.0.3", - "js-cookie": "^3.0.5", "input-otp": "^1.4.2", + "js-cookie": "^3.0.5", "librechat-data-provider": "*", "lodash": "^4.17.21", "lucide-react": "^0.394.0", @@ -86,7 +86,7 @@ "react-i18next": "^15.4.0", "react-lazy-load-image-component": "^1.6.0", "react-markdown": "^9.0.1", - "react-resizable-panels": "^2.1.1", + "react-resizable-panels": "^2.1.7", "react-router-dom": "^6.11.2", "react-speech-recognition": "^3.10.0", "react-textarea-autosize": "^8.4.0", @@ -144,4 +144,4 @@ "vite-plugin-node-polyfills": "^0.17.0", "vite-plugin-pwa": "^0.21.1" } -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index 802cedd19e..ef573d7010 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1095,7 +1095,7 @@ "react-i18next": "^15.4.0", "react-lazy-load-image-component": "^1.6.0", "react-markdown": "^9.0.1", - "react-resizable-panels": "^2.1.1", + "react-resizable-panels": "^2.1.7", "react-router-dom": "^6.11.2", "react-speech-recognition": "^3.10.0", "react-textarea-autosize": "^8.4.0", @@ -1609,6 +1609,15 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "client/node_modules/react-resizable-panels": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.1.7.tgz", + "integrity": "sha512-JtT6gI+nURzhMYQYsx8DKkx6bSoOGFp7A3CwMrOb8y5jFHFyqwo9m68UhmXRw57fRVJksFn1TSlm3ywEQ9vMgA==", + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "client/node_modules/rollup": { "version": "4.34.6", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.6.tgz", @@ -31919,15 +31928,6 @@ } } }, - "node_modules/react-resizable-panels": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.1.1.tgz", - "integrity": "sha512-+cUV/yZBYfiBj+WJtpWDJ3NtR4zgDZfHt3+xtaETKE+FCvp+RK/NJxacDQKxMHgRUTSkfA6AnGljQ5QZNsCQoA==", - "peerDependencies": { - "react": "^16.14.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/react-router": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.0.tgz", From fe7013562b215e574e2ab245c58a6e89ede84017 Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Thu, 20 Feb 2025 22:17:43 +0100 Subject: [PATCH 19/23] =?UTF-8?q?=E2=9C=A8=20style:=20Enhance=20Styling=20?= =?UTF-8?q?&=20Accessibility=20(#5956)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ feat: Enhance UI Components with Shadows and Accessibility Improvements * 🔧 fix: Correct Category Labels and Values in API Model & Adjust Button Class in Prompt List --- api/models/Categories.js | 36 ++++++++-------- client/src/components/Prompts/Command.tsx | 2 +- client/src/components/Prompts/Description.tsx | 2 +- client/src/components/Prompts/Groups/List.tsx | 2 +- .../src/components/Prompts/PromptEditor.tsx | 3 +- client/src/components/Prompts/PromptForm.tsx | 3 +- .../components/Prompts/PromptVariables.tsx | 4 +- .../SidePanel/Agents/ModelPanel.tsx | 13 +++++- client/src/components/ui/SelectDropDown.tsx | 41 ++++++++++--------- 9 files changed, 59 insertions(+), 47 deletions(-) diff --git a/api/models/Categories.js b/api/models/Categories.js index 605b68d176..6fb88fb995 100644 --- a/api/models/Categories.js +++ b/api/models/Categories.js @@ -3,40 +3,40 @@ const { logger } = require('~/config'); const options = [ { - label: 'idea', - value: 'com_ui_idea', + label: 'com_ui_idea', + value: 'idea', }, { - label: 'travel', - value: 'com_ui_travel', + label: 'com_ui_travel', + value: 'travel', }, { - label: 'teach_or_explain', - value: 'com_ui_teach_or_explain', + label: 'com_ui_teach_or_explain', + value: 'teach_or_explain', }, { - label: 'write', - value: 'com_ui_write', + label: 'com_ui_write', + value: 'write', }, { - label: 'shop', - value: 'com_ui_shop', + label: 'com_ui_shop', + value: 'shop', }, { - label: 'code', - value: 'com_ui_code', + label: 'com_ui_code', + value: 'code', }, { - label: 'misc', - value: 'com_ui_misc', + label: 'com_ui_misc', + value: 'misc', }, { - label: 'roleplay', - value: 'com_ui_roleplay', + label: 'com_ui_roleplay', + value: 'roleplay', }, { - label: 'finance', - value: 'com_ui_finance', + label: 'com_ui_finance', + value: 'finance', }, ]; diff --git a/client/src/components/Prompts/Command.tsx b/client/src/components/Prompts/Command.tsx index f64fee4546..e670410ae8 100644 --- a/client/src/components/Prompts/Command.tsx +++ b/client/src/components/Prompts/Command.tsx @@ -44,7 +44,7 @@ const Command = ({ } return ( -
+