diff --git a/backend/apps/webui/internal/migrations/017_add_user_oauth_sub.py b/backend/apps/webui/internal/migrations/017_add_user_oauth_sub.py new file mode 100644 index 000000000..fd1d9b560 --- /dev/null +++ b/backend/apps/webui/internal/migrations/017_add_user_oauth_sub.py @@ -0,0 +1,49 @@ +"""Peewee migrations -- 017_add_user_oauth_sub.py. + +Some examples (model - class or model name):: + + > Model = migrator.orm['table_name'] # Return model in current state by name + > Model = migrator.ModelClass # Return model in current state by name + + > migrator.sql(sql) # Run custom SQL + > migrator.run(func, *args, **kwargs) # Run python function with the given args + > migrator.create_model(Model) # Create a model (could be used as decorator) + > migrator.remove_model(model, cascade=True) # Remove a model + > migrator.add_fields(model, **fields) # Add fields to a model + > migrator.change_fields(model, **fields) # Change fields + > migrator.remove_fields(model, *field_names, cascade=True) + > migrator.rename_field(model, old_field_name, new_field_name) + > migrator.rename_table(model, new_table_name) + > migrator.add_index(model, *col_names, unique=False) + > migrator.add_not_null(model, *field_names) + > migrator.add_default(model, field_name, default) + > migrator.add_constraint(model, name, sql) + > migrator.drop_index(model, *col_names) + > migrator.drop_not_null(model, *field_names) + > migrator.drop_constraints(model, *constraints) + +""" + +from contextlib import suppress + +import peewee as pw +from peewee_migrate import Migrator + + +with suppress(ImportError): + import playhouse.postgres_ext as pw_pext + + +def migrate(migrator: Migrator, database: pw.Database, *, fake=False): + """Write your migrations here.""" + + migrator.add_fields( + "user", + oauth_sub=pw.TextField(null=True, unique=True), + ) + + +def rollback(migrator: Migrator, database: pw.Database, *, fake=False): + """Write your rollback migrations here.""" + + migrator.remove_fields("user", "oauth_sub") diff --git a/backend/apps/webui/main.py b/backend/apps/webui/main.py index f50d1dc36..28b1b4aac 100644 --- a/backend/apps/webui/main.py +++ b/backend/apps/webui/main.py @@ -2,6 +2,8 @@ from fastapi import FastAPI, Depends from fastapi.routing import APIRoute from fastapi.responses import StreamingResponse from fastapi.middleware.cors import CORSMiddleware +from starlette.middleware.sessions import SessionMiddleware + from apps.webui.routers import ( auths, users, diff --git a/backend/apps/webui/models/auths.py b/backend/apps/webui/models/auths.py index e3b659e43..9ea38abcb 100644 --- a/backend/apps/webui/models/auths.py +++ b/backend/apps/webui/models/auths.py @@ -105,6 +105,7 @@ class AuthsTable: name: str, profile_image_url: str = "/user.png", role: str = "pending", + oauth_sub: Optional[str] = None, ) -> Optional[UserModel]: log.info("insert_new_auth") @@ -115,7 +116,9 @@ class AuthsTable: ) result = Auth.create(**auth.model_dump()) - user = Users.insert_new_user(id, name, email, profile_image_url, role) + user = Users.insert_new_user( + id, name, email, profile_image_url, role, oauth_sub + ) if result and user: return user diff --git a/backend/apps/webui/models/users.py b/backend/apps/webui/models/users.py index 485a9eea4..e3e1842b8 100644 --- a/backend/apps/webui/models/users.py +++ b/backend/apps/webui/models/users.py @@ -28,6 +28,8 @@ class User(Model): settings = JSONField(null=True) info = JSONField(null=True) + oauth_sub = TextField(null=True, unique=True) + class Meta: database = DB @@ -53,6 +55,8 @@ class UserModel(BaseModel): settings: Optional[UserSettings] = None info: Optional[dict] = None + oauth_sub: Optional[str] = None + #################### # Forms @@ -83,6 +87,7 @@ class UsersTable: email: str, profile_image_url: str = "/user.png", role: str = "pending", + oauth_sub: Optional[str] = None, ) -> Optional[UserModel]: user = UserModel( **{ @@ -94,6 +99,7 @@ class UsersTable: "last_active_at": int(time.time()), "created_at": int(time.time()), "updated_at": int(time.time()), + "oauth_sub": oauth_sub, } ) result = User.create(**user.model_dump()) @@ -123,6 +129,13 @@ class UsersTable: except: return None + def get_user_by_oauth_sub(self, sub: str) -> Optional[UserModel]: + try: + user = User.get(User.oauth_sub == sub) + return UserModel(**model_to_dict(user)) + except: + return None + def get_users(self, skip: int = 0, limit: int = 50) -> List[UserModel]: return [ UserModel(**model_to_dict(user)) @@ -174,6 +187,18 @@ class UsersTable: except: return None + def update_user_oauth_sub_by_id( + self, id: str, oauth_sub: str + ) -> Optional[UserModel]: + try: + query = User.update(oauth_sub=oauth_sub).where(User.id == id) + query.execute() + + user = User.get(User.id == id) + return UserModel(**model_to_dict(user)) + except: + return None + def update_user_by_id(self, id: str, updated: dict) -> Optional[UserModel]: try: query = User.update(**updated).where(User.id == id) diff --git a/backend/apps/webui/routers/auths.py b/backend/apps/webui/routers/auths.py index 586a2a8a3..1be79d259 100644 --- a/backend/apps/webui/routers/auths.py +++ b/backend/apps/webui/routers/auths.py @@ -10,7 +10,6 @@ import re import uuid import csv - from apps.webui.models.auths import ( SigninForm, SignupForm, diff --git a/backend/config.py b/backend/config.py index 2b78cc252..3ccc15dca 100644 --- a/backend/config.py +++ b/backend/config.py @@ -305,6 +305,135 @@ JWT_EXPIRES_IN = PersistentConfig( "JWT_EXPIRES_IN", "auth.jwt_expiry", os.environ.get("JWT_EXPIRES_IN", "-1") ) +#################################### +# OAuth config +#################################### + +ENABLE_OAUTH_SIGNUP = PersistentConfig( + "ENABLE_OAUTH_SIGNUP", + "oauth.enable_signup", + os.environ.get("ENABLE_OAUTH_SIGNUP", "False").lower() == "true", +) + +OAUTH_MERGE_ACCOUNTS_BY_EMAIL = PersistentConfig( + "OAUTH_MERGE_ACCOUNTS_BY_EMAIL", + "oauth.merge_accounts_by_email", + os.environ.get("OAUTH_MERGE_ACCOUNTS_BY_EMAIL", "False").lower() == "true", +) + +OAUTH_PROVIDERS = {} + +GOOGLE_CLIENT_ID = PersistentConfig( + "GOOGLE_CLIENT_ID", + "oauth.google.client_id", + os.environ.get("GOOGLE_CLIENT_ID", ""), +) + +GOOGLE_CLIENT_SECRET = PersistentConfig( + "GOOGLE_CLIENT_SECRET", + "oauth.google.client_secret", + os.environ.get("GOOGLE_CLIENT_SECRET", ""), +) + +GOOGLE_OAUTH_SCOPE = PersistentConfig( + "GOOGLE_OAUTH_SCOPE", + "oauth.google.scope", + os.environ.get("GOOGLE_OAUTH_SCOPE", "openid email profile"), +) + +MICROSOFT_CLIENT_ID = PersistentConfig( + "MICROSOFT_CLIENT_ID", + "oauth.microsoft.client_id", + os.environ.get("MICROSOFT_CLIENT_ID", ""), +) + +MICROSOFT_CLIENT_SECRET = PersistentConfig( + "MICROSOFT_CLIENT_SECRET", + "oauth.microsoft.client_secret", + os.environ.get("MICROSOFT_CLIENT_SECRET", ""), +) + +MICROSOFT_CLIENT_TENANT_ID = PersistentConfig( + "MICROSOFT_CLIENT_TENANT_ID", + "oauth.microsoft.tenant_id", + os.environ.get("MICROSOFT_CLIENT_TENANT_ID", ""), +) + +MICROSOFT_OAUTH_SCOPE = PersistentConfig( + "MICROSOFT_OAUTH_SCOPE", + "oauth.microsoft.scope", + os.environ.get("MICROSOFT_OAUTH_SCOPE", "openid email profile"), +) + +OAUTH_CLIENT_ID = PersistentConfig( + "OAUTH_CLIENT_ID", + "oauth.oidc.client_id", + os.environ.get("OAUTH_CLIENT_ID", ""), +) + +OAUTH_CLIENT_SECRET = PersistentConfig( + "OAUTH_CLIENT_SECRET", + "oauth.oidc.client_secret", + os.environ.get("OAUTH_CLIENT_SECRET", ""), +) + +OPENID_PROVIDER_URL = PersistentConfig( + "OPENID_PROVIDER_URL", + "oauth.oidc.provider_url", + os.environ.get("OPENID_PROVIDER_URL", ""), +) + +OAUTH_SCOPES = PersistentConfig( + "OAUTH_SCOPES", + "oauth.oidc.scopes", + os.environ.get("OAUTH_SCOPES", "openid email profile"), +) + +OAUTH_PROVIDER_NAME = PersistentConfig( + "OAUTH_PROVIDER_NAME", + "oauth.oidc.provider_name", + os.environ.get("OAUTH_PROVIDER_NAME", "SSO"), +) + + +def load_oauth_providers(): + OAUTH_PROVIDERS.clear() + if GOOGLE_CLIENT_ID.value and GOOGLE_CLIENT_SECRET.value: + OAUTH_PROVIDERS["google"] = { + "client_id": GOOGLE_CLIENT_ID.value, + "client_secret": GOOGLE_CLIENT_SECRET.value, + "server_metadata_url": "https://accounts.google.com/.well-known/openid-configuration", + "scope": GOOGLE_OAUTH_SCOPE.value, + } + + if ( + MICROSOFT_CLIENT_ID.value + and MICROSOFT_CLIENT_SECRET.value + and MICROSOFT_CLIENT_TENANT_ID.value + ): + OAUTH_PROVIDERS["microsoft"] = { + "client_id": MICROSOFT_CLIENT_ID.value, + "client_secret": MICROSOFT_CLIENT_SECRET.value, + "server_metadata_url": f"https://login.microsoftonline.com/{MICROSOFT_CLIENT_TENANT_ID.value}/v2.0/.well-known/openid-configuration", + "scope": MICROSOFT_OAUTH_SCOPE.value, + } + + if ( + OAUTH_CLIENT_ID.value + and OAUTH_CLIENT_SECRET.value + and OPENID_PROVIDER_URL.value + ): + OAUTH_PROVIDERS["oidc"] = { + "client_id": OAUTH_CLIENT_ID.value, + "client_secret": OAUTH_CLIENT_SECRET.value, + "server_metadata_url": OPENID_PROVIDER_URL.value, + "scope": OAUTH_SCOPES.value, + "name": OAUTH_PROVIDER_NAME.value, + } + + +load_oauth_providers() + #################################### # Static DIR #################################### @@ -733,6 +862,16 @@ WEBUI_SECRET_KEY = os.environ.get( ), # DEPRECATED: remove at next major version ) +WEBUI_SESSION_COOKIE_SAME_SITE = os.environ.get( + "WEBUI_SESSION_COOKIE_SAME_SITE", + os.environ.get("WEBUI_SESSION_COOKIE_SAME_SITE", "lax"), +) + +WEBUI_SESSION_COOKIE_SECURE = os.environ.get( + "WEBUI_SESSION_COOKIE_SECURE", + os.environ.get("WEBUI_SESSION_COOKIE_SECURE", "false").lower() == "true", +) + if WEBUI_AUTH and WEBUI_SECRET_KEY == "": raise ValueError(ERROR_MESSAGES.ENV_VAR_NOT_FOUND) diff --git a/backend/main.py b/backend/main.py index 426805276..52da33155 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,4 +1,9 @@ +import base64 +import uuid from contextlib import asynccontextmanager + +from authlib.integrations.starlette_client import OAuth +from authlib.oidc.core import UserInfo from bs4 import BeautifulSoup import json import markdown @@ -24,7 +29,8 @@ from fastapi.middleware.wsgi import WSGIMiddleware from fastapi.middleware.cors import CORSMiddleware from starlette.exceptions import HTTPException as StarletteHTTPException from starlette.middleware.base import BaseHTTPMiddleware -from starlette.responses import StreamingResponse, Response +from starlette.middleware.sessions import SessionMiddleware +from starlette.responses import StreamingResponse, Response, RedirectResponse from apps.socket.main import app as socket_app @@ -53,9 +59,11 @@ from apps.webui.main import ( from pydantic import BaseModel from typing import List, Optional, Iterator, Generator, Union +from apps.webui.models.auths import Auths from apps.webui.models.models import Models, ModelModel from apps.webui.models.tools import Tools from apps.webui.models.functions import Functions +from apps.webui.models.users import Users from apps.webui.utils import load_toolkit_module_by_id, load_function_module_by_id @@ -64,6 +72,8 @@ from utils.utils import ( get_verified_user, get_current_user, get_http_authorization_cred, + get_password_hash, + create_token, ) from utils.task import ( title_generation_template, @@ -74,6 +84,7 @@ from utils.misc import ( get_last_user_message, add_or_update_system_message, stream_message_template, + parse_duration, ) from apps.rag.utils import get_rag_context, rag_template @@ -106,9 +117,16 @@ from config import ( SEARCH_QUERY_PROMPT_LENGTH_THRESHOLD, TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE, SAFE_MODE, + OAUTH_PROVIDERS, + ENABLE_OAUTH_SIGNUP, + OAUTH_MERGE_ACCOUNTS_BY_EMAIL, + WEBUI_SECRET_KEY, + WEBUI_SESSION_COOKIE_SAME_SITE, + WEBUI_SESSION_COOKIE_SECURE, AppConfig, ) -from constants import ERROR_MESSAGES +from constants import ERROR_MESSAGES, WEBHOOK_MESSAGES +from utils.webhook import post_webhook if SAFE_MODE: print("SAFE MODE ENABLED") @@ -1725,6 +1743,12 @@ async def get_app_config(): "engine": audio_app.state.config.STT_ENGINE, }, }, + "oauth": { + "providers": { + name: config.get("name", name) + for name, config in OAUTH_PROVIDERS.items() + } + }, } @@ -1806,6 +1830,154 @@ async def get_app_latest_release_version(): ) +############################ +# OAuth Login & Callback +############################ + +oauth = OAuth() + +for provider_name, provider_config in OAUTH_PROVIDERS.items(): + oauth.register( + name=provider_name, + client_id=provider_config["client_id"], + client_secret=provider_config["client_secret"], + server_metadata_url=provider_config["server_metadata_url"], + client_kwargs={ + "scope": provider_config["scope"], + }, + ) + +# SessionMiddleware is used by authlib for oauth +if len(OAUTH_PROVIDERS) > 0: + app.add_middleware( + SessionMiddleware, + secret_key=WEBUI_SECRET_KEY, + session_cookie="oui-session", + same_site=WEBUI_SESSION_COOKIE_SAME_SITE, + https_only=WEBUI_SESSION_COOKIE_SECURE, + ) + + +@app.get("/oauth/{provider}/login") +async def oauth_login(provider: str, request: Request): + if provider not in OAUTH_PROVIDERS: + raise HTTPException(404) + redirect_uri = request.url_for("oauth_callback", provider=provider) + return await oauth.create_client(provider).authorize_redirect(request, redirect_uri) + + +# OAuth login logic is as follows: +# 1. Attempt to find a user with matching subject ID, tied to the provider +# 2. If OAUTH_MERGE_ACCOUNTS_BY_EMAIL is true, find a user with the email address provided via OAuth +# - This is considered insecure in general, as OAuth providers do not always verify email addresses +# 3. If there is no user, and ENABLE_OAUTH_SIGNUP is true, create a user +# - Email addresses are considered unique, so we fail registration if the email address is alreayd taken +@app.get("/oauth/{provider}/callback") +async def oauth_callback(provider: str, request: Request, response: Response): + if provider not in OAUTH_PROVIDERS: + raise HTTPException(404) + client = oauth.create_client(provider) + try: + token = await client.authorize_access_token(request) + except Exception as e: + log.warning(f"OAuth callback error: {e}") + raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED) + user_data: UserInfo = token["userinfo"] + + sub = user_data.get("sub") + if not sub: + log.warning(f"OAuth callback failed, sub is missing: {user_data}") + raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED) + provider_sub = f"{provider}@{sub}" + email = user_data.get("email", "").lower() + # We currently mandate that email addresses are provided + if not email: + log.warning(f"OAuth callback failed, email is missing: {user_data}") + raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED) + + # Check if the user exists + user = Users.get_user_by_oauth_sub(provider_sub) + + if not user: + # If the user does not exist, check if merging is enabled + if OAUTH_MERGE_ACCOUNTS_BY_EMAIL.value: + # Check if the user exists by email + user = Users.get_user_by_email(email) + if user: + # Update the user with the new oauth sub + Users.update_user_oauth_sub_by_id(user.id, provider_sub) + + if not user: + # If the user does not exist, check if signups are enabled + if ENABLE_OAUTH_SIGNUP.value: + # Check if an existing user with the same email already exists + existing_user = Users.get_user_by_email(user_data.get("email", "").lower()) + if existing_user: + raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN) + + picture_url = user_data.get("picture", "") + if picture_url: + # Download the profile image into a base64 string + try: + async with aiohttp.ClientSession() as session: + async with session.get(picture_url) as resp: + picture = await resp.read() + base64_encoded_picture = base64.b64encode(picture).decode( + "utf-8" + ) + guessed_mime_type = mimetypes.guess_type(picture_url)[0] + if guessed_mime_type is None: + # assume JPG, browsers are tolerant enough of image formats + guessed_mime_type = "image/jpeg" + picture_url = f"data:{guessed_mime_type};base64,{base64_encoded_picture}" + except Exception as e: + log.error(f"Error downloading profile image '{picture_url}': {e}") + picture_url = "" + if not picture_url: + picture_url = "/user.png" + user = Auths.insert_new_auth( + email=email, + password=get_password_hash( + str(uuid.uuid4()) + ), # Random password, not used + name=user_data.get("name", "User"), + profile_image_url=picture_url, + role=webui_app.state.config.DEFAULT_USER_ROLE, + oauth_sub=provider_sub, + ) + + if webui_app.state.config.WEBHOOK_URL: + post_webhook( + webui_app.state.config.WEBHOOK_URL, + WEBHOOK_MESSAGES.USER_SIGNUP(user.name), + { + "action": "signup", + "message": WEBHOOK_MESSAGES.USER_SIGNUP(user.name), + "user": user.model_dump_json(exclude_none=True), + }, + ) + else: + raise HTTPException( + status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED + ) + + jwt_token = create_token( + data={"id": user.id}, + expires_delta=parse_duration(webui_app.state.config.JWT_EXPIRES_IN), + ) + + # Set the cookie token + response.set_cookie( + key="token", + value=token, + httponly=True, # Ensures the cookie is not accessible via JavaScript + ) + + # Redirect back to the frontend with the JWT token + redirect_url = f"{request.base_url}auth#token={jwt_token}" + return RedirectResponse(url=redirect_url) + + @app.get("/manifest.json") async def get_manifest_json(): return { diff --git a/backend/requirements.txt b/backend/requirements.txt index 690425613..a36af5497 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -58,6 +58,7 @@ rank-bm25==0.2.2 faster-whisper==1.0.2 PyJWT[crypto]==2.8.0 +authlib==1.3.0 black==24.4.2 langfuse==2.33.0 diff --git a/pyproject.toml b/pyproject.toml index 4571e5b61..80893b15b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,6 +59,7 @@ dependencies = [ "faster-whisper==1.0.2", "PyJWT[crypto]==2.8.0", + "authlib==1.3.0", "black==24.4.2", "langfuse==2.33.0", diff --git a/requirements-dev.lock b/requirements-dev.lock index 6aa26dad4..f7660eae3 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -31,6 +31,8 @@ asgiref==3.8.1 # via opentelemetry-instrumentation-asgi attrs==23.2.0 # via aiohttp +authlib==1.3.0 + # via open-webui av==11.0.0 # via faster-whisper backoff==2.2.1 @@ -93,6 +95,7 @@ coloredlogs==15.0.1 compressed-rtf==1.0.6 # via extract-msg cryptography==42.0.7 + # via authlib # via msoffcrypto-tool # via pyjwt ctranslate2==4.2.1 @@ -395,6 +398,7 @@ pandas==2.2.2 # via open-webui passlib==1.7.4 # via open-webui + # via passlib pathspec==0.12.1 # via black pcodedmp==1.2.6 @@ -453,6 +457,7 @@ pygments==2.18.0 # via rich pyjwt==2.8.0 # via open-webui + # via pyjwt pymysql==1.1.0 # via open-webui pypandoc==1.13 @@ -554,9 +559,6 @@ scipy==1.13.0 # via sentence-transformers sentence-transformers==2.7.0 # via open-webui -setuptools==69.5.1 - # via ctranslate2 - # via opentelemetry-instrumentation shapely==2.0.4 # via rapidocr-onnxruntime shellingham==1.5.4 @@ -651,6 +653,7 @@ uvicorn==0.22.0 # via chromadb # via fastapi # via open-webui + # via uvicorn uvloop==0.19.0 # via uvicorn validators==0.28.1 @@ -678,3 +681,6 @@ youtube-transcript-api==0.6.2 # via open-webui zipp==3.18.1 # via importlib-metadata +setuptools==69.5.1 + # via ctranslate2 + # via opentelemetry-instrumentation diff --git a/requirements.lock b/requirements.lock index 6aa26dad4..f7660eae3 100644 --- a/requirements.lock +++ b/requirements.lock @@ -31,6 +31,8 @@ asgiref==3.8.1 # via opentelemetry-instrumentation-asgi attrs==23.2.0 # via aiohttp +authlib==1.3.0 + # via open-webui av==11.0.0 # via faster-whisper backoff==2.2.1 @@ -93,6 +95,7 @@ coloredlogs==15.0.1 compressed-rtf==1.0.6 # via extract-msg cryptography==42.0.7 + # via authlib # via msoffcrypto-tool # via pyjwt ctranslate2==4.2.1 @@ -395,6 +398,7 @@ pandas==2.2.2 # via open-webui passlib==1.7.4 # via open-webui + # via passlib pathspec==0.12.1 # via black pcodedmp==1.2.6 @@ -453,6 +457,7 @@ pygments==2.18.0 # via rich pyjwt==2.8.0 # via open-webui + # via pyjwt pymysql==1.1.0 # via open-webui pypandoc==1.13 @@ -554,9 +559,6 @@ scipy==1.13.0 # via sentence-transformers sentence-transformers==2.7.0 # via open-webui -setuptools==69.5.1 - # via ctranslate2 - # via opentelemetry-instrumentation shapely==2.0.4 # via rapidocr-onnxruntime shellingham==1.5.4 @@ -651,6 +653,7 @@ uvicorn==0.22.0 # via chromadb # via fastapi # via open-webui + # via uvicorn uvloop==0.19.0 # via uvicorn validators==0.28.1 @@ -678,3 +681,6 @@ youtube-transcript-api==0.6.2 # via open-webui zipp==3.18.1 # via importlib-metadata +setuptools==69.5.1 + # via ctranslate2 + # via opentelemetry-instrumentation diff --git a/src/lib/i18n/locales/ar-BH/translation.json b/src/lib/i18n/locales/ar-BH/translation.json index fdb43d68a..d9c68c6cb 100644 --- a/src/lib/i18n/locales/ar-BH/translation.json +++ b/src/lib/i18n/locales/ar-BH/translation.json @@ -126,6 +126,7 @@ "Content": "الاتصال", "Context Length": "طول السياق", "Continue Response": "متابعة الرد", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "تم نسخ عنوان URL للدردشة المشتركة إلى الحافظة", "Copy": "نسخ", "Copy last code block": "انسخ كتلة التعليمات البرمجية الأخيرة", @@ -378,6 +379,7 @@ "Notifications": "إشعارات", "November": "نوفمبر", "num_thread (Ollama)": "num_thread (أولاما)", + "OAuth ID": "", "October": "اكتوبر", "Off": "أغلاق", "Okay, Let's Go!": "حسنا دعنا نذهب!", diff --git a/src/lib/i18n/locales/bg-BG/translation.json b/src/lib/i18n/locales/bg-BG/translation.json index fe7d7c4bd..9fdb2a4ce 100644 --- a/src/lib/i18n/locales/bg-BG/translation.json +++ b/src/lib/i18n/locales/bg-BG/translation.json @@ -126,6 +126,7 @@ "Content": "Съдържание", "Context Length": "Дължина на Контекста", "Continue Response": "Продължи отговора", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "Копирана е връзката за чат!", "Copy": "Копирай", "Copy last code block": "Копиране на последен код блок", @@ -378,6 +379,7 @@ "Notifications": "Десктоп Известия", "November": "Ноември", "num_thread (Ollama)": "num_thread (Ollama)", + "OAuth ID": "", "October": "Октомври", "Off": "Изкл.", "Okay, Let's Go!": "ОК, Нека започваме!", diff --git a/src/lib/i18n/locales/bn-BD/translation.json b/src/lib/i18n/locales/bn-BD/translation.json index e964b5af9..b76409820 100644 --- a/src/lib/i18n/locales/bn-BD/translation.json +++ b/src/lib/i18n/locales/bn-BD/translation.json @@ -126,6 +126,7 @@ "Content": "বিষয়বস্তু", "Context Length": "কনটেক্সটের দৈর্ঘ্য", "Continue Response": "যাচাই করুন", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "শেয়ারকৃত কথা-ব্যবহারের URL ক্লিপবোর্ডে কপি করা হয়েছে!", "Copy": "অনুলিপি", "Copy last code block": "সর্বশেষ কোড ব্লক কপি করুন", @@ -378,6 +379,7 @@ "Notifications": "নোটিফিকেশনসমূহ", "November": "নভেম্বর", "num_thread (Ollama)": "num_thread (ওলামা)", + "OAuth ID": "", "October": "অক্টোবর", "Off": "বন্ধ", "Okay, Let's Go!": "ঠিক আছে, চলুন যাই!", diff --git a/src/lib/i18n/locales/ca-ES/translation.json b/src/lib/i18n/locales/ca-ES/translation.json index 64af65a6b..df39ecab5 100644 --- a/src/lib/i18n/locales/ca-ES/translation.json +++ b/src/lib/i18n/locales/ca-ES/translation.json @@ -126,6 +126,7 @@ "Content": "Contingut", "Context Length": "Mida del context", "Continue Response": "Continuar la resposta", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "S'ha copiat l'URL compartida al porta-retalls!", "Copy": "Copiar", "Copy last code block": "Copiar l'últim bloc de codi", @@ -378,6 +379,7 @@ "Notifications": "Notificacions", "November": "Novembre", "num_thread (Ollama)": "num_thread (Ollama)", + "OAuth ID": "", "October": "Octubre", "Off": "Desactivat", "Okay, Let's Go!": "D'acord, som-hi!", diff --git a/src/lib/i18n/locales/ceb-PH/translation.json b/src/lib/i18n/locales/ceb-PH/translation.json index 645c6550f..805475e98 100644 --- a/src/lib/i18n/locales/ceb-PH/translation.json +++ b/src/lib/i18n/locales/ceb-PH/translation.json @@ -126,6 +126,7 @@ "Content": "Kontento", "Context Length": "Ang gitas-on sa konteksto", "Continue Response": "", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "", "Copy": "", "Copy last code block": "Kopyaha ang katapusang bloke sa code", @@ -378,6 +379,7 @@ "Notifications": "Mga pahibalo sa desktop", "November": "", "num_thread (Ollama)": "", + "OAuth ID": "", "October": "", "Off": "Napuo", "Okay, Let's Go!": "Okay, lakaw na!", diff --git a/src/lib/i18n/locales/de-DE/translation.json b/src/lib/i18n/locales/de-DE/translation.json index 4b22614bb..b5b29042f 100644 --- a/src/lib/i18n/locales/de-DE/translation.json +++ b/src/lib/i18n/locales/de-DE/translation.json @@ -126,6 +126,7 @@ "Content": "Info", "Context Length": "Context Length", "Continue Response": "Antwort fortsetzen", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "Geteilte Chat-URL in die Zwischenablage kopiert!", "Copy": "Kopieren", "Copy last code block": "Letzten Codeblock kopieren", @@ -378,6 +379,7 @@ "Notifications": "Desktop-Benachrichtigungen", "November": "November", "num_thread (Ollama)": "num_thread (Ollama)", + "OAuth ID": "", "October": "Oktober", "Off": "Aus", "Okay, Let's Go!": "Okay, los geht's!", diff --git a/src/lib/i18n/locales/dg-DG/translation.json b/src/lib/i18n/locales/dg-DG/translation.json index 25e2375ea..13a513da5 100644 --- a/src/lib/i18n/locales/dg-DG/translation.json +++ b/src/lib/i18n/locales/dg-DG/translation.json @@ -126,6 +126,7 @@ "Content": "Content", "Context Length": "Context Length", "Continue Response": "", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "", "Copy": "", "Copy last code block": "Copy last code block", @@ -378,6 +379,7 @@ "Notifications": "Notifications", "November": "", "num_thread (Ollama)": "", + "OAuth ID": "", "October": "", "Off": "Off", "Okay, Let's Go!": "Okay, Let's Go!", diff --git a/src/lib/i18n/locales/en-GB/translation.json b/src/lib/i18n/locales/en-GB/translation.json index 61386101e..d3a869e83 100644 --- a/src/lib/i18n/locales/en-GB/translation.json +++ b/src/lib/i18n/locales/en-GB/translation.json @@ -126,6 +126,7 @@ "Content": "", "Context Length": "", "Continue Response": "", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "", "Copy": "", "Copy last code block": "", @@ -378,6 +379,7 @@ "Notifications": "", "November": "", "num_thread (Ollama)": "", + "OAuth ID": "", "October": "", "Off": "", "Okay, Let's Go!": "", diff --git a/src/lib/i18n/locales/en-US/translation.json b/src/lib/i18n/locales/en-US/translation.json index 61386101e..d3a869e83 100644 --- a/src/lib/i18n/locales/en-US/translation.json +++ b/src/lib/i18n/locales/en-US/translation.json @@ -126,6 +126,7 @@ "Content": "", "Context Length": "", "Continue Response": "", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "", "Copy": "", "Copy last code block": "", @@ -378,6 +379,7 @@ "Notifications": "", "November": "", "num_thread (Ollama)": "", + "OAuth ID": "", "October": "", "Off": "", "Okay, Let's Go!": "", diff --git a/src/lib/i18n/locales/es-ES/translation.json b/src/lib/i18n/locales/es-ES/translation.json index a9c773c46..186e888a1 100644 --- a/src/lib/i18n/locales/es-ES/translation.json +++ b/src/lib/i18n/locales/es-ES/translation.json @@ -126,6 +126,7 @@ "Content": "Contenido", "Context Length": "Longitud del contexto", "Continue Response": "Continuar Respuesta", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "¡URL de chat compartido copiado al portapapeles!", "Copy": "Copiar", "Copy last code block": "Copia el último bloque de código", @@ -378,6 +379,7 @@ "Notifications": "Notificaciones", "November": "Noviembre", "num_thread (Ollama)": "num_thread (Ollama)", + "OAuth ID": "", "October": "Octubre", "Off": "Desactivado", "Okay, Let's Go!": "Bien, ¡Vamos!", diff --git a/src/lib/i18n/locales/fa-IR/translation.json b/src/lib/i18n/locales/fa-IR/translation.json index 4662e84a5..e87698310 100644 --- a/src/lib/i18n/locales/fa-IR/translation.json +++ b/src/lib/i18n/locales/fa-IR/translation.json @@ -126,6 +126,7 @@ "Content": "محتوا", "Context Length": "طول زمینه", "Continue Response": "ادامه پاسخ", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "URL چت به کلیپ بورد کپی شد!", "Copy": "کپی", "Copy last code block": "کپی آخرین بلوک کد", @@ -378,6 +379,7 @@ "Notifications": "اعلان", "November": "نوامبر", "num_thread (Ollama)": "num_thread (اولاما)", + "OAuth ID": "", "October": "اکتبر", "Off": "خاموش", "Okay, Let's Go!": "باشه، بزن بریم!", diff --git a/src/lib/i18n/locales/fi-FI/translation.json b/src/lib/i18n/locales/fi-FI/translation.json index d2bdf4889..79d58a8b6 100644 --- a/src/lib/i18n/locales/fi-FI/translation.json +++ b/src/lib/i18n/locales/fi-FI/translation.json @@ -126,6 +126,7 @@ "Content": "Sisältö", "Context Length": "Kontekstin pituus", "Continue Response": "Jatka vastausta", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "Jaettu keskustelulinkki kopioitu leikepöydälle!", "Copy": "Kopioi", "Copy last code block": "Kopioi viimeisin koodilohko", @@ -378,6 +379,7 @@ "Notifications": "Ilmoitukset", "November": "marraskuu", "num_thread (Ollama)": "num_thread (Ollama)", + "OAuth ID": "", "October": "lokakuu", "Off": "Pois", "Okay, Let's Go!": "Eikun menoksi!", diff --git a/src/lib/i18n/locales/fr-CA/translation.json b/src/lib/i18n/locales/fr-CA/translation.json index 6a9acf5a5..c77376ddb 100644 --- a/src/lib/i18n/locales/fr-CA/translation.json +++ b/src/lib/i18n/locales/fr-CA/translation.json @@ -126,6 +126,7 @@ "Content": "Contenu", "Context Length": "Longueur du contexte", "Continue Response": "Continuer la réponse", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "URL de chat partagé copié dans le presse-papier !", "Copy": "Copier", "Copy last code block": "Copier le dernier bloc de code", @@ -378,6 +379,7 @@ "Notifications": "Notifications de bureau", "November": "Novembre", "num_thread (Ollama)": "num_thread (Ollama)", + "OAuth ID": "", "October": "Octobre", "Off": "Éteint", "Okay, Let's Go!": "Okay, Allons-y !", diff --git a/src/lib/i18n/locales/fr-FR/translation.json b/src/lib/i18n/locales/fr-FR/translation.json index 06dfc3e03..21094f09c 100644 --- a/src/lib/i18n/locales/fr-FR/translation.json +++ b/src/lib/i18n/locales/fr-FR/translation.json @@ -126,6 +126,7 @@ "Content": "Contenu", "Context Length": "Longueur du contexte", "Continue Response": "Continuer la Réponse", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "URL du chat copié dans le presse-papiers !", "Copy": "Copier", "Copy last code block": "Copier le dernier bloc de code", @@ -378,6 +379,7 @@ "Notifications": "Notifications de bureau", "November": "Novembre", "num_thread (Ollama)": "num_thread (Ollama)", + "OAuth ID": "", "October": "Octobre", "Off": "Désactivé", "Okay, Let's Go!": "D'accord, allons-y !", diff --git a/src/lib/i18n/locales/he-IL/translation.json b/src/lib/i18n/locales/he-IL/translation.json index cedaf5263..da855f755 100644 --- a/src/lib/i18n/locales/he-IL/translation.json +++ b/src/lib/i18n/locales/he-IL/translation.json @@ -126,6 +126,7 @@ "Content": "תוכן", "Context Length": "אורך הקשר", "Continue Response": "המשך תגובה", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "העתקת כתובת URL של צ'אט משותף ללוח!", "Copy": "העתק", "Copy last code block": "העתק את בלוק הקוד האחרון", @@ -378,6 +379,7 @@ "Notifications": "התראות", "November": "נובמבר", "num_thread (Ollama)": "num_thread (Ollama)", + "OAuth ID": "", "October": "אוקטובר", "Off": "כבוי", "Okay, Let's Go!": "בסדר, בואו נתחיל!", diff --git a/src/lib/i18n/locales/hi-IN/translation.json b/src/lib/i18n/locales/hi-IN/translation.json index 13253f042..fd40d5498 100644 --- a/src/lib/i18n/locales/hi-IN/translation.json +++ b/src/lib/i18n/locales/hi-IN/translation.json @@ -126,6 +126,7 @@ "Content": "सामग्री", "Context Length": "प्रसंग की लंबाई", "Continue Response": "प्रतिक्रिया जारी रखें", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "साझा चैट URL को क्लिपबोर्ड पर कॉपी किया गया!", "Copy": "कॉपी", "Copy last code block": "अंतिम कोड ब्लॉक कॉपी करें", @@ -378,6 +379,7 @@ "Notifications": "सूचनाएं", "November": "नवंबर", "num_thread (Ollama)": "num_thread (ओलामा)", + "OAuth ID": "", "October": "अक्टूबर", "Off": "बंद", "Okay, Let's Go!": "ठीक है, चलिए चलते हैं!", diff --git a/src/lib/i18n/locales/hr-HR/translation.json b/src/lib/i18n/locales/hr-HR/translation.json index e8247d9cf..f0ebde409 100644 --- a/src/lib/i18n/locales/hr-HR/translation.json +++ b/src/lib/i18n/locales/hr-HR/translation.json @@ -126,6 +126,7 @@ "Content": "Sadržaj", "Context Length": "Dužina konteksta", "Continue Response": "Nastavi odgovor", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "URL dijeljenog razgovora kopiran u međuspremnik!", "Copy": "Kopiraj", "Copy last code block": "Kopiraj zadnji blok koda", @@ -378,6 +379,7 @@ "Notifications": "Obavijesti", "November": "Studeni", "num_thread (Ollama)": "num_thread (Ollama)", + "OAuth ID": "", "October": "Listopad", "Off": "Isključeno", "Okay, Let's Go!": "U redu, idemo!", diff --git a/src/lib/i18n/locales/it-IT/translation.json b/src/lib/i18n/locales/it-IT/translation.json index 0364d7b9c..60cc2e407 100644 --- a/src/lib/i18n/locales/it-IT/translation.json +++ b/src/lib/i18n/locales/it-IT/translation.json @@ -126,6 +126,7 @@ "Content": "Contenuto", "Context Length": "Lunghezza contesto", "Continue Response": "Continua risposta", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "URL della chat condivisa copiato negli appunti!", "Copy": "Copia", "Copy last code block": "Copia ultimo blocco di codice", @@ -378,6 +379,7 @@ "Notifications": "Notifiche desktop", "November": "Novembre", "num_thread (Ollama)": "num_thread (Ollama)", + "OAuth ID": "", "October": "Ottobre", "Off": "Disattivato", "Okay, Let's Go!": "Ok, andiamo!", diff --git a/src/lib/i18n/locales/ja-JP/translation.json b/src/lib/i18n/locales/ja-JP/translation.json index a38cb5418..30f4897cd 100644 --- a/src/lib/i18n/locales/ja-JP/translation.json +++ b/src/lib/i18n/locales/ja-JP/translation.json @@ -126,6 +126,7 @@ "Content": "コンテンツ", "Context Length": "コンテキストの長さ", "Continue Response": "続きの応答", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "共有チャットURLをクリップボードにコピーしました!", "Copy": "コピー", "Copy last code block": "最後のコードブロックをコピー", @@ -378,6 +379,7 @@ "Notifications": "デスクトップ通知", "November": "11月", "num_thread (Ollama)": "num_thread(オラマ)", + "OAuth ID": "", "October": "10月", "Off": "オフ", "Okay, Let's Go!": "OK、始めましょう!", diff --git a/src/lib/i18n/locales/ka-GE/translation.json b/src/lib/i18n/locales/ka-GE/translation.json index 381762ebf..fd668ce78 100644 --- a/src/lib/i18n/locales/ka-GE/translation.json +++ b/src/lib/i18n/locales/ka-GE/translation.json @@ -126,6 +126,7 @@ "Content": "კონტენტი", "Context Length": "კონტექსტის სიგრძე", "Continue Response": "პასუხის გაგრძელება", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "ყავს ჩათის URL-ი კლიპბორდში!", "Copy": "კოპირება", "Copy last code block": "ბოლო ბლოკის კოპირება", @@ -378,6 +379,7 @@ "Notifications": "შეტყობინება", "November": "ნოემბერი", "num_thread (Ollama)": "num_thread (ოლამა)", + "OAuth ID": "", "October": "ოქტომბერი", "Off": "გამორთვა", "Okay, Let's Go!": "კარგი, წავედით!", diff --git a/src/lib/i18n/locales/ko-KR/translation.json b/src/lib/i18n/locales/ko-KR/translation.json index a5f36c5e9..984b9bbb6 100644 --- a/src/lib/i18n/locales/ko-KR/translation.json +++ b/src/lib/i18n/locales/ko-KR/translation.json @@ -126,6 +126,7 @@ "Content": "내용", "Context Length": "내용 길이", "Continue Response": "대화 계속", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "공유 채팅 URL이 클립보드에 복사되었습니다!", "Copy": "복사", "Copy last code block": "마지막 코드 블록 복사", @@ -378,6 +379,7 @@ "Notifications": "알림", "November": "11월", "num_thread (Ollama)": "num_thread (올라마)", + "OAuth ID": "", "October": "10월", "Off": "끄기", "Okay, Let's Go!": "좋아요, 시작합시다!", diff --git a/src/lib/i18n/locales/lt-LT/translation.json b/src/lib/i18n/locales/lt-LT/translation.json index 7d91a5f1a..9d9bcabcf 100644 --- a/src/lib/i18n/locales/lt-LT/translation.json +++ b/src/lib/i18n/locales/lt-LT/translation.json @@ -126,6 +126,7 @@ "Content": "Turinys", "Context Length": "Konteksto ilgis", "Continue Response": "Tęsti atsakymą", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "Nukopijavote pokalbio nuorodą", "Copy": "Kopijuoti", "Copy last code block": "Kopijuoti paskutinį kodo bloką", @@ -378,6 +379,7 @@ "Notifications": "Pranešimai", "November": "lapkritis", "num_thread (Ollama)": "", + "OAuth ID": "", "October": "spalis", "Off": "Išjungta", "Okay, Let's Go!": "Gerai, važiuojam!", diff --git a/src/lib/i18n/locales/nb-NO/translation.json b/src/lib/i18n/locales/nb-NO/translation.json index dd3864bae..8e57c4da5 100644 --- a/src/lib/i18n/locales/nb-NO/translation.json +++ b/src/lib/i18n/locales/nb-NO/translation.json @@ -126,6 +126,7 @@ "Content": "Innhold", "Context Length": "Kontekstlengde", "Continue Response": "Fortsett svar", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "Kopiert delt chat-URL til utklippstavlen!", "Copy": "Kopier", "Copy last code block": "Kopier siste kodeblokk", @@ -378,6 +379,7 @@ "Notifications": "Varsler", "November": "November", "num_thread (Ollama)": "num_thread (Ollama)", + "OAuth ID": "", "October": "Oktober", "Off": "Av", "Okay, Let's Go!": "Ok, la oss gå!", diff --git a/src/lib/i18n/locales/nl-NL/translation.json b/src/lib/i18n/locales/nl-NL/translation.json index 912df7936..b4de94d85 100644 --- a/src/lib/i18n/locales/nl-NL/translation.json +++ b/src/lib/i18n/locales/nl-NL/translation.json @@ -126,6 +126,7 @@ "Content": "Inhoud", "Context Length": "Context Lengte", "Continue Response": "Doorgaan met Antwoord", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "URL van gedeelde gesprekspagina gekopieerd naar klembord!", "Copy": "Kopieer", "Copy last code block": "Kopieer laatste code blok", @@ -378,6 +379,7 @@ "Notifications": "Desktop Notificaties", "November": "November", "num_thread (Ollama)": "num_thread (Ollama)", + "OAuth ID": "", "October": "Oktober", "Off": "Uit", "Okay, Let's Go!": "Okay, Laten we gaan!", diff --git a/src/lib/i18n/locales/pa-IN/translation.json b/src/lib/i18n/locales/pa-IN/translation.json index 49e1aed32..41f5128d7 100644 --- a/src/lib/i18n/locales/pa-IN/translation.json +++ b/src/lib/i18n/locales/pa-IN/translation.json @@ -126,6 +126,7 @@ "Content": "ਸਮੱਗਰੀ", "Context Length": "ਸੰਦਰਭ ਲੰਬਾਈ", "Continue Response": "ਜਵਾਬ ਜਾਰੀ ਰੱਖੋ", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "ਸਾਂਝੇ ਕੀਤੇ ਗੱਲਬਾਤ URL ਨੂੰ ਕਲਿੱਪਬੋਰਡ 'ਤੇ ਕਾਪੀ ਕਰ ਦਿੱਤਾ!", "Copy": "ਕਾਪੀ ਕਰੋ", "Copy last code block": "ਆਖਰੀ ਕੋਡ ਬਲਾਕ ਨੂੰ ਕਾਪੀ ਕਰੋ", @@ -378,6 +379,7 @@ "Notifications": "ਸੂਚਨਾਵਾਂ", "November": "ਨਵੰਬਰ", "num_thread (Ollama)": "num_thread (ਓਲਾਮਾ)", + "OAuth ID": "", "October": "ਅਕਤੂਬਰ", "Off": "ਬੰਦ", "Okay, Let's Go!": "ਠੀਕ ਹੈ, ਚੱਲੋ ਚੱਲੀਏ!", diff --git a/src/lib/i18n/locales/pl-PL/translation.json b/src/lib/i18n/locales/pl-PL/translation.json index 661b0cea4..44baeba86 100644 --- a/src/lib/i18n/locales/pl-PL/translation.json +++ b/src/lib/i18n/locales/pl-PL/translation.json @@ -126,6 +126,7 @@ "Content": "Zawartość", "Context Length": "Długość kontekstu", "Continue Response": "Kontynuuj odpowiedź", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "Skopiowano URL czatu do schowka!", "Copy": "Kopiuj", "Copy last code block": "Skopiuj ostatni blok kodu", @@ -378,6 +379,7 @@ "Notifications": "Powiadomienia", "November": "Listopad", "num_thread (Ollama)": "num_thread (Ollama)", + "OAuth ID": "", "October": "Październik", "Off": "Wyłączony", "Okay, Let's Go!": "Okej, zaczynamy!", diff --git a/src/lib/i18n/locales/pt-BR/translation.json b/src/lib/i18n/locales/pt-BR/translation.json index d5cc6dbd3..419c2119b 100644 --- a/src/lib/i18n/locales/pt-BR/translation.json +++ b/src/lib/i18n/locales/pt-BR/translation.json @@ -126,6 +126,7 @@ "Content": "Conteúdo", "Context Length": "Comprimento do Contexto", "Continue Response": "Continuar resposta", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "URL de bate-papo compartilhado copiada com sucesso!", "Copy": "Copiar", "Copy last code block": "Copiar último bloco de código", @@ -378,6 +379,7 @@ "Notifications": "Notificações da Área de Trabalho", "November": "Novembro", "num_thread (Ollama)": "num_thread (Ollama)", + "OAuth ID": "", "October": "Outubro", "Off": "Desligado", "Okay, Let's Go!": "Ok, Vamos Lá!", diff --git a/src/lib/i18n/locales/pt-PT/translation.json b/src/lib/i18n/locales/pt-PT/translation.json index 73f01d9dc..ca81b9501 100644 --- a/src/lib/i18n/locales/pt-PT/translation.json +++ b/src/lib/i18n/locales/pt-PT/translation.json @@ -126,6 +126,7 @@ "Content": "Conteúdo", "Context Length": "Comprimento do Contexto", "Continue Response": "Continuar resposta", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "URL de Conversa partilhado copiada com sucesso!", "Copy": "Copiar", "Copy last code block": "Copiar último bloco de código", @@ -378,6 +379,7 @@ "Notifications": "Notificações da Área de Trabalho", "November": "Novembro", "num_thread (Ollama)": "num_thread (Ollama)", + "OAuth ID": "", "October": "Outubro", "Off": "Desligado", "Okay, Let's Go!": "Ok, Vamos Lá!", diff --git a/src/lib/i18n/locales/ru-RU/translation.json b/src/lib/i18n/locales/ru-RU/translation.json index 7260610a0..8664b78c4 100644 --- a/src/lib/i18n/locales/ru-RU/translation.json +++ b/src/lib/i18n/locales/ru-RU/translation.json @@ -126,6 +126,7 @@ "Content": "Содержание", "Context Length": "Длина контексту", "Continue Response": "Продолжить ответ", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "Копирование общей ссылки чат в буфер обмена!", "Copy": "Копировать", "Copy last code block": "Копировать последний блок кода", @@ -378,6 +379,7 @@ "Notifications": "Уведомления на рабочем столе", "November": "Ноябрь", "num_thread (Ollama)": "num_thread (Оллама)", + "OAuth ID": "", "October": "Октябрь", "Off": "Выключено.", "Okay, Let's Go!": "Давайте начнём!", diff --git a/src/lib/i18n/locales/sr-RS/translation.json b/src/lib/i18n/locales/sr-RS/translation.json index 04a150e1f..26ec244d2 100644 --- a/src/lib/i18n/locales/sr-RS/translation.json +++ b/src/lib/i18n/locales/sr-RS/translation.json @@ -126,6 +126,7 @@ "Content": "Садржај", "Context Length": "Дужина контекста", "Continue Response": "Настави одговор", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "Адреса дељеног ћаскања ископирана у оставу!", "Copy": "Копирај", "Copy last code block": "Копирај последњи блок кода", @@ -378,6 +379,7 @@ "Notifications": "Обавештења", "November": "Новембар", "num_thread (Ollama)": "нум _тхреад (Оллама)", + "OAuth ID": "", "October": "Октобар", "Off": "Искључено", "Okay, Let's Go!": "У реду, хајде да кренемо!", diff --git a/src/lib/i18n/locales/sv-SE/translation.json b/src/lib/i18n/locales/sv-SE/translation.json index 84eb23209..50ba68cd8 100644 --- a/src/lib/i18n/locales/sv-SE/translation.json +++ b/src/lib/i18n/locales/sv-SE/translation.json @@ -126,6 +126,7 @@ "Content": "Innehåll", "Context Length": "Kontextlängd", "Continue Response": "Fortsätt svar", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "Kopierad delad chatt-URL till urklipp!", "Copy": "Kopiera", "Copy last code block": "Kopiera sista kodblock", @@ -378,6 +379,7 @@ "Notifications": "Notifikationer", "November": "november", "num_thread (Ollama)": "num_thread (Ollama)", + "OAuth ID": "", "October": "oktober", "Off": "Av", "Okay, Let's Go!": "Okej, nu kör vi!", diff --git a/src/lib/i18n/locales/tk-TW/translation.json b/src/lib/i18n/locales/tk-TW/translation.json index 61386101e..d3a869e83 100644 --- a/src/lib/i18n/locales/tk-TW/translation.json +++ b/src/lib/i18n/locales/tk-TW/translation.json @@ -126,6 +126,7 @@ "Content": "", "Context Length": "", "Continue Response": "", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "", "Copy": "", "Copy last code block": "", @@ -378,6 +379,7 @@ "Notifications": "", "November": "", "num_thread (Ollama)": "", + "OAuth ID": "", "October": "", "Off": "", "Okay, Let's Go!": "", diff --git a/src/lib/i18n/locales/tr-TR/translation.json b/src/lib/i18n/locales/tr-TR/translation.json index b780b5393..a6b49afa8 100644 --- a/src/lib/i18n/locales/tr-TR/translation.json +++ b/src/lib/i18n/locales/tr-TR/translation.json @@ -126,6 +126,7 @@ "Content": "İçerik", "Context Length": "Bağlam Uzunluğu", "Continue Response": "Yanıta Devam Et", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "Paylaşılan sohbet URL'si panoya kopyalandı!", "Copy": "Kopyala", "Copy last code block": "Son kod bloğunu kopyala", @@ -378,6 +379,7 @@ "Notifications": "Bildirimler", "November": "Kasım", "num_thread (Ollama)": "num_thread (Ollama)", + "OAuth ID": "", "October": "Ekim", "Off": "Kapalı", "Okay, Let's Go!": "Tamam, Hadi Başlayalım!", diff --git a/src/lib/i18n/locales/uk-UA/translation.json b/src/lib/i18n/locales/uk-UA/translation.json index af6592ca5..e259160a1 100644 --- a/src/lib/i18n/locales/uk-UA/translation.json +++ b/src/lib/i18n/locales/uk-UA/translation.json @@ -126,6 +126,7 @@ "Content": "Зміст", "Context Length": "Довжина контексту", "Continue Response": "Продовжити відповідь", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "Скопійовано URL-адресу спільного чату в буфер обміну!", "Copy": "Копіювати", "Copy last code block": "Копіювати останній блок коду", @@ -378,6 +379,7 @@ "Notifications": "Сповіщення", "November": "Листопад", "num_thread (Ollama)": "num_thread (Ollama)", + "OAuth ID": "", "October": "Жовтень", "Off": "Вимк", "Okay, Let's Go!": "Гаразд, давайте почнемо!", diff --git a/src/lib/i18n/locales/vi-VN/translation.json b/src/lib/i18n/locales/vi-VN/translation.json index d23340bc5..9aeaaee0f 100644 --- a/src/lib/i18n/locales/vi-VN/translation.json +++ b/src/lib/i18n/locales/vi-VN/translation.json @@ -126,6 +126,7 @@ "Content": "Nội dung", "Context Length": "Độ dài ngữ cảnh (Context Length)", "Continue Response": "Tiếp tục trả lời", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "Đã sao chép link chia sẻ trò chuyện vào clipboard!", "Copy": "Sao chép", "Copy last code block": "Sao chép khối mã cuối cùng", @@ -378,6 +379,7 @@ "Notifications": "Thông báo trên máy tính (Notification)", "November": "Tháng 11", "num_thread (Ollama)": "num_thread (Ollama)", + "OAuth ID": "", "October": "Tháng 10", "Off": "Tắt", "Okay, Let's Go!": "Được rồi, Bắt đầu thôi!", diff --git a/src/lib/i18n/locales/zh-CN/translation.json b/src/lib/i18n/locales/zh-CN/translation.json index 7474f29d9..22b49ac5d 100644 --- a/src/lib/i18n/locales/zh-CN/translation.json +++ b/src/lib/i18n/locales/zh-CN/translation.json @@ -126,6 +126,7 @@ "Content": "内容", "Context Length": "上下文长度", "Continue Response": "继续生成", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "已复制此对话分享链接至剪贴板!", "Copy": "复制", "Copy last code block": "复制最后一个代码块中的代码", @@ -378,6 +379,7 @@ "Notifications": "桌面通知", "November": "十一月", "num_thread (Ollama)": "num_thread(Ollama)", + "OAuth ID": "", "October": "十月", "Off": "关闭", "Okay, Let's Go!": "确认,开始使用!", diff --git a/src/lib/i18n/locales/zh-TW/translation.json b/src/lib/i18n/locales/zh-TW/translation.json index f3e4c985c..7808ba96c 100644 --- a/src/lib/i18n/locales/zh-TW/translation.json +++ b/src/lib/i18n/locales/zh-TW/translation.json @@ -126,6 +126,7 @@ "Content": "內容", "Context Length": "上下文長度", "Continue Response": "繼續回答", + "Continue with {{provider}}": "", "Copied shared chat URL to clipboard!": "已複製共享聊天連結到剪貼簿!", "Copy": "複製", "Copy last code block": "複製最後一個程式碼區塊", @@ -378,6 +379,7 @@ "Notifications": "通知", "November": "11 月", "num_thread (Ollama)": "num_thread(Ollama)", + "OAuth ID": "", "October": "10 月", "Off": "關閉", "Okay, Let's Go!": "好的,啟動吧!", diff --git a/src/lib/stores/index.ts b/src/lib/stores/index.ts index 894565ef3..8a481fe6c 100644 --- a/src/lib/stores/index.ts +++ b/src/lib/stores/index.ts @@ -149,6 +149,11 @@ type Config = { enable_admin_export: boolean; enable_community_sharing: boolean; }; + oauth: { + providers: { + [key: string]: string; + }; + }; }; type PromptSuggestion = { diff --git a/src/routes/(app)/admin/+page.svelte b/src/routes/(app)/admin/+page.svelte index 6ae7b0c92..26375c9c3 100644 --- a/src/routes/(app)/admin/+page.svelte +++ b/src/routes/(app)/admin/+page.svelte @@ -195,6 +195,18 @@ {/if} + setSortKey('oauth_sub')} + > + {$i18n.t('OAuth ID')} + {#if sortKey === 'oauth_sub'} + {sortOrder === 'asc' ? '▲' : '▼'} + {:else} + + {/if} + {user.email} + {user.oauth_sub ?? ''} + {dayjs(user.last_active_at * 1000).fromNow()} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 23ca65b2f..7a703cce6 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -18,6 +18,7 @@ USAGE_POOL } from '$lib/stores'; import { goto } from '$app/navigation'; + import { page } from '$app/stores'; import { Toaster, toast } from 'svelte-sonner'; import { getBackendConfig } from '$lib/apis'; @@ -141,7 +142,11 @@ await goto('/auth'); } } else { - await goto('/auth'); + // Don't redirect if we're already on the auth page + // Needed because we pass in tokens from OAuth logins via URL fragments + if ($page.url.pathname !== '/auth') { + await goto('/auth'); + } } } } else { diff --git a/src/routes/auth/+page.svelte b/src/routes/auth/+page.svelte index 42a1faca8..21ca9743a 100644 --- a/src/routes/auth/+page.svelte +++ b/src/routes/auth/+page.svelte @@ -1,12 +1,13 @@