Unverified Commit 1eebb85f authored by Timothy Jaeryang Baek's avatar Timothy Jaeryang Baek Committed by GitHub
Browse files

Merge pull request #3323 from open-webui/dev

0.3.6
parents 9e4dd4b8 b224ba00
...@@ -167,6 +167,12 @@ for version in soup.find_all("h2"): ...@@ -167,6 +167,12 @@ for version in soup.find_all("h2"):
CHANGELOG = changelog_json CHANGELOG = changelog_json
####################################
# SAFE_MODE
####################################
SAFE_MODE = os.environ.get("SAFE_MODE", "false").lower() == "true"
#################################### ####################################
# WEBUI_BUILD_HASH # WEBUI_BUILD_HASH
#################################### ####################################
...@@ -299,6 +305,135 @@ JWT_EXPIRES_IN = PersistentConfig( ...@@ -299,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
#################################### ####################################
...@@ -377,6 +512,14 @@ TOOLS_DIR = os.getenv("TOOLS_DIR", f"{DATA_DIR}/tools") ...@@ -377,6 +512,14 @@ TOOLS_DIR = os.getenv("TOOLS_DIR", f"{DATA_DIR}/tools")
Path(TOOLS_DIR).mkdir(parents=True, exist_ok=True) Path(TOOLS_DIR).mkdir(parents=True, exist_ok=True)
####################################
# Functions DIR
####################################
FUNCTIONS_DIR = os.getenv("FUNCTIONS_DIR", f"{DATA_DIR}/functions")
Path(FUNCTIONS_DIR).mkdir(parents=True, exist_ok=True)
#################################### ####################################
# LITELLM_CONFIG # LITELLM_CONFIG
#################################### ####################################
...@@ -426,12 +569,15 @@ OLLAMA_API_BASE_URL = os.environ.get( ...@@ -426,12 +569,15 @@ OLLAMA_API_BASE_URL = os.environ.get(
) )
OLLAMA_BASE_URL = os.environ.get("OLLAMA_BASE_URL", "") OLLAMA_BASE_URL = os.environ.get("OLLAMA_BASE_URL", "")
AIOHTTP_CLIENT_TIMEOUT = os.environ.get("AIOHTTP_CLIENT_TIMEOUT", "300") AIOHTTP_CLIENT_TIMEOUT = os.environ.get("AIOHTTP_CLIENT_TIMEOUT", "")
if AIOHTTP_CLIENT_TIMEOUT == "": if AIOHTTP_CLIENT_TIMEOUT == "":
AIOHTTP_CLIENT_TIMEOUT = None AIOHTTP_CLIENT_TIMEOUT = None
else: else:
try:
AIOHTTP_CLIENT_TIMEOUT = int(AIOHTTP_CLIENT_TIMEOUT) AIOHTTP_CLIENT_TIMEOUT = int(AIOHTTP_CLIENT_TIMEOUT)
except:
AIOHTTP_CLIENT_TIMEOUT = 300
K8S_FLAG = os.environ.get("K8S_FLAG", "") K8S_FLAG = os.environ.get("K8S_FLAG", "")
...@@ -719,6 +865,16 @@ WEBUI_SECRET_KEY = os.environ.get( ...@@ -719,6 +865,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)
...@@ -903,6 +1059,18 @@ RAG_WEB_SEARCH_ENGINE = PersistentConfig( ...@@ -903,6 +1059,18 @@ RAG_WEB_SEARCH_ENGINE = PersistentConfig(
os.getenv("RAG_WEB_SEARCH_ENGINE", ""), os.getenv("RAG_WEB_SEARCH_ENGINE", ""),
) )
# You can provide a list of your own websites to filter after performing a web search.
# This ensures the highest level of safety and reliability of the information sources.
RAG_WEB_SEARCH_DOMAIN_FILTER_LIST = PersistentConfig(
"RAG_WEB_SEARCH_DOMAIN_FILTER_LIST",
"rag.rag.web.search.domain.filter_list",
[
# "wikipedia.com",
# "wikimedia.org",
# "wikidata.org",
],
)
SEARXNG_QUERY_URL = PersistentConfig( SEARXNG_QUERY_URL = PersistentConfig(
"SEARXNG_QUERY_URL", "SEARXNG_QUERY_URL",
"rag.web.search.searxng_query_url", "rag.web.search.searxng_query_url",
...@@ -1001,6 +1169,11 @@ AUTOMATIC1111_BASE_URL = PersistentConfig( ...@@ -1001,6 +1169,11 @@ AUTOMATIC1111_BASE_URL = PersistentConfig(
"image_generation.automatic1111.base_url", "image_generation.automatic1111.base_url",
os.getenv("AUTOMATIC1111_BASE_URL", ""), os.getenv("AUTOMATIC1111_BASE_URL", ""),
) )
AUTOMATIC1111_API_AUTH = PersistentConfig(
"AUTOMATIC1111_API_AUTH",
"image_generation.automatic1111.api_auth",
os.getenv("AUTOMATIC1111_API_AUTH", ""),
)
COMFYUI_BASE_URL = PersistentConfig( COMFYUI_BASE_URL = PersistentConfig(
"COMFYUI_BASE_URL", "COMFYUI_BASE_URL",
......
This diff is collapsed.
...@@ -17,11 +17,17 @@ peewee-migrate==1.12.2 ...@@ -17,11 +17,17 @@ peewee-migrate==1.12.2
psycopg2-binary==2.9.9 psycopg2-binary==2.9.9
PyMySQL==1.1.1 PyMySQL==1.1.1
bcrypt==4.1.3 bcrypt==4.1.3
SQLAlchemy
pymongo
redis
boto3==1.34.110 boto3==1.34.110
argon2-cffi==23.1.0 argon2-cffi==23.1.0
APScheduler==3.10.4 APScheduler==3.10.4
# AI libraries
openai
anthropic
google-generativeai==0.5.4 google-generativeai==0.5.4
langchain==0.2.0 langchain==0.2.0
...@@ -52,6 +58,7 @@ rank-bm25==0.2.2 ...@@ -52,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
......
...@@ -3,7 +3,9 @@ import hashlib ...@@ -3,7 +3,9 @@ import hashlib
import json import json
import re import re
from datetime import timedelta from datetime import timedelta
from typing import Optional, List from typing import Optional, List, Tuple
import uuid
import time
def get_last_user_message(messages: List[dict]) -> str: def get_last_user_message(messages: List[dict]) -> str:
...@@ -28,6 +30,21 @@ def get_last_assistant_message(messages: List[dict]) -> str: ...@@ -28,6 +30,21 @@ def get_last_assistant_message(messages: List[dict]) -> str:
return None return None
def get_system_message(messages: List[dict]) -> dict:
for message in messages:
if message["role"] == "system":
return message
return None
def remove_system_message(messages: List[dict]) -> List[dict]:
return [message for message in messages if message["role"] != "system"]
def pop_system_message(messages: List[dict]) -> Tuple[dict, List[dict]]:
return get_system_message(messages), remove_system_message(messages)
def add_or_update_system_message(content: str, messages: List[dict]): def add_or_update_system_message(content: str, messages: List[dict]):
""" """
Adds a new system message at the beginning of the messages list Adds a new system message at the beginning of the messages list
...@@ -47,6 +64,23 @@ def add_or_update_system_message(content: str, messages: List[dict]): ...@@ -47,6 +64,23 @@ def add_or_update_system_message(content: str, messages: List[dict]):
return messages return messages
def stream_message_template(model: str, message: str):
return {
"id": f"{model}-{str(uuid.uuid4())}",
"object": "chat.completion.chunk",
"created": int(time.time()),
"model": model,
"choices": [
{
"index": 0,
"delta": {"content": message},
"logprobs": None,
"finish_reason": None,
}
],
}
def get_gravatar_url(email): def get_gravatar_url(email):
# Trim leading and trailing whitespace from # Trim leading and trailing whitespace from
# an email address and force all characters # an email address and force all characters
......
...@@ -24,10 +24,16 @@ def prompt_template( ...@@ -24,10 +24,16 @@ def prompt_template(
if user_name: if user_name:
# Replace {{USER_NAME}} in the template with the user's name # Replace {{USER_NAME}} in the template with the user's name
template = template.replace("{{USER_NAME}}", user_name) template = template.replace("{{USER_NAME}}", user_name)
else:
# Replace {{USER_NAME}} in the template with "Unknown"
template = template.replace("{{USER_NAME}}", "Unknown")
if user_location: if user_location:
# Replace {{USER_LOCATION}} in the template with the current location # Replace {{USER_LOCATION}} in the template with the current location
template = template.replace("{{USER_LOCATION}}", user_location) template = template.replace("{{USER_LOCATION}}", user_location)
else:
# Replace {{USER_LOCATION}} in the template with "Unknown"
template = template.replace("{{USER_LOCATION}}", "Unknown")
return template return template
......
...@@ -20,7 +20,9 @@ def get_tools_specs(tools) -> List[dict]: ...@@ -20,7 +20,9 @@ def get_tools_specs(tools) -> List[dict]:
function_list = [ function_list = [
{"name": func, "function": getattr(tools, func)} {"name": func, "function": getattr(tools, func)}
for func in dir(tools) for func in dir(tools)
if callable(getattr(tools, func)) and not func.startswith("__") if callable(getattr(tools, func))
and not func.startswith("__")
and not inspect.isclass(getattr(tools, func))
] ]
specs = [] specs = []
...@@ -65,6 +67,7 @@ def get_tools_specs(tools) -> List[dict]: ...@@ -65,6 +67,7 @@ def get_tools_specs(tools) -> List[dict]:
function function
).parameters.items() ).parameters.items()
if param.default is param.empty if param.default is param.empty
and not (name.startswith("__") and name.endswith("__"))
], ],
}, },
} }
......
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from fastapi import HTTPException, status, Depends from fastapi import HTTPException, status, Depends, Request
from apps.webui.models.users import Users from apps.webui.models.users import Users
...@@ -24,7 +24,7 @@ ALGORITHM = "HS256" ...@@ -24,7 +24,7 @@ ALGORITHM = "HS256"
# Auth Utils # Auth Utils
############## ##############
bearer_security = HTTPBearer() bearer_security = HTTPBearer(auto_error=False)
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
...@@ -75,13 +75,26 @@ def get_http_authorization_cred(auth_header: str): ...@@ -75,13 +75,26 @@ def get_http_authorization_cred(auth_header: str):
def get_current_user( def get_current_user(
request: Request,
auth_token: HTTPAuthorizationCredentials = Depends(bearer_security), auth_token: HTTPAuthorizationCredentials = Depends(bearer_security),
): ):
token = None
if auth_token is not None:
token = auth_token.credentials
if token is None and "token" in request.cookies:
token = request.cookies.get("token")
if token is None:
raise HTTPException(status_code=403, detail="Not authenticated")
# auth by api key # auth by api key
if auth_token.credentials.startswith("sk-"): if token.startswith("sk-"):
return get_current_user_by_api_key(auth_token.credentials) return get_current_user_by_api_key(token)
# auth by jwt token # auth by jwt token
data = decode_token(auth_token.credentials) data = decode_token(token)
if data != None and "id" in data: if data != None and "id" in data:
user = Users.get_user_by_id(data["id"]) user = Users.get_user_by_id(data["id"])
if user is None: if user is None:
......
{ {
"name": "open-webui", "name": "open-webui",
"version": "0.3.5", "version": "0.3.6",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "open-webui", "name": "open-webui",
"version": "0.3.5", "version": "0.3.6",
"dependencies": { "dependencies": {
"@codemirror/lang-javascript": "^6.2.2", "@codemirror/lang-javascript": "^6.2.2",
"@codemirror/lang-python": "^6.1.6", "@codemirror/lang-python": "^6.1.6",
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
"async": "^3.2.5", "async": "^3.2.5",
"bits-ui": "^0.19.7", "bits-ui": "^0.19.7",
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"crc-32": "^1.2.2",
"dayjs": "^1.11.10", "dayjs": "^1.11.10",
"eventsource-parser": "^1.1.2", "eventsource-parser": "^1.1.2",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
...@@ -28,11 +29,12 @@ ...@@ -28,11 +29,12 @@
"katex": "^0.16.9", "katex": "^0.16.9",
"marked": "^9.1.0", "marked": "^9.1.0",
"mermaid": "^10.9.1", "mermaid": "^10.9.1",
"pyodide": "^0.26.0-alpha.4", "pyodide": "^0.26.1",
"socket.io-client": "^4.7.5", "socket.io-client": "^4.2.0",
"sortablejs": "^1.15.2", "sortablejs": "^1.15.2",
"svelte-sonner": "^0.3.19", "svelte-sonner": "^0.3.19",
"tippy.js": "^6.3.7", "tippy.js": "^6.3.7",
"turndown": "^7.2.0",
"uuid": "^9.0.1" "uuid": "^9.0.1"
}, },
"devDependencies": { "devDependencies": {
...@@ -999,6 +1001,11 @@ ...@@ -999,6 +1001,11 @@
"svelte": ">=3 <5" "svelte": ">=3 <5"
} }
}, },
"node_modules/@mixmark-io/domino": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@mixmark-io/domino/-/domino-2.2.0.tgz",
"integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="
},
"node_modules/@nodelib/fs.scandir": { "node_modules/@nodelib/fs.scandir": {
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
...@@ -2266,11 +2273,6 @@ ...@@ -2266,11 +2273,6 @@
"dev": true, "dev": true,
"optional": true "optional": true
}, },
"node_modules/base-64": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz",
"integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg=="
},
"node_modules/base64-js": { "node_modules/base64-js": {
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
...@@ -3063,6 +3065,17 @@ ...@@ -3063,6 +3065,17 @@
"layout-base": "^1.0.0" "layout-base": "^1.0.0"
} }
}, },
"node_modules/crc-32": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
"bin": {
"crc32": "bin/crc32.njs"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/crelt": { "node_modules/crelt": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
...@@ -3984,37 +3997,17 @@ ...@@ -3984,37 +3997,17 @@
} }
}, },
"node_modules/engine.io-client": { "node_modules/engine.io-client": {
"version": "6.5.3", "version": "6.5.4",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz", "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.4.tgz",
"integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==", "integrity": "sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==",
"dependencies": { "dependencies": {
"@socket.io/component-emitter": "~3.1.0", "@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1", "debug": "~4.3.1",
"engine.io-parser": "~5.2.1", "engine.io-parser": "~5.2.1",
"ws": "~8.11.0", "ws": "~8.17.1",
"xmlhttprequest-ssl": "~2.0.0" "xmlhttprequest-ssl": "~2.0.0"
} }
}, },
"node_modules/engine.io-client/node_modules/ws": {
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/engine.io-parser": { "node_modules/engine.io-parser": {
"version": "5.2.2", "version": "5.2.2",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz",
...@@ -7551,11 +7544,10 @@ ...@@ -7551,11 +7544,10 @@
} }
}, },
"node_modules/pyodide": { "node_modules/pyodide": {
"version": "0.26.0-alpha.4", "version": "0.26.1",
"resolved": "https://registry.npmjs.org/pyodide/-/pyodide-0.26.0-alpha.4.tgz", "resolved": "https://registry.npmjs.org/pyodide/-/pyodide-0.26.1.tgz",
"integrity": "sha512-Ixuczq99DwhQlE+Bt0RaS6Ln9MHSZOkbU6iN8azwaeorjHtr7ukaxh+FeTxViFrp2y+ITyKgmcobY+JnBPcULw==", "integrity": "sha512-P+Gm88nwZqY7uBgjbQH8CqqU6Ei/rDn7pS1t02sNZsbyLJMyE2OVXjgNuqVT3KqYWnyGREUN0DbBUCJqk8R0ew==",
"dependencies": { "dependencies": {
"base-64": "^1.0.0",
"ws": "^8.5.0" "ws": "^8.5.0"
}, },
"engines": { "engines": {
...@@ -9065,6 +9057,14 @@ ...@@ -9065,6 +9057,14 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/turndown": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/turndown/-/turndown-7.2.0.tgz",
"integrity": "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A==",
"dependencies": {
"@mixmark-io/domino": "^2.2.0"
}
},
"node_modules/tweetnacl": { "node_modules/tweetnacl": {
"version": "0.14.5", "version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
...@@ -10382,9 +10382,9 @@ ...@@ -10382,9 +10382,9 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
}, },
"node_modules/ws": { "node_modules/ws": {
"version": "8.17.0", "version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"engines": { "engines": {
"node": ">=10.0.0" "node": ">=10.0.0"
}, },
......
{ {
"name": "open-webui", "name": "open-webui",
"version": "0.3.5", "version": "0.3.6",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "npm run pyodide:fetch && vite dev --host", "dev": "npm run pyodide:fetch && vite dev --host",
...@@ -56,6 +56,7 @@ ...@@ -56,6 +56,7 @@
"async": "^3.2.5", "async": "^3.2.5",
"bits-ui": "^0.19.7", "bits-ui": "^0.19.7",
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"crc-32": "^1.2.2",
"dayjs": "^1.11.10", "dayjs": "^1.11.10",
"eventsource-parser": "^1.1.2", "eventsource-parser": "^1.1.2",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
...@@ -68,11 +69,12 @@ ...@@ -68,11 +69,12 @@
"katex": "^0.16.9", "katex": "^0.16.9",
"marked": "^9.1.0", "marked": "^9.1.0",
"mermaid": "^10.9.1", "mermaid": "^10.9.1",
"pyodide": "^0.26.0-alpha.4", "pyodide": "^0.26.1",
"socket.io-client": "^4.7.5", "socket.io-client": "^4.2.0",
"sortablejs": "^1.15.2", "sortablejs": "^1.15.2",
"svelte-sonner": "^0.3.19", "svelte-sonner": "^0.3.19",
"tippy.js": "^6.3.7", "tippy.js": "^6.3.7",
"turndown": "^7.2.0",
"uuid": "^9.0.1" "uuid": "^9.0.1"
} }
} }
...@@ -59,6 +59,7 @@ dependencies = [ ...@@ -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",
......
...@@ -31,6 +31,8 @@ asgiref==3.8.1 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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
...@@ -31,6 +31,8 @@ asgiref==3.8.1 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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
const packages = [ const packages = [
'micropip',
'packaging',
'requests', 'requests',
'beautifulsoup4', 'beautifulsoup4',
'numpy', 'numpy',
...@@ -11,20 +13,64 @@ const packages = [ ...@@ -11,20 +13,64 @@ const packages = [
]; ];
import { loadPyodide } from 'pyodide'; import { loadPyodide } from 'pyodide';
import { writeFile, copyFile, readdir } from 'fs/promises'; import { writeFile, readFile, copyFile, readdir, rmdir } from 'fs/promises';
async function downloadPackages() { async function downloadPackages() {
console.log('Setting up pyodide + micropip'); console.log('Setting up pyodide + micropip');
const pyodide = await loadPyodide({
let pyodide;
try {
pyodide = await loadPyodide({
packageCacheDir: 'static/pyodide' packageCacheDir: 'static/pyodide'
}); });
} catch (err) {
console.error('Failed to load Pyodide:', err);
return;
}
const packageJson = JSON.parse(await readFile('package.json'));
const pyodideVersion = packageJson.dependencies.pyodide.replace('^', '');
try {
const pyodidePackageJson = JSON.parse(await readFile('static/pyodide/package.json'));
const pyodidePackageVersion = pyodidePackageJson.version.replace('^', '');
if (pyodideVersion !== pyodidePackageVersion) {
console.log('Pyodide version mismatch, removing static/pyodide directory');
await rmdir('static/pyodide', { recursive: true });
}
} catch (e) {
console.log('Pyodide package not found, proceeding with download.');
}
try {
console.log('Loading micropip package');
await pyodide.loadPackage('micropip'); await pyodide.loadPackage('micropip');
const micropip = pyodide.pyimport('micropip'); const micropip = pyodide.pyimport('micropip');
console.log('Downloading Pyodide packages:', packages); console.log('Downloading Pyodide packages:', packages);
await micropip.install(packages);
try {
for (const pkg of packages) {
console.log(`Installing package: ${pkg}`);
await micropip.install(pkg);
}
} catch (err) {
console.error('Package installation failed:', err);
return;
}
console.log('Pyodide packages downloaded, freezing into lock file'); console.log('Pyodide packages downloaded, freezing into lock file');
try {
const lockFile = await micropip.freeze(); const lockFile = await micropip.freeze();
await writeFile('static/pyodide/pyodide-lock.json', lockFile); await writeFile('static/pyodide/pyodide-lock.json', lockFile);
} catch (err) {
console.error('Failed to write lock file:', err);
}
} catch (err) {
console.error('Failed to load or install micropip:', err);
}
} }
async function copyPyodide() { async function copyPyodide() {
......
...@@ -32,6 +32,10 @@ math { ...@@ -32,6 +32,10 @@ math {
@apply underline; @apply underline;
} }
iframe {
@apply rounded-lg;
}
ol > li { ol > li {
counter-increment: list-number; counter-increment: list-number;
display: block; display: block;
......
...@@ -13,6 +13,12 @@ ...@@ -13,6 +13,12 @@
href="/opensearch.xml" href="/opensearch.xml"
/> />
<script>
function resizeIframe(obj) {
obj.style.height = obj.contentWindow.document.documentElement.scrollHeight + 'px';
}
</script>
<script> <script>
// On page load or when changing themes, best to add inline in `head` to avoid FOUC // On page load or when changing themes, best to add inline in `head` to avoid FOUC
(() => { (() => {
......
...@@ -90,7 +90,8 @@ export const getSessionUser = async (token: string) => { ...@@ -90,7 +90,8 @@ export const getSessionUser = async (token: string) => {
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Authorization: `Bearer ${token}` Authorization: `Bearer ${token}`
} },
credentials: 'include'
}) })
.then(async (res) => { .then(async (res) => {
if (!res.ok) throw await res.json(); if (!res.ok) throw await res.json();
...@@ -117,6 +118,7 @@ export const userSignIn = async (email: string, password: string) => { ...@@ -117,6 +118,7 @@ export const userSignIn = async (email: string, password: string) => {
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
credentials: 'include',
body: JSON.stringify({ body: JSON.stringify({
email: email, email: email,
password: password password: password
...@@ -153,6 +155,7 @@ export const userSignUp = async ( ...@@ -153,6 +155,7 @@ export const userSignUp = async (
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
credentials: 'include',
body: JSON.stringify({ body: JSON.stringify({
name: name, name: name,
email: email, email: email,
......
import { WEBUI_API_BASE_URL } from '$lib/constants';
export const uploadFile = async (token: string, file: File) => {
const data = new FormData();
data.append('file', file);
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/files/`, {
method: 'POST',
headers: {
Accept: 'application/json',
authorization: `Bearer ${token}`
},
body: data
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((err) => {
error = err.detail;
console.log(err);
return null;
});
if (error) {
throw error;
}
return res;
};
export const getFiles = async (token: string = '') => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/files/`, {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `Bearer ${token}`
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.then((json) => {
return json;
})
.catch((err) => {
error = err.detail;
console.log(err);
return null;
});
if (error) {
throw error;
}
return res;
};
export const getFileById = async (token: string, id: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/files/${id}`, {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `Bearer ${token}`
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.then((json) => {
return json;
})
.catch((err) => {
error = err.detail;
console.log(err);
return null;
});
if (error) {
throw error;
}
return res;
};
export const getFileContentById = async (id: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/files/${id}/content`, {
method: 'GET',
headers: {
Accept: 'application/json'
},
credentials: 'include'
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return await res.blob();
})
.catch((err) => {
error = err.detail;
console.log(err);
return null;
});
if (error) {
throw error;
}
return res;
};
export const deleteFileById = async (token: string, id: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/files/${id}`, {
method: 'DELETE',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `Bearer ${token}`
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.then((json) => {
return json;
})
.catch((err) => {
error = err.detail;
console.log(err);
return null;
});
if (error) {
throw error;
}
return res;
};
export const deleteAllFiles = async (token: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/files/all`, {
method: 'DELETE',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `Bearer ${token}`
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.then((json) => {
return json;
})
.catch((err) => {
error = err.detail;
console.log(err);
return null;
});
if (error) {
throw error;
}
return res;
};
This diff is collapsed.
...@@ -164,6 +164,37 @@ export const updateQuerySettings = async (token: string, settings: QuerySettings ...@@ -164,6 +164,37 @@ export const updateQuerySettings = async (token: string, settings: QuerySettings
return res; return res;
}; };
export const processDocToVectorDB = async (token: string, file_id: string) => {
let error = null;
const res = await fetch(`${RAG_API_BASE_URL}/process/doc`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `Bearer ${token}`
},
body: JSON.stringify({
file_id: file_id
})
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((err) => {
error = err.detail;
console.log(err);
return null;
});
if (error) {
throw error;
}
return res;
};
export const uploadDocToVectorDB = async (token: string, collection_name: string, file: File) => { export const uploadDocToVectorDB = async (token: string, collection_name: string, file: File) => {
const data = new FormData(); const data = new FormData();
data.append('file', file); data.append('file', file);
......
...@@ -191,3 +191,201 @@ export const deleteToolById = async (token: string, id: string) => { ...@@ -191,3 +191,201 @@ export const deleteToolById = async (token: string, id: string) => {
return res; return res;
}; };
export const getToolValvesById = async (token: string, id: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/tools/id/${id}/valves`, {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `Bearer ${token}`
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.then((json) => {
return json;
})
.catch((err) => {
error = err.detail;
console.log(err);
return null;
});
if (error) {
throw error;
}
return res;
};
export const getToolValvesSpecById = async (token: string, id: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/tools/id/${id}/valves/spec`, {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `Bearer ${token}`
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.then((json) => {
return json;
})
.catch((err) => {
error = err.detail;
console.log(err);
return null;
});
if (error) {
throw error;
}
return res;
};
export const updateToolValvesById = async (token: string, id: string, valves: object) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/tools/id/${id}/valves/update`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `Bearer ${token}`
},
body: JSON.stringify({
...valves
})
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.then((json) => {
return json;
})
.catch((err) => {
error = err.detail;
console.log(err);
return null;
});
if (error) {
throw error;
}
return res;
};
export const getUserValvesById = async (token: string, id: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/tools/id/${id}/valves/user`, {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `Bearer ${token}`
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.then((json) => {
return json;
})
.catch((err) => {
error = err.detail;
console.log(err);
return null;
});
if (error) {
throw error;
}
return res;
};
export const getUserValvesSpecById = async (token: string, id: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/tools/id/${id}/valves/user/spec`, {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `Bearer ${token}`
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.then((json) => {
return json;
})
.catch((err) => {
error = err.detail;
console.log(err);
return null;
});
if (error) {
throw error;
}
return res;
};
export const updateUserValvesById = async (token: string, id: string, valves: object) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/tools/id/${id}/valves/user/update`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `Bearer ${token}`
},
body: JSON.stringify({
...valves
})
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.then((json) => {
return json;
})
.catch((err) => {
error = err.detail;
console.log(err);
return null;
});
if (error) {
throw error;
}
return res;
};
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment