Merge pull request #2574 from cheahjs/feat/oauth

feat: experimental SSO support for Google, Microsoft, and OIDC
This commit is contained in:
Timothy Jaeryang Baek 2024-06-24 19:05:58 -07:00 committed by GitHub
commit d17dc59246
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
52 changed files with 633 additions and 13 deletions

View File

@ -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")

View File

@ -2,6 +2,8 @@ from fastapi import FastAPI, Depends
from fastapi.routing import APIRoute from fastapi.routing import APIRoute
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from starlette.middleware.sessions import SessionMiddleware
from apps.webui.routers import ( from apps.webui.routers import (
auths, auths,
users, users,

View File

@ -105,6 +105,7 @@ class AuthsTable:
name: str, name: str,
profile_image_url: str = "/user.png", profile_image_url: str = "/user.png",
role: str = "pending", role: str = "pending",
oauth_sub: Optional[str] = None,
) -> Optional[UserModel]: ) -> Optional[UserModel]:
log.info("insert_new_auth") log.info("insert_new_auth")
@ -115,7 +116,9 @@ class AuthsTable:
) )
result = Auth.create(**auth.model_dump()) 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: if result and user:
return user return user

View File

@ -28,6 +28,8 @@ class User(Model):
settings = JSONField(null=True) settings = JSONField(null=True)
info = JSONField(null=True) info = JSONField(null=True)
oauth_sub = TextField(null=True, unique=True)
class Meta: class Meta:
database = DB database = DB
@ -53,6 +55,8 @@ class UserModel(BaseModel):
settings: Optional[UserSettings] = None settings: Optional[UserSettings] = None
info: Optional[dict] = None info: Optional[dict] = None
oauth_sub: Optional[str] = None
#################### ####################
# Forms # Forms
@ -83,6 +87,7 @@ class UsersTable:
email: str, email: str,
profile_image_url: str = "/user.png", profile_image_url: str = "/user.png",
role: str = "pending", role: str = "pending",
oauth_sub: Optional[str] = None,
) -> Optional[UserModel]: ) -> Optional[UserModel]:
user = UserModel( user = UserModel(
**{ **{
@ -94,6 +99,7 @@ class UsersTable:
"last_active_at": int(time.time()), "last_active_at": int(time.time()),
"created_at": int(time.time()), "created_at": int(time.time()),
"updated_at": int(time.time()), "updated_at": int(time.time()),
"oauth_sub": oauth_sub,
} }
) )
result = User.create(**user.model_dump()) result = User.create(**user.model_dump())
@ -123,6 +129,13 @@ class UsersTable:
except: except:
return None 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]: def get_users(self, skip: int = 0, limit: int = 50) -> List[UserModel]:
return [ return [
UserModel(**model_to_dict(user)) UserModel(**model_to_dict(user))
@ -174,6 +187,18 @@ class UsersTable:
except: except:
return None 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]: def update_user_by_id(self, id: str, updated: dict) -> Optional[UserModel]:
try: try:
query = User.update(**updated).where(User.id == id) query = User.update(**updated).where(User.id == id)

View File

@ -10,7 +10,6 @@ import re
import uuid import uuid
import csv import csv
from apps.webui.models.auths import ( from apps.webui.models.auths import (
SigninForm, SigninForm,
SignupForm, SignupForm,

View File

@ -305,6 +305,135 @@ JWT_EXPIRES_IN = PersistentConfig(
"JWT_EXPIRES_IN", "auth.jwt_expiry", os.environ.get("JWT_EXPIRES_IN", "-1") "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 # Static DIR
#################################### ####################################
@ -733,6 +862,16 @@ WEBUI_SECRET_KEY = os.environ.get(
), # DEPRECATED: remove at next major version ), # 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 == "": if WEBUI_AUTH and WEBUI_SECRET_KEY == "":
raise ValueError(ERROR_MESSAGES.ENV_VAR_NOT_FOUND) raise ValueError(ERROR_MESSAGES.ENV_VAR_NOT_FOUND)

View File

@ -1,4 +1,9 @@
import base64
import uuid
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from authlib.integrations.starlette_client import OAuth
from authlib.oidc.core import UserInfo
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
import json import json
import markdown import markdown
@ -24,7 +29,8 @@ from fastapi.middleware.wsgi import WSGIMiddleware
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from starlette.exceptions import HTTPException as StarletteHTTPException from starlette.exceptions import HTTPException as StarletteHTTPException
from starlette.middleware.base import BaseHTTPMiddleware 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 from apps.socket.main import app as socket_app
@ -53,9 +59,11 @@ from apps.webui.main import (
from pydantic import BaseModel from pydantic import BaseModel
from typing import List, Optional, Iterator, Generator, Union 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.models import Models, ModelModel
from apps.webui.models.tools import Tools from apps.webui.models.tools import Tools
from apps.webui.models.functions import Functions 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 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_verified_user,
get_current_user, get_current_user,
get_http_authorization_cred, get_http_authorization_cred,
get_password_hash,
create_token,
) )
from utils.task import ( from utils.task import (
title_generation_template, title_generation_template,
@ -74,6 +84,7 @@ from utils.misc import (
get_last_user_message, get_last_user_message,
add_or_update_system_message, add_or_update_system_message,
stream_message_template, stream_message_template,
parse_duration,
) )
from apps.rag.utils import get_rag_context, rag_template from apps.rag.utils import get_rag_context, rag_template
@ -106,9 +117,16 @@ from config import (
SEARCH_QUERY_PROMPT_LENGTH_THRESHOLD, SEARCH_QUERY_PROMPT_LENGTH_THRESHOLD,
TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE, TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE,
SAFE_MODE, 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, AppConfig,
) )
from constants import ERROR_MESSAGES from constants import ERROR_MESSAGES, WEBHOOK_MESSAGES
from utils.webhook import post_webhook
if SAFE_MODE: if SAFE_MODE:
print("SAFE MODE ENABLED") print("SAFE MODE ENABLED")
@ -1725,6 +1743,12 @@ async def get_app_config():
"engine": audio_app.state.config.STT_ENGINE, "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") @app.get("/manifest.json")
async def get_manifest_json(): async def get_manifest_json():
return { return {

View File

@ -58,6 +58,7 @@ rank-bm25==0.2.2
faster-whisper==1.0.2 faster-whisper==1.0.2
PyJWT[crypto]==2.8.0 PyJWT[crypto]==2.8.0
authlib==1.3.0
black==24.4.2 black==24.4.2
langfuse==2.33.0 langfuse==2.33.0

View File

@ -59,6 +59,7 @@ dependencies = [
"faster-whisper==1.0.2", "faster-whisper==1.0.2",
"PyJWT[crypto]==2.8.0", "PyJWT[crypto]==2.8.0",
"authlib==1.3.0",
"black==24.4.2", "black==24.4.2",
"langfuse==2.33.0", "langfuse==2.33.0",

View File

@ -31,6 +31,8 @@ asgiref==3.8.1
# via opentelemetry-instrumentation-asgi # via opentelemetry-instrumentation-asgi
attrs==23.2.0 attrs==23.2.0
# via aiohttp # via aiohttp
authlib==1.3.0
# via open-webui
av==11.0.0 av==11.0.0
# via faster-whisper # via faster-whisper
backoff==2.2.1 backoff==2.2.1
@ -93,6 +95,7 @@ coloredlogs==15.0.1
compressed-rtf==1.0.6 compressed-rtf==1.0.6
# via extract-msg # via extract-msg
cryptography==42.0.7 cryptography==42.0.7
# via authlib
# via msoffcrypto-tool # via msoffcrypto-tool
# via pyjwt # via pyjwt
ctranslate2==4.2.1 ctranslate2==4.2.1
@ -395,6 +398,7 @@ pandas==2.2.2
# via open-webui # via open-webui
passlib==1.7.4 passlib==1.7.4
# via open-webui # via open-webui
# via passlib
pathspec==0.12.1 pathspec==0.12.1
# via black # via black
pcodedmp==1.2.6 pcodedmp==1.2.6
@ -453,6 +457,7 @@ pygments==2.18.0
# via rich # via rich
pyjwt==2.8.0 pyjwt==2.8.0
# via open-webui # via open-webui
# via pyjwt
pymysql==1.1.0 pymysql==1.1.0
# via open-webui # via open-webui
pypandoc==1.13 pypandoc==1.13
@ -554,9 +559,6 @@ scipy==1.13.0
# via sentence-transformers # via sentence-transformers
sentence-transformers==2.7.0 sentence-transformers==2.7.0
# via open-webui # via open-webui
setuptools==69.5.1
# via ctranslate2
# via opentelemetry-instrumentation
shapely==2.0.4 shapely==2.0.4
# via rapidocr-onnxruntime # via rapidocr-onnxruntime
shellingham==1.5.4 shellingham==1.5.4
@ -651,6 +653,7 @@ uvicorn==0.22.0
# via chromadb # via chromadb
# via fastapi # via fastapi
# via open-webui # via open-webui
# via uvicorn
uvloop==0.19.0 uvloop==0.19.0
# via uvicorn # via uvicorn
validators==0.28.1 validators==0.28.1
@ -678,3 +681,6 @@ youtube-transcript-api==0.6.2
# via open-webui # via open-webui
zipp==3.18.1 zipp==3.18.1
# via importlib-metadata # via importlib-metadata
setuptools==69.5.1
# via ctranslate2
# via opentelemetry-instrumentation

View File

@ -31,6 +31,8 @@ asgiref==3.8.1
# via opentelemetry-instrumentation-asgi # via opentelemetry-instrumentation-asgi
attrs==23.2.0 attrs==23.2.0
# via aiohttp # via aiohttp
authlib==1.3.0
# via open-webui
av==11.0.0 av==11.0.0
# via faster-whisper # via faster-whisper
backoff==2.2.1 backoff==2.2.1
@ -93,6 +95,7 @@ coloredlogs==15.0.1
compressed-rtf==1.0.6 compressed-rtf==1.0.6
# via extract-msg # via extract-msg
cryptography==42.0.7 cryptography==42.0.7
# via authlib
# via msoffcrypto-tool # via msoffcrypto-tool
# via pyjwt # via pyjwt
ctranslate2==4.2.1 ctranslate2==4.2.1
@ -395,6 +398,7 @@ pandas==2.2.2
# via open-webui # via open-webui
passlib==1.7.4 passlib==1.7.4
# via open-webui # via open-webui
# via passlib
pathspec==0.12.1 pathspec==0.12.1
# via black # via black
pcodedmp==1.2.6 pcodedmp==1.2.6
@ -453,6 +457,7 @@ pygments==2.18.0
# via rich # via rich
pyjwt==2.8.0 pyjwt==2.8.0
# via open-webui # via open-webui
# via pyjwt
pymysql==1.1.0 pymysql==1.1.0
# via open-webui # via open-webui
pypandoc==1.13 pypandoc==1.13
@ -554,9 +559,6 @@ scipy==1.13.0
# via sentence-transformers # via sentence-transformers
sentence-transformers==2.7.0 sentence-transformers==2.7.0
# via open-webui # via open-webui
setuptools==69.5.1
# via ctranslate2
# via opentelemetry-instrumentation
shapely==2.0.4 shapely==2.0.4
# via rapidocr-onnxruntime # via rapidocr-onnxruntime
shellingham==1.5.4 shellingham==1.5.4
@ -651,6 +653,7 @@ uvicorn==0.22.0
# via chromadb # via chromadb
# via fastapi # via fastapi
# via open-webui # via open-webui
# via uvicorn
uvloop==0.19.0 uvloop==0.19.0
# via uvicorn # via uvicorn
validators==0.28.1 validators==0.28.1
@ -678,3 +681,6 @@ youtube-transcript-api==0.6.2
# via open-webui # via open-webui
zipp==3.18.1 zipp==3.18.1
# via importlib-metadata # via importlib-metadata
setuptools==69.5.1
# via ctranslate2
# via opentelemetry-instrumentation

View File

@ -126,6 +126,7 @@
"Content": "الاتصال", "Content": "الاتصال",
"Context Length": "طول السياق", "Context Length": "طول السياق",
"Continue Response": "متابعة الرد", "Continue Response": "متابعة الرد",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "تم نسخ عنوان URL للدردشة المشتركة إلى الحافظة", "Copied shared chat URL to clipboard!": "تم نسخ عنوان URL للدردشة المشتركة إلى الحافظة",
"Copy": "نسخ", "Copy": "نسخ",
"Copy last code block": "انسخ كتلة التعليمات البرمجية الأخيرة", "Copy last code block": "انسخ كتلة التعليمات البرمجية الأخيرة",
@ -378,6 +379,7 @@
"Notifications": "إشعارات", "Notifications": "إشعارات",
"November": "نوفمبر", "November": "نوفمبر",
"num_thread (Ollama)": "num_thread (أولاما)", "num_thread (Ollama)": "num_thread (أولاما)",
"OAuth ID": "",
"October": "اكتوبر", "October": "اكتوبر",
"Off": "أغلاق", "Off": "أغلاق",
"Okay, Let's Go!": "حسنا دعنا نذهب!", "Okay, Let's Go!": "حسنا دعنا نذهب!",

View File

@ -126,6 +126,7 @@
"Content": "Съдържание", "Content": "Съдържание",
"Context Length": "Дължина на Контекста", "Context Length": "Дължина на Контекста",
"Continue Response": "Продължи отговора", "Continue Response": "Продължи отговора",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "Копирана е връзката за чат!", "Copied shared chat URL to clipboard!": "Копирана е връзката за чат!",
"Copy": "Копирай", "Copy": "Копирай",
"Copy last code block": "Копиране на последен код блок", "Copy last code block": "Копиране на последен код блок",
@ -378,6 +379,7 @@
"Notifications": "Десктоп Известия", "Notifications": "Десктоп Известия",
"November": "Ноември", "November": "Ноември",
"num_thread (Ollama)": "num_thread (Ollama)", "num_thread (Ollama)": "num_thread (Ollama)",
"OAuth ID": "",
"October": "Октомври", "October": "Октомври",
"Off": "Изкл.", "Off": "Изкл.",
"Okay, Let's Go!": "ОК, Нека започваме!", "Okay, Let's Go!": "ОК, Нека започваме!",

View File

@ -126,6 +126,7 @@
"Content": "বিষয়বস্তু", "Content": "বিষয়বস্তু",
"Context Length": "কনটেক্সটের দৈর্ঘ্য", "Context Length": "কনটেক্সটের দৈর্ঘ্য",
"Continue Response": "যাচাই করুন", "Continue Response": "যাচাই করুন",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "শেয়ারকৃত কথা-ব্যবহারের URL ক্লিপবোর্ডে কপি করা হয়েছে!", "Copied shared chat URL to clipboard!": "শেয়ারকৃত কথা-ব্যবহারের URL ক্লিপবোর্ডে কপি করা হয়েছে!",
"Copy": "অনুলিপি", "Copy": "অনুলিপি",
"Copy last code block": "সর্বশেষ কোড ব্লক কপি করুন", "Copy last code block": "সর্বশেষ কোড ব্লক কপি করুন",
@ -378,6 +379,7 @@
"Notifications": "নোটিফিকেশনসমূহ", "Notifications": "নোটিফিকেশনসমূহ",
"November": "নভেম্বর", "November": "নভেম্বর",
"num_thread (Ollama)": "num_thread (ওলামা)", "num_thread (Ollama)": "num_thread (ওলামা)",
"OAuth ID": "",
"October": "অক্টোবর", "October": "অক্টোবর",
"Off": "বন্ধ", "Off": "বন্ধ",
"Okay, Let's Go!": "ঠিক আছে, চলুন যাই!", "Okay, Let's Go!": "ঠিক আছে, চলুন যাই!",

View File

@ -126,6 +126,7 @@
"Content": "Contingut", "Content": "Contingut",
"Context Length": "Mida del context", "Context Length": "Mida del context",
"Continue Response": "Continuar la resposta", "Continue Response": "Continuar la resposta",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "S'ha copiat l'URL compartida al porta-retalls!", "Copied shared chat URL to clipboard!": "S'ha copiat l'URL compartida al porta-retalls!",
"Copy": "Copiar", "Copy": "Copiar",
"Copy last code block": "Copiar l'últim bloc de codi", "Copy last code block": "Copiar l'últim bloc de codi",
@ -378,6 +379,7 @@
"Notifications": "Notificacions", "Notifications": "Notificacions",
"November": "Novembre", "November": "Novembre",
"num_thread (Ollama)": "num_thread (Ollama)", "num_thread (Ollama)": "num_thread (Ollama)",
"OAuth ID": "",
"October": "Octubre", "October": "Octubre",
"Off": "Desactivat", "Off": "Desactivat",
"Okay, Let's Go!": "D'acord, som-hi!", "Okay, Let's Go!": "D'acord, som-hi!",

View File

@ -126,6 +126,7 @@
"Content": "Kontento", "Content": "Kontento",
"Context Length": "Ang gitas-on sa konteksto", "Context Length": "Ang gitas-on sa konteksto",
"Continue Response": "", "Continue Response": "",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "", "Copied shared chat URL to clipboard!": "",
"Copy": "", "Copy": "",
"Copy last code block": "Kopyaha ang katapusang bloke sa code", "Copy last code block": "Kopyaha ang katapusang bloke sa code",
@ -378,6 +379,7 @@
"Notifications": "Mga pahibalo sa desktop", "Notifications": "Mga pahibalo sa desktop",
"November": "", "November": "",
"num_thread (Ollama)": "", "num_thread (Ollama)": "",
"OAuth ID": "",
"October": "", "October": "",
"Off": "Napuo", "Off": "Napuo",
"Okay, Let's Go!": "Okay, lakaw na!", "Okay, Let's Go!": "Okay, lakaw na!",

View File

@ -126,6 +126,7 @@
"Content": "Info", "Content": "Info",
"Context Length": "Context Length", "Context Length": "Context Length",
"Continue Response": "Antwort fortsetzen", "Continue Response": "Antwort fortsetzen",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "Geteilte Chat-URL in die Zwischenablage kopiert!", "Copied shared chat URL to clipboard!": "Geteilte Chat-URL in die Zwischenablage kopiert!",
"Copy": "Kopieren", "Copy": "Kopieren",
"Copy last code block": "Letzten Codeblock kopieren", "Copy last code block": "Letzten Codeblock kopieren",
@ -378,6 +379,7 @@
"Notifications": "Desktop-Benachrichtigungen", "Notifications": "Desktop-Benachrichtigungen",
"November": "November", "November": "November",
"num_thread (Ollama)": "num_thread (Ollama)", "num_thread (Ollama)": "num_thread (Ollama)",
"OAuth ID": "",
"October": "Oktober", "October": "Oktober",
"Off": "Aus", "Off": "Aus",
"Okay, Let's Go!": "Okay, los geht's!", "Okay, Let's Go!": "Okay, los geht's!",

View File

@ -126,6 +126,7 @@
"Content": "Content", "Content": "Content",
"Context Length": "Context Length", "Context Length": "Context Length",
"Continue Response": "", "Continue Response": "",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "", "Copied shared chat URL to clipboard!": "",
"Copy": "", "Copy": "",
"Copy last code block": "Copy last code block", "Copy last code block": "Copy last code block",
@ -378,6 +379,7 @@
"Notifications": "Notifications", "Notifications": "Notifications",
"November": "", "November": "",
"num_thread (Ollama)": "", "num_thread (Ollama)": "",
"OAuth ID": "",
"October": "", "October": "",
"Off": "Off", "Off": "Off",
"Okay, Let's Go!": "Okay, Let's Go!", "Okay, Let's Go!": "Okay, Let's Go!",

View File

@ -126,6 +126,7 @@
"Content": "", "Content": "",
"Context Length": "", "Context Length": "",
"Continue Response": "", "Continue Response": "",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "", "Copied shared chat URL to clipboard!": "",
"Copy": "", "Copy": "",
"Copy last code block": "", "Copy last code block": "",
@ -378,6 +379,7 @@
"Notifications": "", "Notifications": "",
"November": "", "November": "",
"num_thread (Ollama)": "", "num_thread (Ollama)": "",
"OAuth ID": "",
"October": "", "October": "",
"Off": "", "Off": "",
"Okay, Let's Go!": "", "Okay, Let's Go!": "",

View File

@ -126,6 +126,7 @@
"Content": "", "Content": "",
"Context Length": "", "Context Length": "",
"Continue Response": "", "Continue Response": "",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "", "Copied shared chat URL to clipboard!": "",
"Copy": "", "Copy": "",
"Copy last code block": "", "Copy last code block": "",
@ -378,6 +379,7 @@
"Notifications": "", "Notifications": "",
"November": "", "November": "",
"num_thread (Ollama)": "", "num_thread (Ollama)": "",
"OAuth ID": "",
"October": "", "October": "",
"Off": "", "Off": "",
"Okay, Let's Go!": "", "Okay, Let's Go!": "",

View File

@ -126,6 +126,7 @@
"Content": "Contenido", "Content": "Contenido",
"Context Length": "Longitud del contexto", "Context Length": "Longitud del contexto",
"Continue Response": "Continuar Respuesta", "Continue Response": "Continuar Respuesta",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "¡URL de chat compartido copiado al portapapeles!", "Copied shared chat URL to clipboard!": "¡URL de chat compartido copiado al portapapeles!",
"Copy": "Copiar", "Copy": "Copiar",
"Copy last code block": "Copia el último bloque de código", "Copy last code block": "Copia el último bloque de código",
@ -378,6 +379,7 @@
"Notifications": "Notificaciones", "Notifications": "Notificaciones",
"November": "Noviembre", "November": "Noviembre",
"num_thread (Ollama)": "num_thread (Ollama)", "num_thread (Ollama)": "num_thread (Ollama)",
"OAuth ID": "",
"October": "Octubre", "October": "Octubre",
"Off": "Desactivado", "Off": "Desactivado",
"Okay, Let's Go!": "Bien, ¡Vamos!", "Okay, Let's Go!": "Bien, ¡Vamos!",

View File

@ -126,6 +126,7 @@
"Content": "محتوا", "Content": "محتوا",
"Context Length": "طول زمینه", "Context Length": "طول زمینه",
"Continue Response": "ادامه پاسخ", "Continue Response": "ادامه پاسخ",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "URL چت به کلیپ بورد کپی شد!", "Copied shared chat URL to clipboard!": "URL چت به کلیپ بورد کپی شد!",
"Copy": "کپی", "Copy": "کپی",
"Copy last code block": "کپی آخرین بلوک کد", "Copy last code block": "کپی آخرین بلوک کد",
@ -378,6 +379,7 @@
"Notifications": "اعلان", "Notifications": "اعلان",
"November": "نوامبر", "November": "نوامبر",
"num_thread (Ollama)": "num_thread (اولاما)", "num_thread (Ollama)": "num_thread (اولاما)",
"OAuth ID": "",
"October": "اکتبر", "October": "اکتبر",
"Off": "خاموش", "Off": "خاموش",
"Okay, Let's Go!": "باشه، بزن بریم!", "Okay, Let's Go!": "باشه، بزن بریم!",

View File

@ -126,6 +126,7 @@
"Content": "Sisältö", "Content": "Sisältö",
"Context Length": "Kontekstin pituus", "Context Length": "Kontekstin pituus",
"Continue Response": "Jatka vastausta", "Continue Response": "Jatka vastausta",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "Jaettu keskustelulinkki kopioitu leikepöydälle!", "Copied shared chat URL to clipboard!": "Jaettu keskustelulinkki kopioitu leikepöydälle!",
"Copy": "Kopioi", "Copy": "Kopioi",
"Copy last code block": "Kopioi viimeisin koodilohko", "Copy last code block": "Kopioi viimeisin koodilohko",
@ -378,6 +379,7 @@
"Notifications": "Ilmoitukset", "Notifications": "Ilmoitukset",
"November": "marraskuu", "November": "marraskuu",
"num_thread (Ollama)": "num_thread (Ollama)", "num_thread (Ollama)": "num_thread (Ollama)",
"OAuth ID": "",
"October": "lokakuu", "October": "lokakuu",
"Off": "Pois", "Off": "Pois",
"Okay, Let's Go!": "Eikun menoksi!", "Okay, Let's Go!": "Eikun menoksi!",

View File

@ -126,6 +126,7 @@
"Content": "Contenu", "Content": "Contenu",
"Context Length": "Longueur du contexte", "Context Length": "Longueur du contexte",
"Continue Response": "Continuer la réponse", "Continue Response": "Continuer la réponse",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "URL de chat partagé copié dans le presse-papier !", "Copied shared chat URL to clipboard!": "URL de chat partagé copié dans le presse-papier !",
"Copy": "Copier", "Copy": "Copier",
"Copy last code block": "Copier le dernier bloc de code", "Copy last code block": "Copier le dernier bloc de code",
@ -378,6 +379,7 @@
"Notifications": "Notifications de bureau", "Notifications": "Notifications de bureau",
"November": "Novembre", "November": "Novembre",
"num_thread (Ollama)": "num_thread (Ollama)", "num_thread (Ollama)": "num_thread (Ollama)",
"OAuth ID": "",
"October": "Octobre", "October": "Octobre",
"Off": "Éteint", "Off": "Éteint",
"Okay, Let's Go!": "Okay, Allons-y !", "Okay, Let's Go!": "Okay, Allons-y !",

View File

@ -126,6 +126,7 @@
"Content": "Contenu", "Content": "Contenu",
"Context Length": "Longueur du contexte", "Context Length": "Longueur du contexte",
"Continue Response": "Continuer la Réponse", "Continue Response": "Continuer la Réponse",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "URL du chat copié dans le presse-papiers !", "Copied shared chat URL to clipboard!": "URL du chat copié dans le presse-papiers !",
"Copy": "Copier", "Copy": "Copier",
"Copy last code block": "Copier le dernier bloc de code", "Copy last code block": "Copier le dernier bloc de code",
@ -378,6 +379,7 @@
"Notifications": "Notifications de bureau", "Notifications": "Notifications de bureau",
"November": "Novembre", "November": "Novembre",
"num_thread (Ollama)": "num_thread (Ollama)", "num_thread (Ollama)": "num_thread (Ollama)",
"OAuth ID": "",
"October": "Octobre", "October": "Octobre",
"Off": "Désactivé", "Off": "Désactivé",
"Okay, Let's Go!": "D'accord, allons-y !", "Okay, Let's Go!": "D'accord, allons-y !",

View File

@ -126,6 +126,7 @@
"Content": "תוכן", "Content": "תוכן",
"Context Length": "אורך הקשר", "Context Length": "אורך הקשר",
"Continue Response": "המשך תגובה", "Continue Response": "המשך תגובה",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "העתקת כתובת URL של צ'אט משותף ללוח!", "Copied shared chat URL to clipboard!": "העתקת כתובת URL של צ'אט משותף ללוח!",
"Copy": "העתק", "Copy": "העתק",
"Copy last code block": "העתק את בלוק הקוד האחרון", "Copy last code block": "העתק את בלוק הקוד האחרון",
@ -378,6 +379,7 @@
"Notifications": "התראות", "Notifications": "התראות",
"November": "נובמבר", "November": "נובמבר",
"num_thread (Ollama)": "num_thread (Ollama)", "num_thread (Ollama)": "num_thread (Ollama)",
"OAuth ID": "",
"October": "אוקטובר", "October": "אוקטובר",
"Off": "כבוי", "Off": "כבוי",
"Okay, Let's Go!": "בסדר, בואו נתחיל!", "Okay, Let's Go!": "בסדר, בואו נתחיל!",

View File

@ -126,6 +126,7 @@
"Content": "सामग्री", "Content": "सामग्री",
"Context Length": "प्रसंग की लंबाई", "Context Length": "प्रसंग की लंबाई",
"Continue Response": "प्रतिक्रिया जारी रखें", "Continue Response": "प्रतिक्रिया जारी रखें",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "साझा चैट URL को क्लिपबोर्ड पर कॉपी किया गया!", "Copied shared chat URL to clipboard!": "साझा चैट URL को क्लिपबोर्ड पर कॉपी किया गया!",
"Copy": "कॉपी", "Copy": "कॉपी",
"Copy last code block": "अंतिम कोड ब्लॉक कॉपी करें", "Copy last code block": "अंतिम कोड ब्लॉक कॉपी करें",
@ -378,6 +379,7 @@
"Notifications": "सूचनाएं", "Notifications": "सूचनाएं",
"November": "नवंबर", "November": "नवंबर",
"num_thread (Ollama)": "num_thread (ओलामा)", "num_thread (Ollama)": "num_thread (ओलामा)",
"OAuth ID": "",
"October": "अक्टूबर", "October": "अक्टूबर",
"Off": "बंद", "Off": "बंद",
"Okay, Let's Go!": "ठीक है, चलिए चलते हैं!", "Okay, Let's Go!": "ठीक है, चलिए चलते हैं!",

View File

@ -126,6 +126,7 @@
"Content": "Sadržaj", "Content": "Sadržaj",
"Context Length": "Dužina konteksta", "Context Length": "Dužina konteksta",
"Continue Response": "Nastavi odgovor", "Continue Response": "Nastavi odgovor",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "URL dijeljenog razgovora kopiran u međuspremnik!", "Copied shared chat URL to clipboard!": "URL dijeljenog razgovora kopiran u međuspremnik!",
"Copy": "Kopiraj", "Copy": "Kopiraj",
"Copy last code block": "Kopiraj zadnji blok koda", "Copy last code block": "Kopiraj zadnji blok koda",
@ -378,6 +379,7 @@
"Notifications": "Obavijesti", "Notifications": "Obavijesti",
"November": "Studeni", "November": "Studeni",
"num_thread (Ollama)": "num_thread (Ollama)", "num_thread (Ollama)": "num_thread (Ollama)",
"OAuth ID": "",
"October": "Listopad", "October": "Listopad",
"Off": "Isključeno", "Off": "Isključeno",
"Okay, Let's Go!": "U redu, idemo!", "Okay, Let's Go!": "U redu, idemo!",

View File

@ -126,6 +126,7 @@
"Content": "Contenuto", "Content": "Contenuto",
"Context Length": "Lunghezza contesto", "Context Length": "Lunghezza contesto",
"Continue Response": "Continua risposta", "Continue Response": "Continua risposta",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "URL della chat condivisa copiato negli appunti!", "Copied shared chat URL to clipboard!": "URL della chat condivisa copiato negli appunti!",
"Copy": "Copia", "Copy": "Copia",
"Copy last code block": "Copia ultimo blocco di codice", "Copy last code block": "Copia ultimo blocco di codice",
@ -378,6 +379,7 @@
"Notifications": "Notifiche desktop", "Notifications": "Notifiche desktop",
"November": "Novembre", "November": "Novembre",
"num_thread (Ollama)": "num_thread (Ollama)", "num_thread (Ollama)": "num_thread (Ollama)",
"OAuth ID": "",
"October": "Ottobre", "October": "Ottobre",
"Off": "Disattivato", "Off": "Disattivato",
"Okay, Let's Go!": "Ok, andiamo!", "Okay, Let's Go!": "Ok, andiamo!",

View File

@ -126,6 +126,7 @@
"Content": "コンテンツ", "Content": "コンテンツ",
"Context Length": "コンテキストの長さ", "Context Length": "コンテキストの長さ",
"Continue Response": "続きの応答", "Continue Response": "続きの応答",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "共有チャットURLをクリップボードにコピーしました", "Copied shared chat URL to clipboard!": "共有チャットURLをクリップボードにコピーしました",
"Copy": "コピー", "Copy": "コピー",
"Copy last code block": "最後のコードブロックをコピー", "Copy last code block": "最後のコードブロックをコピー",
@ -378,6 +379,7 @@
"Notifications": "デスクトップ通知", "Notifications": "デスクトップ通知",
"November": "11月", "November": "11月",
"num_thread (Ollama)": "num_thread(オラマ)", "num_thread (Ollama)": "num_thread(オラマ)",
"OAuth ID": "",
"October": "10月", "October": "10月",
"Off": "オフ", "Off": "オフ",
"Okay, Let's Go!": "OK、始めましょう", "Okay, Let's Go!": "OK、始めましょう",

View File

@ -126,6 +126,7 @@
"Content": "კონტენტი", "Content": "კონტენტი",
"Context Length": "კონტექსტის სიგრძე", "Context Length": "კონტექსტის სიგრძე",
"Continue Response": "პასუხის გაგრძელება", "Continue Response": "პასუხის გაგრძელება",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "ყავს ჩათის URL-ი კლიპბორდში!", "Copied shared chat URL to clipboard!": "ყავს ჩათის URL-ი კლიპბორდში!",
"Copy": "კოპირება", "Copy": "კოპირება",
"Copy last code block": "ბოლო ბლოკის კოპირება", "Copy last code block": "ბოლო ბლოკის კოპირება",
@ -378,6 +379,7 @@
"Notifications": "შეტყობინება", "Notifications": "შეტყობინება",
"November": "ნოემბერი", "November": "ნოემბერი",
"num_thread (Ollama)": "num_thread (ოლამა)", "num_thread (Ollama)": "num_thread (ოლამა)",
"OAuth ID": "",
"October": "ოქტომბერი", "October": "ოქტომბერი",
"Off": "გამორთვა", "Off": "გამორთვა",
"Okay, Let's Go!": "კარგი, წავედით!", "Okay, Let's Go!": "კარგი, წავედით!",

View File

@ -126,6 +126,7 @@
"Content": "내용", "Content": "내용",
"Context Length": "내용 길이", "Context Length": "내용 길이",
"Continue Response": "대화 계속", "Continue Response": "대화 계속",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "공유 채팅 URL이 클립보드에 복사되었습니다!", "Copied shared chat URL to clipboard!": "공유 채팅 URL이 클립보드에 복사되었습니다!",
"Copy": "복사", "Copy": "복사",
"Copy last code block": "마지막 코드 블록 복사", "Copy last code block": "마지막 코드 블록 복사",
@ -378,6 +379,7 @@
"Notifications": "알림", "Notifications": "알림",
"November": "11월", "November": "11월",
"num_thread (Ollama)": "num_thread (올라마)", "num_thread (Ollama)": "num_thread (올라마)",
"OAuth ID": "",
"October": "10월", "October": "10월",
"Off": "끄기", "Off": "끄기",
"Okay, Let's Go!": "좋아요, 시작합시다!", "Okay, Let's Go!": "좋아요, 시작합시다!",

View File

@ -126,6 +126,7 @@
"Content": "Turinys", "Content": "Turinys",
"Context Length": "Konteksto ilgis", "Context Length": "Konteksto ilgis",
"Continue Response": "Tęsti atsakymą", "Continue Response": "Tęsti atsakymą",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "Nukopijavote pokalbio nuorodą", "Copied shared chat URL to clipboard!": "Nukopijavote pokalbio nuorodą",
"Copy": "Kopijuoti", "Copy": "Kopijuoti",
"Copy last code block": "Kopijuoti paskutinį kodo bloką", "Copy last code block": "Kopijuoti paskutinį kodo bloką",
@ -378,6 +379,7 @@
"Notifications": "Pranešimai", "Notifications": "Pranešimai",
"November": "lapkritis", "November": "lapkritis",
"num_thread (Ollama)": "", "num_thread (Ollama)": "",
"OAuth ID": "",
"October": "spalis", "October": "spalis",
"Off": "Išjungta", "Off": "Išjungta",
"Okay, Let's Go!": "Gerai, važiuojam!", "Okay, Let's Go!": "Gerai, važiuojam!",

View File

@ -126,6 +126,7 @@
"Content": "Innhold", "Content": "Innhold",
"Context Length": "Kontekstlengde", "Context Length": "Kontekstlengde",
"Continue Response": "Fortsett svar", "Continue Response": "Fortsett svar",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "Kopiert delt chat-URL til utklippstavlen!", "Copied shared chat URL to clipboard!": "Kopiert delt chat-URL til utklippstavlen!",
"Copy": "Kopier", "Copy": "Kopier",
"Copy last code block": "Kopier siste kodeblokk", "Copy last code block": "Kopier siste kodeblokk",
@ -378,6 +379,7 @@
"Notifications": "Varsler", "Notifications": "Varsler",
"November": "November", "November": "November",
"num_thread (Ollama)": "num_thread (Ollama)", "num_thread (Ollama)": "num_thread (Ollama)",
"OAuth ID": "",
"October": "Oktober", "October": "Oktober",
"Off": "Av", "Off": "Av",
"Okay, Let's Go!": "Ok, la oss gå!", "Okay, Let's Go!": "Ok, la oss gå!",

View File

@ -126,6 +126,7 @@
"Content": "Inhoud", "Content": "Inhoud",
"Context Length": "Context Lengte", "Context Length": "Context Lengte",
"Continue Response": "Doorgaan met Antwoord", "Continue Response": "Doorgaan met Antwoord",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "URL van gedeelde gesprekspagina gekopieerd naar klembord!", "Copied shared chat URL to clipboard!": "URL van gedeelde gesprekspagina gekopieerd naar klembord!",
"Copy": "Kopieer", "Copy": "Kopieer",
"Copy last code block": "Kopieer laatste code blok", "Copy last code block": "Kopieer laatste code blok",
@ -378,6 +379,7 @@
"Notifications": "Desktop Notificaties", "Notifications": "Desktop Notificaties",
"November": "November", "November": "November",
"num_thread (Ollama)": "num_thread (Ollama)", "num_thread (Ollama)": "num_thread (Ollama)",
"OAuth ID": "",
"October": "Oktober", "October": "Oktober",
"Off": "Uit", "Off": "Uit",
"Okay, Let's Go!": "Okay, Laten we gaan!", "Okay, Let's Go!": "Okay, Laten we gaan!",

View File

@ -126,6 +126,7 @@
"Content": "ਸਮੱਗਰੀ", "Content": "ਸਮੱਗਰੀ",
"Context Length": "ਸੰਦਰਭ ਲੰਬਾਈ", "Context Length": "ਸੰਦਰਭ ਲੰਬਾਈ",
"Continue Response": "ਜਵਾਬ ਜਾਰੀ ਰੱਖੋ", "Continue Response": "ਜਵਾਬ ਜਾਰੀ ਰੱਖੋ",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "ਸਾਂਝੇ ਕੀਤੇ ਗੱਲਬਾਤ URL ਨੂੰ ਕਲਿੱਪਬੋਰਡ 'ਤੇ ਕਾਪੀ ਕਰ ਦਿੱਤਾ!", "Copied shared chat URL to clipboard!": "ਸਾਂਝੇ ਕੀਤੇ ਗੱਲਬਾਤ URL ਨੂੰ ਕਲਿੱਪਬੋਰਡ 'ਤੇ ਕਾਪੀ ਕਰ ਦਿੱਤਾ!",
"Copy": "ਕਾਪੀ ਕਰੋ", "Copy": "ਕਾਪੀ ਕਰੋ",
"Copy last code block": "ਆਖਰੀ ਕੋਡ ਬਲਾਕ ਨੂੰ ਕਾਪੀ ਕਰੋ", "Copy last code block": "ਆਖਰੀ ਕੋਡ ਬਲਾਕ ਨੂੰ ਕਾਪੀ ਕਰੋ",
@ -378,6 +379,7 @@
"Notifications": "ਸੂਚਨਾਵਾਂ", "Notifications": "ਸੂਚਨਾਵਾਂ",
"November": "ਨਵੰਬਰ", "November": "ਨਵੰਬਰ",
"num_thread (Ollama)": "num_thread (ਓਲਾਮਾ)", "num_thread (Ollama)": "num_thread (ਓਲਾਮਾ)",
"OAuth ID": "",
"October": "ਅਕਤੂਬਰ", "October": "ਅਕਤੂਬਰ",
"Off": "ਬੰਦ", "Off": "ਬੰਦ",
"Okay, Let's Go!": "ਠੀਕ ਹੈ, ਚੱਲੋ ਚੱਲੀਏ!", "Okay, Let's Go!": "ਠੀਕ ਹੈ, ਚੱਲੋ ਚੱਲੀਏ!",

View File

@ -126,6 +126,7 @@
"Content": "Zawartość", "Content": "Zawartość",
"Context Length": "Długość kontekstu", "Context Length": "Długość kontekstu",
"Continue Response": "Kontynuuj odpowiedź", "Continue Response": "Kontynuuj odpowiedź",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "Skopiowano URL czatu do schowka!", "Copied shared chat URL to clipboard!": "Skopiowano URL czatu do schowka!",
"Copy": "Kopiuj", "Copy": "Kopiuj",
"Copy last code block": "Skopiuj ostatni blok kodu", "Copy last code block": "Skopiuj ostatni blok kodu",
@ -378,6 +379,7 @@
"Notifications": "Powiadomienia", "Notifications": "Powiadomienia",
"November": "Listopad", "November": "Listopad",
"num_thread (Ollama)": "num_thread (Ollama)", "num_thread (Ollama)": "num_thread (Ollama)",
"OAuth ID": "",
"October": "Październik", "October": "Październik",
"Off": "Wyłączony", "Off": "Wyłączony",
"Okay, Let's Go!": "Okej, zaczynamy!", "Okay, Let's Go!": "Okej, zaczynamy!",

View File

@ -126,6 +126,7 @@
"Content": "Conteúdo", "Content": "Conteúdo",
"Context Length": "Comprimento do Contexto", "Context Length": "Comprimento do Contexto",
"Continue Response": "Continuar resposta", "Continue Response": "Continuar resposta",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "URL de bate-papo compartilhado copiada com sucesso!", "Copied shared chat URL to clipboard!": "URL de bate-papo compartilhado copiada com sucesso!",
"Copy": "Copiar", "Copy": "Copiar",
"Copy last code block": "Copiar último bloco de código", "Copy last code block": "Copiar último bloco de código",
@ -378,6 +379,7 @@
"Notifications": "Notificações da Área de Trabalho", "Notifications": "Notificações da Área de Trabalho",
"November": "Novembro", "November": "Novembro",
"num_thread (Ollama)": "num_thread (Ollama)", "num_thread (Ollama)": "num_thread (Ollama)",
"OAuth ID": "",
"October": "Outubro", "October": "Outubro",
"Off": "Desligado", "Off": "Desligado",
"Okay, Let's Go!": "Ok, Vamos Lá!", "Okay, Let's Go!": "Ok, Vamos Lá!",

View File

@ -126,6 +126,7 @@
"Content": "Conteúdo", "Content": "Conteúdo",
"Context Length": "Comprimento do Contexto", "Context Length": "Comprimento do Contexto",
"Continue Response": "Continuar resposta", "Continue Response": "Continuar resposta",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "URL de Conversa partilhado copiada com sucesso!", "Copied shared chat URL to clipboard!": "URL de Conversa partilhado copiada com sucesso!",
"Copy": "Copiar", "Copy": "Copiar",
"Copy last code block": "Copiar último bloco de código", "Copy last code block": "Copiar último bloco de código",
@ -378,6 +379,7 @@
"Notifications": "Notificações da Área de Trabalho", "Notifications": "Notificações da Área de Trabalho",
"November": "Novembro", "November": "Novembro",
"num_thread (Ollama)": "num_thread (Ollama)", "num_thread (Ollama)": "num_thread (Ollama)",
"OAuth ID": "",
"October": "Outubro", "October": "Outubro",
"Off": "Desligado", "Off": "Desligado",
"Okay, Let's Go!": "Ok, Vamos Lá!", "Okay, Let's Go!": "Ok, Vamos Lá!",

View File

@ -126,6 +126,7 @@
"Content": "Содержание", "Content": "Содержание",
"Context Length": "Длина контексту", "Context Length": "Длина контексту",
"Continue Response": "Продолжить ответ", "Continue Response": "Продолжить ответ",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "Копирование общей ссылки чат в буфер обмена!", "Copied shared chat URL to clipboard!": "Копирование общей ссылки чат в буфер обмена!",
"Copy": "Копировать", "Copy": "Копировать",
"Copy last code block": "Копировать последний блок кода", "Copy last code block": "Копировать последний блок кода",
@ -378,6 +379,7 @@
"Notifications": "Уведомления на рабочем столе", "Notifications": "Уведомления на рабочем столе",
"November": "Ноябрь", "November": "Ноябрь",
"num_thread (Ollama)": "num_thread (Оллама)", "num_thread (Ollama)": "num_thread (Оллама)",
"OAuth ID": "",
"October": "Октябрь", "October": "Октябрь",
"Off": "Выключено.", "Off": "Выключено.",
"Okay, Let's Go!": "Давайте начнём!", "Okay, Let's Go!": "Давайте начнём!",

View File

@ -126,6 +126,7 @@
"Content": "Садржај", "Content": "Садржај",
"Context Length": "Дужина контекста", "Context Length": "Дужина контекста",
"Continue Response": "Настави одговор", "Continue Response": "Настави одговор",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "Адреса дељеног ћаскања ископирана у оставу!", "Copied shared chat URL to clipboard!": "Адреса дељеног ћаскања ископирана у оставу!",
"Copy": "Копирај", "Copy": "Копирај",
"Copy last code block": "Копирај последњи блок кода", "Copy last code block": "Копирај последњи блок кода",
@ -378,6 +379,7 @@
"Notifications": "Обавештења", "Notifications": "Обавештења",
"November": "Новембар", "November": "Новембар",
"num_thread (Ollama)": "нум _тхреад (Оллама)", "num_thread (Ollama)": "нум _тхреад (Оллама)",
"OAuth ID": "",
"October": "Октобар", "October": "Октобар",
"Off": "Искључено", "Off": "Искључено",
"Okay, Let's Go!": "У реду, хајде да кренемо!", "Okay, Let's Go!": "У реду, хајде да кренемо!",

View File

@ -126,6 +126,7 @@
"Content": "Innehåll", "Content": "Innehåll",
"Context Length": "Kontextlängd", "Context Length": "Kontextlängd",
"Continue Response": "Fortsätt svar", "Continue Response": "Fortsätt svar",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "Kopierad delad chatt-URL till urklipp!", "Copied shared chat URL to clipboard!": "Kopierad delad chatt-URL till urklipp!",
"Copy": "Kopiera", "Copy": "Kopiera",
"Copy last code block": "Kopiera sista kodblock", "Copy last code block": "Kopiera sista kodblock",
@ -378,6 +379,7 @@
"Notifications": "Notifikationer", "Notifications": "Notifikationer",
"November": "november", "November": "november",
"num_thread (Ollama)": "num_thread (Ollama)", "num_thread (Ollama)": "num_thread (Ollama)",
"OAuth ID": "",
"October": "oktober", "October": "oktober",
"Off": "Av", "Off": "Av",
"Okay, Let's Go!": "Okej, nu kör vi!", "Okay, Let's Go!": "Okej, nu kör vi!",

View File

@ -126,6 +126,7 @@
"Content": "", "Content": "",
"Context Length": "", "Context Length": "",
"Continue Response": "", "Continue Response": "",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "", "Copied shared chat URL to clipboard!": "",
"Copy": "", "Copy": "",
"Copy last code block": "", "Copy last code block": "",
@ -378,6 +379,7 @@
"Notifications": "", "Notifications": "",
"November": "", "November": "",
"num_thread (Ollama)": "", "num_thread (Ollama)": "",
"OAuth ID": "",
"October": "", "October": "",
"Off": "", "Off": "",
"Okay, Let's Go!": "", "Okay, Let's Go!": "",

View File

@ -126,6 +126,7 @@
"Content": "İçerik", "Content": "İçerik",
"Context Length": "Bağlam Uzunluğu", "Context Length": "Bağlam Uzunluğu",
"Continue Response": "Yanıta Devam Et", "Continue Response": "Yanıta Devam Et",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "Paylaşılan sohbet URL'si panoya kopyalandı!", "Copied shared chat URL to clipboard!": "Paylaşılan sohbet URL'si panoya kopyalandı!",
"Copy": "Kopyala", "Copy": "Kopyala",
"Copy last code block": "Son kod bloğunu kopyala", "Copy last code block": "Son kod bloğunu kopyala",
@ -378,6 +379,7 @@
"Notifications": "Bildirimler", "Notifications": "Bildirimler",
"November": "Kasım", "November": "Kasım",
"num_thread (Ollama)": "num_thread (Ollama)", "num_thread (Ollama)": "num_thread (Ollama)",
"OAuth ID": "",
"October": "Ekim", "October": "Ekim",
"Off": "Kapalı", "Off": "Kapalı",
"Okay, Let's Go!": "Tamam, Hadi Başlayalım!", "Okay, Let's Go!": "Tamam, Hadi Başlayalım!",

View File

@ -126,6 +126,7 @@
"Content": "Зміст", "Content": "Зміст",
"Context Length": "Довжина контексту", "Context Length": "Довжина контексту",
"Continue Response": "Продовжити відповідь", "Continue Response": "Продовжити відповідь",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "Скопійовано URL-адресу спільного чату в буфер обміну!", "Copied shared chat URL to clipboard!": "Скопійовано URL-адресу спільного чату в буфер обміну!",
"Copy": "Копіювати", "Copy": "Копіювати",
"Copy last code block": "Копіювати останній блок коду", "Copy last code block": "Копіювати останній блок коду",
@ -378,6 +379,7 @@
"Notifications": "Сповіщення", "Notifications": "Сповіщення",
"November": "Листопад", "November": "Листопад",
"num_thread (Ollama)": "num_thread (Ollama)", "num_thread (Ollama)": "num_thread (Ollama)",
"OAuth ID": "",
"October": "Жовтень", "October": "Жовтень",
"Off": "Вимк", "Off": "Вимк",
"Okay, Let's Go!": "Гаразд, давайте почнемо!", "Okay, Let's Go!": "Гаразд, давайте почнемо!",

View File

@ -126,6 +126,7 @@
"Content": "Nội dung", "Content": "Nội dung",
"Context Length": "Độ dài ngữ cảnh (Context Length)", "Context Length": "Độ dài ngữ cảnh (Context Length)",
"Continue Response": "Tiếp tục trả lời", "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!", "Copied shared chat URL to clipboard!": "Đã sao chép link chia sẻ trò chuyện vào clipboard!",
"Copy": "Sao chép", "Copy": "Sao chép",
"Copy last code block": "Sao chép khối mã cuối cùng", "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)", "Notifications": "Thông báo trên máy tính (Notification)",
"November": "Tháng 11", "November": "Tháng 11",
"num_thread (Ollama)": "num_thread (Ollama)", "num_thread (Ollama)": "num_thread (Ollama)",
"OAuth ID": "",
"October": "Tháng 10", "October": "Tháng 10",
"Off": "Tắt", "Off": "Tắt",
"Okay, Let's Go!": "Được rồi, Bắt đầu thôi!", "Okay, Let's Go!": "Được rồi, Bắt đầu thôi!",

View File

@ -126,6 +126,7 @@
"Content": "内容", "Content": "内容",
"Context Length": "上下文长度", "Context Length": "上下文长度",
"Continue Response": "继续生成", "Continue Response": "继续生成",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "已复制此对话分享链接至剪贴板!", "Copied shared chat URL to clipboard!": "已复制此对话分享链接至剪贴板!",
"Copy": "复制", "Copy": "复制",
"Copy last code block": "复制最后一个代码块中的代码", "Copy last code block": "复制最后一个代码块中的代码",
@ -378,6 +379,7 @@
"Notifications": "桌面通知", "Notifications": "桌面通知",
"November": "十一月", "November": "十一月",
"num_thread (Ollama)": "num_threadOllama", "num_thread (Ollama)": "num_threadOllama",
"OAuth ID": "",
"October": "十月", "October": "十月",
"Off": "关闭", "Off": "关闭",
"Okay, Let's Go!": "确认,开始使用!", "Okay, Let's Go!": "确认,开始使用!",

View File

@ -126,6 +126,7 @@
"Content": "內容", "Content": "內容",
"Context Length": "上下文長度", "Context Length": "上下文長度",
"Continue Response": "繼續回答", "Continue Response": "繼續回答",
"Continue with {{provider}}": "",
"Copied shared chat URL to clipboard!": "已複製共享聊天連結到剪貼簿!", "Copied shared chat URL to clipboard!": "已複製共享聊天連結到剪貼簿!",
"Copy": "複製", "Copy": "複製",
"Copy last code block": "複製最後一個程式碼區塊", "Copy last code block": "複製最後一個程式碼區塊",
@ -378,6 +379,7 @@
"Notifications": "通知", "Notifications": "通知",
"November": "11 月", "November": "11 月",
"num_thread (Ollama)": "num_threadOllama", "num_thread (Ollama)": "num_threadOllama",
"OAuth ID": "",
"October": "10 月", "October": "10 月",
"Off": "關閉", "Off": "關閉",
"Okay, Let's Go!": "好的,啟動吧!", "Okay, Let's Go!": "好的,啟動吧!",

View File

@ -149,6 +149,11 @@ type Config = {
enable_admin_export: boolean; enable_admin_export: boolean;
enable_community_sharing: boolean; enable_community_sharing: boolean;
}; };
oauth: {
providers: {
[key: string]: string;
};
};
}; };
type PromptSuggestion = { type PromptSuggestion = {

View File

@ -195,6 +195,18 @@
<span class="invisible"></span> <span class="invisible"></span>
{/if} {/if}
</th> </th>
<th
scope="col"
class="px-3 py-2 cursor-pointer select-none"
on:click={() => setSortKey('oauth_sub')}
>
{$i18n.t('OAuth ID')}
{#if sortKey === 'oauth_sub'}
{sortOrder === 'asc' ? '▲' : '▼'}
{:else}
<span class="invisible"></span>
{/if}
</th>
<th <th
scope="col" scope="col"
class="px-3 py-2 cursor-pointer select-none" class="px-3 py-2 cursor-pointer select-none"
@ -283,6 +295,8 @@
</td> </td>
<td class=" px-3 py-2"> {user.email} </td> <td class=" px-3 py-2"> {user.email} </td>
<td class=" px-3 py-2"> {user.oauth_sub ?? ''} </td>
<td class=" px-3 py-2"> <td class=" px-3 py-2">
{dayjs(user.last_active_at * 1000).fromNow()} {dayjs(user.last_active_at * 1000).fromNow()}
</td> </td>

View File

@ -18,6 +18,7 @@
USAGE_POOL USAGE_POOL
} from '$lib/stores'; } from '$lib/stores';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { Toaster, toast } from 'svelte-sonner'; import { Toaster, toast } from 'svelte-sonner';
import { getBackendConfig } from '$lib/apis'; import { getBackendConfig } from '$lib/apis';
@ -141,7 +142,11 @@
await goto('/auth'); await goto('/auth');
} }
} else { } 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 { } else {

View File

@ -1,12 +1,13 @@
<script> <script>
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { userSignIn, userSignUp } from '$lib/apis/auths'; import { getSessionUser, userSignIn, userSignUp } from '$lib/apis/auths';
import Spinner from '$lib/components/common/Spinner.svelte'; import Spinner from '$lib/components/common/Spinner.svelte';
import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants'; import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
import { WEBUI_NAME, config, user, socket } from '$lib/stores'; import { WEBUI_NAME, config, user, socket } from '$lib/stores';
import { onMount, getContext } from 'svelte'; import { onMount, getContext } from 'svelte';
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
import { generateInitialsImage, canvasPixelTest } from '$lib/utils'; import { generateInitialsImage, canvasPixelTest } from '$lib/utils';
import { page } from '$app/stores';
const i18n = getContext('i18n'); const i18n = getContext('i18n');
@ -21,7 +22,9 @@
if (sessionUser) { if (sessionUser) {
console.log(sessionUser); console.log(sessionUser);
toast.success($i18n.t(`You're now logged in.`)); toast.success($i18n.t(`You're now logged in.`));
localStorage.token = sessionUser.token; if (sessionUser.token) {
localStorage.token = sessionUser.token;
}
$socket.emit('user-join', { auth: { token: sessionUser.token } }); $socket.emit('user-join', { auth: { token: sessionUser.token } });
await user.set(sessionUser); await user.set(sessionUser);
@ -57,10 +60,35 @@
} }
}; };
const checkOauthCallback = async () => {
if (!$page.url.hash) {
return;
}
const hash = $page.url.hash.substring(1);
if (!hash) {
return;
}
const params = new URLSearchParams(hash);
const token = params.get('token');
if (!token) {
return;
}
const sessionUser = await getSessionUser(token).catch((error) => {
toast.error(error);
return null;
});
if (!sessionUser) {
return;
}
localStorage.token = token;
await setSessionUser(sessionUser);
};
onMount(async () => { onMount(async () => {
if ($user !== undefined) { if ($user !== undefined) {
await goto('/'); await goto('/');
} }
await checkOauthCallback();
loaded = true; loaded = true;
if (($config?.features.auth_trusted_header ?? false) || $config?.features.auth === false) { if (($config?.features.auth_trusted_header ?? false) || $config?.features.auth === false) {
await signInHandler(); await signInHandler();
@ -219,6 +247,97 @@
{/if} {/if}
</div> </div>
</form> </form>
{#if Object.keys($config?.oauth?.providers ?? {}).length > 0}
<div class="inline-flex items-center justify-center w-full">
<hr class="w-64 h-px my-8 bg-gray-200 border-0 dark:bg-gray-700" />
<span
class="absolute px-3 font-medium text-gray-900 -translate-x-1/2 bg-white left-1/2 dark:text-white dark:bg-gray-950"
>{$i18n.t('or')}</span
>
</div>
<div class="flex flex-col space-y-2">
{#if $config?.oauth?.providers?.google}
<button
class="flex items-center px-6 border-2 dark:border-gray-800 duration-300 dark:bg-gray-900 hover:bg-gray-100 dark:hover:bg-gray-800 w-full rounded-2xl dark:text-white text-sm py-3 transition"
on:click={() => {
window.location.href = `${WEBUI_BASE_URL}/oauth/google/login`;
}}
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" class="size-6 mr-3">
<path
fill="#EA4335"
d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"
/><path
fill="#4285F4"
d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"
/><path
fill="#FBBC05"
d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"
/><path
fill="#34A853"
d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"
/><path fill="none" d="M0 0h48v48H0z" />
</svg>
<span>{$i18n.t('Continue with {{provider}}', { provider: 'Google' })}</span>
</button>
{/if}
{#if $config?.oauth?.providers?.microsoft}
<button
class="flex items-center px-6 border-2 dark:border-gray-800 duration-300 dark:bg-gray-900 hover:bg-gray-100 dark:hover:bg-gray-800 w-full rounded-2xl dark:text-white text-sm py-3 transition"
on:click={() => {
window.location.href = `${WEBUI_BASE_URL}/oauth/microsoft/login`;
}}
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 21" class="size-6 mr-3">
<rect x="1" y="1" width="9" height="9" fill="#f25022" /><rect
x="1"
y="11"
width="9"
height="9"
fill="#00a4ef"
/><rect x="11" y="1" width="9" height="9" fill="#7fba00" /><rect
x="11"
y="11"
width="9"
height="9"
fill="#ffb900"
/>
</svg>
<span>{$i18n.t('Continue with {{provider}}', { provider: 'Microsoft' })}</span>
</button>
{/if}
{#if $config?.oauth?.providers?.oidc}
<button
class="flex items-center px-6 border-2 dark:border-gray-800 duration-300 dark:bg-gray-900 hover:bg-gray-100 dark:hover:bg-gray-800 w-full rounded-2xl dark:text-white text-sm py-3 transition"
on:click={() => {
window.location.href = `${WEBUI_BASE_URL}/oauth/oidc/login`;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-6 mr-3"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15.75 5.25a3 3 0 0 1 3 3m3 0a6 6 0 0 1-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1 1 21.75 8.25Z"
/>
</svg>
<span
>{$i18n.t('Continue with {{provider}}', {
provider: $config?.oauth?.providers?.oidc ?? 'SSO'
})}</span
>
</button>
{/if}
</div>
{/if}
</div> </div>
{/if} {/if}
</div> </div>