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

Merge pull request #2376 from open-webui/dev

0.1.125
parents 90503be2 bcc2bab6
...@@ -5,6 +5,7 @@ import chromadb ...@@ -5,6 +5,7 @@ import chromadb
from chromadb import Settings from chromadb import Settings
from base64 import b64encode from base64 import b64encode
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from typing import TypeVar, Generic, Union
from pathlib import Path from pathlib import Path
import json import json
...@@ -17,7 +18,6 @@ import shutil ...@@ -17,7 +18,6 @@ import shutil
from secrets import token_bytes from secrets import token_bytes
from constants import ERROR_MESSAGES from constants import ERROR_MESSAGES
#################################### ####################################
# Load .env file # Load .env file
#################################### ####################################
...@@ -71,7 +71,6 @@ for source in log_sources: ...@@ -71,7 +71,6 @@ for source in log_sources:
log.setLevel(SRC_LOG_LEVELS["CONFIG"]) log.setLevel(SRC_LOG_LEVELS["CONFIG"])
WEBUI_NAME = os.environ.get("WEBUI_NAME", "Open WebUI") WEBUI_NAME = os.environ.get("WEBUI_NAME", "Open WebUI")
if WEBUI_NAME != "Open WebUI": if WEBUI_NAME != "Open WebUI":
WEBUI_NAME += " (Open WebUI)" WEBUI_NAME += " (Open WebUI)"
...@@ -161,16 +160,6 @@ CHANGELOG = changelog_json ...@@ -161,16 +160,6 @@ CHANGELOG = changelog_json
WEBUI_VERSION = os.environ.get("WEBUI_VERSION", "v1.0.0-alpha.100") WEBUI_VERSION = os.environ.get("WEBUI_VERSION", "v1.0.0-alpha.100")
####################################
# WEBUI_AUTH (Required for security)
####################################
WEBUI_AUTH = os.environ.get("WEBUI_AUTH", "True").lower() == "true"
WEBUI_AUTH_TRUSTED_EMAIL_HEADER = os.environ.get(
"WEBUI_AUTH_TRUSTED_EMAIL_HEADER", None
)
#################################### ####################################
# DATA/FRONTEND BUILD DIR # DATA/FRONTEND BUILD DIR
#################################### ####################################
...@@ -184,6 +173,108 @@ try: ...@@ -184,6 +173,108 @@ try:
except: except:
CONFIG_DATA = {} CONFIG_DATA = {}
####################################
# Config helpers
####################################
def save_config():
try:
with open(f"{DATA_DIR}/config.json", "w") as f:
json.dump(CONFIG_DATA, f, indent="\t")
except Exception as e:
log.exception(e)
def get_config_value(config_path: str):
path_parts = config_path.split(".")
cur_config = CONFIG_DATA
for key in path_parts:
if key in cur_config:
cur_config = cur_config[key]
else:
return None
return cur_config
T = TypeVar("T")
class PersistentConfig(Generic[T]):
def __init__(self, env_name: str, config_path: str, env_value: T):
self.env_name = env_name
self.config_path = config_path
self.env_value = env_value
self.config_value = get_config_value(config_path)
if self.config_value is not None:
log.info(f"'{env_name}' loaded from config.json")
self.value = self.config_value
else:
self.value = env_value
def __str__(self):
return str(self.value)
@property
def __dict__(self):
raise TypeError(
"PersistentConfig object cannot be converted to dict, use config_get or .value instead."
)
def __getattribute__(self, item):
if item == "__dict__":
raise TypeError(
"PersistentConfig object cannot be converted to dict, use config_get or .value instead."
)
return super().__getattribute__(item)
def save(self):
# Don't save if the value is the same as the env value and the config value
if self.env_value == self.value:
if self.config_value == self.value:
return
log.info(f"Saving '{self.env_name}' to config.json")
path_parts = self.config_path.split(".")
config = CONFIG_DATA
for key in path_parts[:-1]:
if key not in config:
config[key] = {}
config = config[key]
config[path_parts[-1]] = self.value
save_config()
self.config_value = self.value
class AppConfig:
_state: dict[str, PersistentConfig]
def __init__(self):
super().__setattr__("_state", {})
def __setattr__(self, key, value):
if isinstance(value, PersistentConfig):
self._state[key] = value
else:
self._state[key].value = value
self._state[key].save()
def __getattr__(self, key):
return self._state[key].value
####################################
# WEBUI_AUTH (Required for security)
####################################
WEBUI_AUTH = os.environ.get("WEBUI_AUTH", "True").lower() == "true"
WEBUI_AUTH_TRUSTED_EMAIL_HEADER = os.environ.get(
"WEBUI_AUTH_TRUSTED_EMAIL_HEADER", None
)
JWT_EXPIRES_IN = PersistentConfig(
"JWT_EXPIRES_IN", "auth.jwt_expiry", os.environ.get("JWT_EXPIRES_IN", "-1")
)
#################################### ####################################
# Static DIR # Static DIR
#################################### ####################################
...@@ -318,12 +409,22 @@ OLLAMA_BASE_URLS = os.environ.get("OLLAMA_BASE_URLS", "") ...@@ -318,12 +409,22 @@ OLLAMA_BASE_URLS = os.environ.get("OLLAMA_BASE_URLS", "")
OLLAMA_BASE_URLS = OLLAMA_BASE_URLS if OLLAMA_BASE_URLS != "" else OLLAMA_BASE_URL OLLAMA_BASE_URLS = OLLAMA_BASE_URLS if OLLAMA_BASE_URLS != "" else OLLAMA_BASE_URL
OLLAMA_BASE_URLS = [url.strip() for url in OLLAMA_BASE_URLS.split(";")] OLLAMA_BASE_URLS = [url.strip() for url in OLLAMA_BASE_URLS.split(";")]
OLLAMA_BASE_URLS = PersistentConfig(
"OLLAMA_BASE_URLS", "ollama.base_urls", OLLAMA_BASE_URLS
)
#################################### ####################################
# OPENAI_API # OPENAI_API
#################################### ####################################
ENABLE_OPENAI_API = PersistentConfig(
"ENABLE_OPENAI_API",
"openai.enable",
os.environ.get("ENABLE_OPENAI_API", "True").lower() == "true",
)
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "") OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "")
OPENAI_API_BASE_URL = os.environ.get("OPENAI_API_BASE_URL", "") OPENAI_API_BASE_URL = os.environ.get("OPENAI_API_BASE_URL", "")
...@@ -335,7 +436,9 @@ OPENAI_API_KEYS = os.environ.get("OPENAI_API_KEYS", "") ...@@ -335,7 +436,9 @@ OPENAI_API_KEYS = os.environ.get("OPENAI_API_KEYS", "")
OPENAI_API_KEYS = OPENAI_API_KEYS if OPENAI_API_KEYS != "" else OPENAI_API_KEY OPENAI_API_KEYS = OPENAI_API_KEYS if OPENAI_API_KEYS != "" else OPENAI_API_KEY
OPENAI_API_KEYS = [url.strip() for url in OPENAI_API_KEYS.split(";")] OPENAI_API_KEYS = [url.strip() for url in OPENAI_API_KEYS.split(";")]
OPENAI_API_KEYS = PersistentConfig(
"OPENAI_API_KEYS", "openai.api_keys", OPENAI_API_KEYS
)
OPENAI_API_BASE_URLS = os.environ.get("OPENAI_API_BASE_URLS", "") OPENAI_API_BASE_URLS = os.environ.get("OPENAI_API_BASE_URLS", "")
OPENAI_API_BASE_URLS = ( OPENAI_API_BASE_URLS = (
...@@ -346,37 +449,42 @@ OPENAI_API_BASE_URLS = [ ...@@ -346,37 +449,42 @@ OPENAI_API_BASE_URLS = [
url.strip() if url != "" else "https://api.openai.com/v1" url.strip() if url != "" else "https://api.openai.com/v1"
for url in OPENAI_API_BASE_URLS.split(";") for url in OPENAI_API_BASE_URLS.split(";")
] ]
OPENAI_API_BASE_URLS = PersistentConfig(
"OPENAI_API_BASE_URLS", "openai.api_base_urls", OPENAI_API_BASE_URLS
)
OPENAI_API_KEY = "" OPENAI_API_KEY = ""
try: try:
OPENAI_API_KEY = OPENAI_API_KEYS[ OPENAI_API_KEY = OPENAI_API_KEYS.value[
OPENAI_API_BASE_URLS.index("https://api.openai.com/v1") OPENAI_API_BASE_URLS.value.index("https://api.openai.com/v1")
] ]
except: except:
pass pass
OPENAI_API_BASE_URL = "https://api.openai.com/v1" OPENAI_API_BASE_URL = "https://api.openai.com/v1"
#################################### ####################################
# WEBUI # WEBUI
#################################### ####################################
ENABLE_SIGNUP = ( ENABLE_SIGNUP = PersistentConfig(
"ENABLE_SIGNUP",
"ui.enable_signup",
(
False False
if WEBUI_AUTH == False if not WEBUI_AUTH
else os.environ.get("ENABLE_SIGNUP", "True").lower() == "true" else os.environ.get("ENABLE_SIGNUP", "True").lower() == "true"
),
)
DEFAULT_MODELS = PersistentConfig(
"DEFAULT_MODELS", "ui.default_models", os.environ.get("DEFAULT_MODELS", None)
) )
DEFAULT_MODELS = os.environ.get("DEFAULT_MODELS", None)
DEFAULT_PROMPT_SUGGESTIONS = ( DEFAULT_PROMPT_SUGGESTIONS = PersistentConfig(
CONFIG_DATA["ui"]["prompt_suggestions"] "DEFAULT_PROMPT_SUGGESTIONS",
if "ui" in CONFIG_DATA "ui.prompt_suggestions",
and "prompt_suggestions" in CONFIG_DATA["ui"] [
and type(CONFIG_DATA["ui"]["prompt_suggestions"]) is list
else [
{ {
"title": ["Help me study", "vocabulary for a college entrance exam"], "title": ["Help me study", "vocabulary for a college entrance exam"],
"content": "Help me study vocabulary: write a sentence for me to fill in the blank, and I'll try to pick the correct option.", "content": "Help me study vocabulary: write a sentence for me to fill in the blank, and I'll try to pick the correct option.",
...@@ -404,23 +512,40 @@ DEFAULT_PROMPT_SUGGESTIONS = ( ...@@ -404,23 +512,40 @@ DEFAULT_PROMPT_SUGGESTIONS = (
"title": ["Overcome procrastination", "give me tips"], "title": ["Overcome procrastination", "give me tips"],
"content": "Could you start by asking me about instances when I procrastinate the most and then give me some suggestions to overcome it?", "content": "Could you start by asking me about instances when I procrastinate the most and then give me some suggestions to overcome it?",
}, },
] ],
) )
DEFAULT_USER_ROLE = PersistentConfig(
DEFAULT_USER_ROLE = os.getenv("DEFAULT_USER_ROLE", "pending") "DEFAULT_USER_ROLE",
"ui.default_user_role",
os.getenv("DEFAULT_USER_ROLE", "pending"),
)
USER_PERMISSIONS_CHAT_DELETION = ( USER_PERMISSIONS_CHAT_DELETION = (
os.environ.get("USER_PERMISSIONS_CHAT_DELETION", "True").lower() == "true" os.environ.get("USER_PERMISSIONS_CHAT_DELETION", "True").lower() == "true"
) )
USER_PERMISSIONS = {"chat": {"deletion": USER_PERMISSIONS_CHAT_DELETION}} USER_PERMISSIONS = PersistentConfig(
"USER_PERMISSIONS",
"ui.user_permissions",
{"chat": {"deletion": USER_PERMISSIONS_CHAT_DELETION}},
)
ENABLE_MODEL_FILTER = os.environ.get("ENABLE_MODEL_FILTER", "False").lower() == "true" ENABLE_MODEL_FILTER = PersistentConfig(
"ENABLE_MODEL_FILTER",
"model_filter.enable",
os.environ.get("ENABLE_MODEL_FILTER", "False").lower() == "true",
)
MODEL_FILTER_LIST = os.environ.get("MODEL_FILTER_LIST", "") MODEL_FILTER_LIST = os.environ.get("MODEL_FILTER_LIST", "")
MODEL_FILTER_LIST = [model.strip() for model in MODEL_FILTER_LIST.split(";")] MODEL_FILTER_LIST = PersistentConfig(
"MODEL_FILTER_LIST",
"model_filter.list",
[model.strip() for model in MODEL_FILTER_LIST.split(";")],
)
WEBHOOK_URL = os.environ.get("WEBHOOK_URL", "") WEBHOOK_URL = PersistentConfig(
"WEBHOOK_URL", "webhook_url", os.environ.get("WEBHOOK_URL", "")
)
ENABLE_ADMIN_EXPORT = os.environ.get("ENABLE_ADMIN_EXPORT", "True").lower() == "true" ENABLE_ADMIN_EXPORT = os.environ.get("ENABLE_ADMIN_EXPORT", "True").lower() == "true"
...@@ -458,26 +583,45 @@ else: ...@@ -458,26 +583,45 @@ else:
CHROMA_HTTP_SSL = os.environ.get("CHROMA_HTTP_SSL", "false").lower() == "true" CHROMA_HTTP_SSL = os.environ.get("CHROMA_HTTP_SSL", "false").lower() == "true"
# this uses the model defined in the Dockerfile ENV variable. If you dont use docker or docker based deployments such as k8s, the default embedding model will be used (sentence-transformers/all-MiniLM-L6-v2) # this uses the model defined in the Dockerfile ENV variable. If you dont use docker or docker based deployments such as k8s, the default embedding model will be used (sentence-transformers/all-MiniLM-L6-v2)
RAG_TOP_K = int(os.environ.get("RAG_TOP_K", "5")) RAG_TOP_K = PersistentConfig(
RAG_RELEVANCE_THRESHOLD = float(os.environ.get("RAG_RELEVANCE_THRESHOLD", "0.0")) "RAG_TOP_K", "rag.top_k", int(os.environ.get("RAG_TOP_K", "5"))
)
ENABLE_RAG_HYBRID_SEARCH = ( RAG_RELEVANCE_THRESHOLD = PersistentConfig(
os.environ.get("ENABLE_RAG_HYBRID_SEARCH", "").lower() == "true" "RAG_RELEVANCE_THRESHOLD",
"rag.relevance_threshold",
float(os.environ.get("RAG_RELEVANCE_THRESHOLD", "0.0")),
) )
ENABLE_RAG_HYBRID_SEARCH = PersistentConfig(
"ENABLE_RAG_HYBRID_SEARCH",
"rag.enable_hybrid_search",
os.environ.get("ENABLE_RAG_HYBRID_SEARCH", "").lower() == "true",
)
ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION = ( ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION = PersistentConfig(
os.environ.get("ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION", "True").lower() == "true" "ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION",
"rag.enable_web_loader_ssl_verification",
os.environ.get("ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION", "True").lower() == "true",
) )
RAG_EMBEDDING_ENGINE = os.environ.get("RAG_EMBEDDING_ENGINE", "") RAG_EMBEDDING_ENGINE = PersistentConfig(
"RAG_EMBEDDING_ENGINE",
"rag.embedding_engine",
os.environ.get("RAG_EMBEDDING_ENGINE", ""),
)
PDF_EXTRACT_IMAGES = os.environ.get("PDF_EXTRACT_IMAGES", "False").lower() == "true" PDF_EXTRACT_IMAGES = PersistentConfig(
"PDF_EXTRACT_IMAGES",
"rag.pdf_extract_images",
os.environ.get("PDF_EXTRACT_IMAGES", "False").lower() == "true",
)
RAG_EMBEDDING_MODEL = os.environ.get( RAG_EMBEDDING_MODEL = PersistentConfig(
"RAG_EMBEDDING_MODEL", "sentence-transformers/all-MiniLM-L6-v2" "RAG_EMBEDDING_MODEL",
"rag.embedding_model",
os.environ.get("RAG_EMBEDDING_MODEL", "sentence-transformers/all-MiniLM-L6-v2"),
) )
log.info(f"Embedding model set: {RAG_EMBEDDING_MODEL}"), log.info(f"Embedding model set: {RAG_EMBEDDING_MODEL.value}"),
RAG_EMBEDDING_MODEL_AUTO_UPDATE = ( RAG_EMBEDDING_MODEL_AUTO_UPDATE = (
os.environ.get("RAG_EMBEDDING_MODEL_AUTO_UPDATE", "").lower() == "true" os.environ.get("RAG_EMBEDDING_MODEL_AUTO_UPDATE", "").lower() == "true"
...@@ -487,9 +631,13 @@ RAG_EMBEDDING_MODEL_TRUST_REMOTE_CODE = ( ...@@ -487,9 +631,13 @@ RAG_EMBEDDING_MODEL_TRUST_REMOTE_CODE = (
os.environ.get("RAG_EMBEDDING_MODEL_TRUST_REMOTE_CODE", "").lower() == "true" os.environ.get("RAG_EMBEDDING_MODEL_TRUST_REMOTE_CODE", "").lower() == "true"
) )
RAG_RERANKING_MODEL = os.environ.get("RAG_RERANKING_MODEL", "") RAG_RERANKING_MODEL = PersistentConfig(
if not RAG_RERANKING_MODEL == "": "RAG_RERANKING_MODEL",
log.info(f"Reranking model set: {RAG_RERANKING_MODEL}"), "rag.reranking_model",
os.environ.get("RAG_RERANKING_MODEL", ""),
)
if RAG_RERANKING_MODEL.value != "":
log.info(f"Reranking model set: {RAG_RERANKING_MODEL.value}"),
RAG_RERANKING_MODEL_AUTO_UPDATE = ( RAG_RERANKING_MODEL_AUTO_UPDATE = (
os.environ.get("RAG_RERANKING_MODEL_AUTO_UPDATE", "").lower() == "true" os.environ.get("RAG_RERANKING_MODEL_AUTO_UPDATE", "").lower() == "true"
...@@ -527,9 +675,14 @@ if USE_CUDA.lower() == "true": ...@@ -527,9 +675,14 @@ if USE_CUDA.lower() == "true":
else: else:
DEVICE_TYPE = "cpu" DEVICE_TYPE = "cpu"
CHUNK_SIZE = PersistentConfig(
CHUNK_SIZE = int(os.environ.get("CHUNK_SIZE", "1500")) "CHUNK_SIZE", "rag.chunk_size", int(os.environ.get("CHUNK_SIZE", "1500"))
CHUNK_OVERLAP = int(os.environ.get("CHUNK_OVERLAP", "100")) )
CHUNK_OVERLAP = PersistentConfig(
"CHUNK_OVERLAP",
"rag.chunk_overlap",
int(os.environ.get("CHUNK_OVERLAP", "100")),
)
DEFAULT_RAG_TEMPLATE = """Use the following context as your learned knowledge, inside <context></context> XML tags. DEFAULT_RAG_TEMPLATE = """Use the following context as your learned knowledge, inside <context></context> XML tags.
<context> <context>
...@@ -545,16 +698,32 @@ And answer according to the language of the user's question. ...@@ -545,16 +698,32 @@ And answer according to the language of the user's question.
Given the context information, answer the query. Given the context information, answer the query.
Query: [query]""" Query: [query]"""
RAG_TEMPLATE = os.environ.get("RAG_TEMPLATE", DEFAULT_RAG_TEMPLATE) RAG_TEMPLATE = PersistentConfig(
"RAG_TEMPLATE",
"rag.template",
os.environ.get("RAG_TEMPLATE", DEFAULT_RAG_TEMPLATE),
)
RAG_OPENAI_API_BASE_URL = os.getenv("RAG_OPENAI_API_BASE_URL", OPENAI_API_BASE_URL) RAG_OPENAI_API_BASE_URL = PersistentConfig(
RAG_OPENAI_API_KEY = os.getenv("RAG_OPENAI_API_KEY", OPENAI_API_KEY) "RAG_OPENAI_API_BASE_URL",
"rag.openai_api_base_url",
os.getenv("RAG_OPENAI_API_BASE_URL", OPENAI_API_BASE_URL),
)
RAG_OPENAI_API_KEY = PersistentConfig(
"RAG_OPENAI_API_KEY",
"rag.openai_api_key",
os.getenv("RAG_OPENAI_API_KEY", OPENAI_API_KEY),
)
ENABLE_RAG_LOCAL_WEB_FETCH = ( ENABLE_RAG_LOCAL_WEB_FETCH = (
os.getenv("ENABLE_RAG_LOCAL_WEB_FETCH", "False").lower() == "true" os.getenv("ENABLE_RAG_LOCAL_WEB_FETCH", "False").lower() == "true"
) )
YOUTUBE_LOADER_LANGUAGE = os.getenv("YOUTUBE_LOADER_LANGUAGE", "en").split(",") YOUTUBE_LOADER_LANGUAGE = PersistentConfig(
"YOUTUBE_LOADER_LANGUAGE",
"rag.youtube_loader_language",
os.getenv("YOUTUBE_LOADER_LANGUAGE", "en").split(","),
)
#################################### ####################################
# Transcribe # Transcribe
...@@ -571,34 +740,78 @@ WHISPER_MODEL_AUTO_UPDATE = ( ...@@ -571,34 +740,78 @@ WHISPER_MODEL_AUTO_UPDATE = (
# Images # Images
#################################### ####################################
IMAGE_GENERATION_ENGINE = os.getenv("IMAGE_GENERATION_ENGINE", "") IMAGE_GENERATION_ENGINE = PersistentConfig(
"IMAGE_GENERATION_ENGINE",
"image_generation.engine",
os.getenv("IMAGE_GENERATION_ENGINE", ""),
)
ENABLE_IMAGE_GENERATION = ( ENABLE_IMAGE_GENERATION = PersistentConfig(
os.environ.get("ENABLE_IMAGE_GENERATION", "").lower() == "true" "ENABLE_IMAGE_GENERATION",
"image_generation.enable",
os.environ.get("ENABLE_IMAGE_GENERATION", "").lower() == "true",
)
AUTOMATIC1111_BASE_URL = PersistentConfig(
"AUTOMATIC1111_BASE_URL",
"image_generation.automatic1111.base_url",
os.getenv("AUTOMATIC1111_BASE_URL", ""),
) )
AUTOMATIC1111_BASE_URL = os.getenv("AUTOMATIC1111_BASE_URL", "")
COMFYUI_BASE_URL = os.getenv("COMFYUI_BASE_URL", "") COMFYUI_BASE_URL = PersistentConfig(
"COMFYUI_BASE_URL",
"image_generation.comfyui.base_url",
os.getenv("COMFYUI_BASE_URL", ""),
)
IMAGES_OPENAI_API_BASE_URL = os.getenv( IMAGES_OPENAI_API_BASE_URL = PersistentConfig(
"IMAGES_OPENAI_API_BASE_URL", OPENAI_API_BASE_URL "IMAGES_OPENAI_API_BASE_URL",
"image_generation.openai.api_base_url",
os.getenv("IMAGES_OPENAI_API_BASE_URL", OPENAI_API_BASE_URL),
)
IMAGES_OPENAI_API_KEY = PersistentConfig(
"IMAGES_OPENAI_API_KEY",
"image_generation.openai.api_key",
os.getenv("IMAGES_OPENAI_API_KEY", OPENAI_API_KEY),
) )
IMAGES_OPENAI_API_KEY = os.getenv("IMAGES_OPENAI_API_KEY", OPENAI_API_KEY)
IMAGE_SIZE = os.getenv("IMAGE_SIZE", "512x512") IMAGE_SIZE = PersistentConfig(
"IMAGE_SIZE", "image_generation.size", os.getenv("IMAGE_SIZE", "512x512")
)
IMAGE_STEPS = int(os.getenv("IMAGE_STEPS", 50)) IMAGE_STEPS = PersistentConfig(
"IMAGE_STEPS", "image_generation.steps", int(os.getenv("IMAGE_STEPS", 50))
)
IMAGE_GENERATION_MODEL = os.getenv("IMAGE_GENERATION_MODEL", "") IMAGE_GENERATION_MODEL = PersistentConfig(
"IMAGE_GENERATION_MODEL",
"image_generation.model",
os.getenv("IMAGE_GENERATION_MODEL", ""),
)
#################################### ####################################
# Audio # Audio
#################################### ####################################
AUDIO_OPENAI_API_BASE_URL = os.getenv("AUDIO_OPENAI_API_BASE_URL", OPENAI_API_BASE_URL) AUDIO_OPENAI_API_BASE_URL = PersistentConfig(
AUDIO_OPENAI_API_KEY = os.getenv("AUDIO_OPENAI_API_KEY", OPENAI_API_KEY) "AUDIO_OPENAI_API_BASE_URL",
AUDIO_OPENAI_API_MODEL = os.getenv("AUDIO_OPENAI_API_MODEL", "tts-1") "audio.openai.api_base_url",
AUDIO_OPENAI_API_VOICE = os.getenv("AUDIO_OPENAI_API_VOICE", "alloy") os.getenv("AUDIO_OPENAI_API_BASE_URL", OPENAI_API_BASE_URL),
)
AUDIO_OPENAI_API_KEY = PersistentConfig(
"AUDIO_OPENAI_API_KEY",
"audio.openai.api_key",
os.getenv("AUDIO_OPENAI_API_KEY", OPENAI_API_KEY),
)
AUDIO_OPENAI_API_MODEL = PersistentConfig(
"AUDIO_OPENAI_API_MODEL",
"audio.openai.api_model",
os.getenv("AUDIO_OPENAI_API_MODEL", "tts-1"),
)
AUDIO_OPENAI_API_VOICE = PersistentConfig(
"AUDIO_OPENAI_API_VOICE",
"audio.openai.api_voice",
os.getenv("AUDIO_OPENAI_API_VOICE", "alloy"),
)
#################################### ####################################
# LiteLLM # LiteLLM
......
from contextlib import asynccontextmanager
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
import json import json
import markdown import markdown
...@@ -58,6 +59,7 @@ from config import ( ...@@ -58,6 +59,7 @@ from config import (
SRC_LOG_LEVELS, SRC_LOG_LEVELS,
WEBHOOK_URL, WEBHOOK_URL,
ENABLE_ADMIN_EXPORT, ENABLE_ADMIN_EXPORT,
AppConfig,
) )
from constants import ERROR_MESSAGES from constants import ERROR_MESSAGES
...@@ -92,16 +94,41 @@ https://github.com/open-webui/open-webui ...@@ -92,16 +94,41 @@ https://github.com/open-webui/open-webui
""" """
) )
app = FastAPI(docs_url="/docs" if ENV == "dev" else None, redoc_url=None)
app.state.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER @asynccontextmanager
app.state.MODEL_FILTER_LIST = MODEL_FILTER_LIST async def lifespan(app: FastAPI):
if ENABLE_LITELLM:
asyncio.create_task(start_litellm_background())
yield
if ENABLE_LITELLM:
await shutdown_litellm_background()
app = FastAPI(
docs_url="/docs" if ENV == "dev" else None, redoc_url=None, lifespan=lifespan
)
app.state.WEBHOOK_URL = WEBHOOK_URL app.state.config = AppConfig()
app.state.config.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER
app.state.config.MODEL_FILTER_LIST = MODEL_FILTER_LIST
app.state.config.WEBHOOK_URL = WEBHOOK_URL
origins = ["*"] origins = ["*"]
# Custom middleware to add security headers
# class SecurityHeadersMiddleware(BaseHTTPMiddleware):
# async def dispatch(self, request: Request, call_next):
# response: Response = await call_next(request)
# response.headers["Cross-Origin-Opener-Policy"] = "same-origin"
# response.headers["Cross-Origin-Embedder-Policy"] = "require-corp"
# return response
# app.add_middleware(SecurityHeadersMiddleware)
class RAGMiddleware(BaseHTTPMiddleware): class RAGMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next): async def dispatch(self, request: Request, call_next):
return_citations = False return_citations = False
...@@ -129,12 +156,12 @@ class RAGMiddleware(BaseHTTPMiddleware): ...@@ -129,12 +156,12 @@ class RAGMiddleware(BaseHTTPMiddleware):
data["messages"], citations = rag_messages( data["messages"], citations = rag_messages(
docs=data["docs"], docs=data["docs"],
messages=data["messages"], messages=data["messages"],
template=rag_app.state.RAG_TEMPLATE, template=rag_app.state.config.RAG_TEMPLATE,
embedding_function=rag_app.state.EMBEDDING_FUNCTION, embedding_function=rag_app.state.EMBEDDING_FUNCTION,
k=rag_app.state.TOP_K, k=rag_app.state.config.TOP_K,
reranking_function=rag_app.state.sentence_transformer_rf, reranking_function=rag_app.state.sentence_transformer_rf,
r=rag_app.state.RELEVANCE_THRESHOLD, r=rag_app.state.config.RELEVANCE_THRESHOLD,
hybrid_search=rag_app.state.ENABLE_RAG_HYBRID_SEARCH, hybrid_search=rag_app.state.config.ENABLE_RAG_HYBRID_SEARCH,
) )
del data["docs"] del data["docs"]
...@@ -211,15 +238,15 @@ async def check_url(request: Request, call_next): ...@@ -211,15 +238,15 @@ async def check_url(request: Request, call_next):
return response return response
@app.on_event("startup") @app.middleware("http")
async def on_startup(): async def update_embedding_function(request: Request, call_next):
if ENABLE_LITELLM: response = await call_next(request)
asyncio.create_task(start_litellm_background()) if "/embedding/update" in request.url.path:
webui_app.state.EMBEDDING_FUNCTION = rag_app.state.EMBEDDING_FUNCTION
return response
app.mount("/api/v1", webui_app)
app.mount("/litellm/api", litellm_app) app.mount("/litellm/api", litellm_app)
app.mount("/ollama", ollama_app) app.mount("/ollama", ollama_app)
app.mount("/openai/api", openai_app) app.mount("/openai/api", openai_app)
...@@ -227,6 +254,10 @@ app.mount("/images/api/v1", images_app) ...@@ -227,6 +254,10 @@ app.mount("/images/api/v1", images_app)
app.mount("/audio/api/v1", audio_app) app.mount("/audio/api/v1", audio_app)
app.mount("/rag/api/v1", rag_app) app.mount("/rag/api/v1", rag_app)
app.mount("/api/v1", webui_app)
webui_app.state.EMBEDDING_FUNCTION = rag_app.state.EMBEDDING_FUNCTION
@app.get("/api/config") @app.get("/api/config")
async def get_app_config(): async def get_app_config():
...@@ -243,9 +274,9 @@ async def get_app_config(): ...@@ -243,9 +274,9 @@ async def get_app_config():
"version": VERSION, "version": VERSION,
"auth": WEBUI_AUTH, "auth": WEBUI_AUTH,
"default_locale": default_locale, "default_locale": default_locale,
"images": images_app.state.ENABLED, "images": images_app.state.config.ENABLED,
"default_models": webui_app.state.DEFAULT_MODELS, "default_models": webui_app.state.config.DEFAULT_MODELS,
"default_prompt_suggestions": webui_app.state.DEFAULT_PROMPT_SUGGESTIONS, "default_prompt_suggestions": webui_app.state.config.DEFAULT_PROMPT_SUGGESTIONS,
"trusted_header_auth": bool(webui_app.state.AUTH_TRUSTED_EMAIL_HEADER), "trusted_header_auth": bool(webui_app.state.AUTH_TRUSTED_EMAIL_HEADER),
"admin_export_enabled": ENABLE_ADMIN_EXPORT, "admin_export_enabled": ENABLE_ADMIN_EXPORT,
} }
...@@ -254,8 +285,8 @@ async def get_app_config(): ...@@ -254,8 +285,8 @@ async def get_app_config():
@app.get("/api/config/model/filter") @app.get("/api/config/model/filter")
async def get_model_filter_config(user=Depends(get_admin_user)): async def get_model_filter_config(user=Depends(get_admin_user)):
return { return {
"enabled": app.state.ENABLE_MODEL_FILTER, "enabled": app.state.config.ENABLE_MODEL_FILTER,
"models": app.state.MODEL_FILTER_LIST, "models": app.state.config.MODEL_FILTER_LIST,
} }
...@@ -268,28 +299,28 @@ class ModelFilterConfigForm(BaseModel): ...@@ -268,28 +299,28 @@ class ModelFilterConfigForm(BaseModel):
async def update_model_filter_config( async def update_model_filter_config(
form_data: ModelFilterConfigForm, user=Depends(get_admin_user) form_data: ModelFilterConfigForm, user=Depends(get_admin_user)
): ):
app.state.ENABLE_MODEL_FILTER = form_data.enabled app.state.config.ENABLE_MODEL_FILTER = form_data.enabled
app.state.MODEL_FILTER_LIST = form_data.models app.state.config.MODEL_FILTER_LIST = form_data.models
ollama_app.state.ENABLE_MODEL_FILTER = app.state.ENABLE_MODEL_FILTER ollama_app.state.config.ENABLE_MODEL_FILTER = app.state.config.ENABLE_MODEL_FILTER
ollama_app.state.MODEL_FILTER_LIST = app.state.MODEL_FILTER_LIST ollama_app.state.config.MODEL_FILTER_LIST = app.state.config.MODEL_FILTER_LIST
openai_app.state.ENABLE_MODEL_FILTER = app.state.ENABLE_MODEL_FILTER openai_app.state.config.ENABLE_MODEL_FILTER = app.state.config.ENABLE_MODEL_FILTER
openai_app.state.MODEL_FILTER_LIST = app.state.MODEL_FILTER_LIST openai_app.state.config.MODEL_FILTER_LIST = app.state.config.MODEL_FILTER_LIST
litellm_app.state.ENABLE_MODEL_FILTER = app.state.ENABLE_MODEL_FILTER litellm_app.state.ENABLE_MODEL_FILTER = app.state.config.ENABLE_MODEL_FILTER
litellm_app.state.MODEL_FILTER_LIST = app.state.MODEL_FILTER_LIST litellm_app.state.MODEL_FILTER_LIST = app.state.config.MODEL_FILTER_LIST
return { return {
"enabled": app.state.ENABLE_MODEL_FILTER, "enabled": app.state.config.ENABLE_MODEL_FILTER,
"models": app.state.MODEL_FILTER_LIST, "models": app.state.config.MODEL_FILTER_LIST,
} }
@app.get("/api/webhook") @app.get("/api/webhook")
async def get_webhook_url(user=Depends(get_admin_user)): async def get_webhook_url(user=Depends(get_admin_user)):
return { return {
"url": app.state.WEBHOOK_URL, "url": app.state.config.WEBHOOK_URL,
} }
...@@ -299,12 +330,12 @@ class UrlForm(BaseModel): ...@@ -299,12 +330,12 @@ class UrlForm(BaseModel):
@app.post("/api/webhook") @app.post("/api/webhook")
async def update_webhook_url(form_data: UrlForm, user=Depends(get_admin_user)): async def update_webhook_url(form_data: UrlForm, user=Depends(get_admin_user)):
app.state.WEBHOOK_URL = form_data.url app.state.config.WEBHOOK_URL = form_data.url
webui_app.state.WEBHOOK_URL = app.state.WEBHOOK_URL webui_app.state.WEBHOOK_URL = app.state.config.WEBHOOK_URL
return { return {
"url": app.state.WEBHOOK_URL, "url": app.state.config.WEBHOOK_URL,
} }
...@@ -368,6 +399,11 @@ async def get_opensearch_xml(): ...@@ -368,6 +399,11 @@ async def get_opensearch_xml():
return Response(content=xml_content, media_type="application/xml") return Response(content=xml_content, media_type="application/xml")
@app.get("/health")
async def healthcheck():
return {"status": True}
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static") app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
app.mount("/cache", StaticFiles(directory=CACHE_DIR), name="cache") app.mount("/cache", StaticFiles(directory=CACHE_DIR), name="cache")
...@@ -381,9 +417,3 @@ else: ...@@ -381,9 +417,3 @@ else:
log.warning( log.warning(
f"Frontend build directory not found at '{FRONTEND_BUILD_DIR}'. Serving API only." f"Frontend build directory not found at '{FRONTEND_BUILD_DIR}'. Serving API only."
) )
@app.on_event("shutdown")
async def shutdown_event():
if ENABLE_LITELLM:
await shutdown_litellm_background()
...@@ -42,5 +42,37 @@ describe('Settings', () => { ...@@ -42,5 +42,37 @@ describe('Settings', () => {
.find('div[aria-label="Generation Info"]', { timeout: 120_000 }) // Generation Info is created after the stop token is received .find('div[aria-label="Generation Info"]', { timeout: 120_000 }) // Generation Info is created after the stop token is received
.should('exist'); .should('exist');
}); });
it('user can share chat', () => {
// Click on the model selector
cy.get('button[aria-label="Select a model"]').click();
// Select the first model
cy.get('button[aria-label="model-item"]').first().click();
// Type a message
cy.get('#chat-textarea').type('Hi, what can you do? A single sentence only please.', {
force: true
});
// Send the message
cy.get('button[type="submit"]').click();
// User's message should be visible
cy.get('.chat-user').should('exist');
// Wait for the response
cy.get('.chat-assistant', { timeout: 120_000 }) // .chat-assistant is created after the first token is received
.find('div[aria-label="Generation Info"]', { timeout: 120_000 }) // Generation Info is created after the stop token is received
.should('exist');
// spy on requests
const spy = cy.spy();
cy.intercept('GET', '/api/v1/chats/*', spy);
// Open context menu
cy.get('#chat-context-menu-button').click();
// Click share button
cy.get('#chat-share-button').click();
// Check if the share dialog is visible
cy.get('#copy-and-share-chat-button').should('exist');
cy.wrap({}, { timeout: 5000 }).should(() => {
// Check if the request was made twice (once for to replace chat object and once more due to change event)
expect(spy).to.be.callCount(2);
});
});
}); });
}); });
...@@ -15,12 +15,8 @@ describe('Settings', () => { ...@@ -15,12 +15,8 @@ describe('Settings', () => {
cy.loginAdmin(); cy.loginAdmin();
// Visit the home page // Visit the home page
cy.visit('/'); cy.visit('/');
// Open the sidebar if it is not already open // Click on the user menu
cy.get('[aria-label="Open sidebar"]').then(() => { cy.get('button[aria-label="User Menu"]').click();
cy.get('button[id="sidebar-toggle-button"]').click();
});
// Click on the profile link
cy.get('button').contains(adminUser.name).click();
// Click on the settings link // Click on the settings link
cy.get('button').contains('Settings').click(); cy.get('button').contains('Settings').click();
}); });
......
version: '3.8'
services: services:
ollama: ollama:
# Expose Ollama API outside the container stack # Expose Ollama API outside the container stack
......
version: '3.8'
services: services:
ollama: ollama:
volumes: volumes:
......
version: '3.8'
services: services:
ollama: ollama:
# GPU support # GPU support
......
version: '3.8'
services: services:
ollama: ollama:
volumes: volumes:
......
...@@ -17,7 +17,7 @@ If your issue or contribution pertains directly to the core Ollama technology, p ...@@ -17,7 +17,7 @@ If your issue or contribution pertains directly to the core Ollama technology, p
### 🚨 Reporting Issues ### 🚨 Reporting Issues
Noticed something off? Have an idea? Check our [Issues tab](https://github.com/open-webui/oopen-webui/issues) to see if it's already been reported or suggested. If not, feel free to open a new issue. When reporting an issue, please follow our issue templates. These templates are designed to ensure that all necessary details are provided from the start, enabling us to address your concerns more efficiently. Noticed something off? Have an idea? Check our [Issues tab](https://github.com/open-webui/open-webui/issues) to see if it's already been reported or suggested. If not, feel free to open a new issue. When reporting an issue, please follow our issue templates. These templates are designed to ensure that all necessary details are provided from the start, enabling us to address your concerns more efficiently.
> [!IMPORTANT] > [!IMPORTANT]
> >
...@@ -54,7 +54,7 @@ Help us make Open WebUI more accessible by improving documentation, writing tuto ...@@ -54,7 +54,7 @@ Help us make Open WebUI more accessible by improving documentation, writing tuto
Help us make Open WebUI available to a wider audience. In this section, we'll guide you through the process of adding new translations to the project. Help us make Open WebUI available to a wider audience. In this section, we'll guide you through the process of adding new translations to the project.
We use JSON files to store translations. You can find the existing translation files in the `src/lib/i18n/locales` directory. Each directory corresponds to a specific language, for example, `en-US` for English (US), `fr-FR` for French (France) and so on. You can refer to [ISO 639 Language Codes][http://www.lingoes.net/en/translator/langcode.htm] to find the appropriate code for a specific language. We use JSON files to store translations. You can find the existing translation files in the `src/lib/i18n/locales` directory. Each directory corresponds to a specific language, for example, `en-US` for English (US), `fr-FR` for French (France) and so on. You can refer to [ISO 639 Language Codes](http://www.lingoes.net/en/translator/langcode.htm) to find the appropriate code for a specific language.
To add a new language: To add a new language:
......
This diff is collapsed.
{ {
"name": "open-webui", "name": "open-webui",
"version": "0.1.124", "version": "0.1.125",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite dev --host", "dev": "npm run pyodide:fetch && vite dev --host",
"build": "vite build", "build": "npm run pyodide:fetch && vite build",
"preview": "vite preview", "preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
...@@ -12,10 +12,12 @@ ...@@ -12,10 +12,12 @@
"lint:frontend": "eslint . --fix", "lint:frontend": "eslint . --fix",
"lint:types": "npm run check", "lint:types": "npm run check",
"lint:backend": "pylint backend/", "lint:backend": "pylint backend/",
"format": "prettier --plugin-search-dir --write '**/*.{js,ts,svelte,css,md,html,json}'", "format": "prettier --plugin-search-dir --write \"**/*.{js,ts,svelte,css,md,html,json}\"",
"format:backend": "black . --exclude \"/venv/\"", "format:backend": "black . --exclude \"/venv/\"",
"i18n:parse": "i18next --config i18next-parser.config.ts && prettier --write 'src/lib/i18n/**/*.{js,json}'", "i18n:parse": "i18next --config i18next-parser.config.ts && prettier --write \"src/lib/i18n/**/*.{js,json}\"",
"cy:open": "cypress open" "cy:open": "cypress open",
"test:frontend": "vitest",
"pyodide:fetch": "node scripts/prepare-pyodide.js"
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/adapter-auto": "^2.0.0", "@sveltejs/adapter-auto": "^2.0.0",
...@@ -41,10 +43,12 @@ ...@@ -41,10 +43,12 @@
"tailwindcss": "^3.3.3", "tailwindcss": "^3.3.3",
"tslib": "^2.4.1", "tslib": "^2.4.1",
"typescript": "^5.0.0", "typescript": "^5.0.0",
"vite": "^4.4.2" "vite": "^4.4.2",
"vitest": "^1.6.0"
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@pyscript/core": "^0.4.32",
"@sveltejs/adapter-node": "^1.3.1", "@sveltejs/adapter-node": "^1.3.1",
"async": "^3.2.5", "async": "^3.2.5",
"bits-ui": "^0.19.7", "bits-ui": "^0.19.7",
...@@ -59,6 +63,7 @@ ...@@ -59,6 +63,7 @@
"js-sha256": "^0.10.1", "js-sha256": "^0.10.1",
"katex": "^0.16.9", "katex": "^0.16.9",
"marked": "^9.1.0", "marked": "^9.1.0",
"pyodide": "^0.26.0-alpha.4",
"svelte-sonner": "^0.3.19", "svelte-sonner": "^0.3.19",
"tippy.js": "^6.3.7", "tippy.js": "^6.3.7",
"uuid": "^9.0.1" "uuid": "^9.0.1"
......
const packages = [
'requests',
'beautifulsoup4',
'numpy',
'pandas',
'matplotlib',
'scikit-learn',
'scipy',
'regex',
'seaborn'
];
import { loadPyodide } from 'pyodide';
import { writeFile, copyFile, readdir } from 'fs/promises';
async function downloadPackages() {
console.log('Setting up pyodide + micropip');
const pyodide = await loadPyodide({
packageCacheDir: 'static/pyodide'
});
await pyodide.loadPackage('micropip');
const micropip = pyodide.pyimport('micropip');
console.log('Downloading Pyodide packages:', packages);
await micropip.install(packages);
console.log('Pyodide packages downloaded, freezing into lock file');
const lockFile = await micropip.freeze();
await writeFile('static/pyodide/pyodide-lock.json', lockFile);
}
async function copyPyodide() {
console.log('Copying Pyodide files into static directory');
// Copy all files from node_modules/pyodide to static/pyodide
for await (const entry of await readdir('node_modules/pyodide')) {
await copyFile(`node_modules/pyodide/${entry}`, `static/pyodide/${entry}`);
}
}
await downloadPackages();
await copyPyodide();
...@@ -83,11 +83,31 @@ select { ...@@ -83,11 +83,31 @@ select {
display: none; display: none;
} }
.scrollbar-none:active::-webkit-scrollbar-thumb, .scrollbar-hidden:active::-webkit-scrollbar-thumb,
.scrollbar-none:focus::-webkit-scrollbar-thumb, .scrollbar-hidden:focus::-webkit-scrollbar-thumb,
.scrollbar-none:hover::-webkit-scrollbar-thumb { .scrollbar-hidden:hover::-webkit-scrollbar-thumb {
visibility: visible; visibility: visible;
} }
.scrollbar-none::-webkit-scrollbar-thumb { .scrollbar-hidden::-webkit-scrollbar-thumb {
visibility: hidden; visibility: hidden;
} }
.scrollbar-none::-webkit-scrollbar {
display: none; /* for Chrome, Safari and Opera */
}
.scrollbar-none {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
/* display: none; <- Crashes Chrome on hover */
-webkit-appearance: none;
margin: 0; /* <-- Apparently some margin are still there even though it's hidden */
}
input[type='number'] {
-moz-appearance: textfield; /* Firefox */
}
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
title="Open WebUI" title="Open WebUI"
href="/opensearch.xml" href="/opensearch.xml"
/> />
<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
(() => { (() => {
......
import { WEBUI_API_BASE_URL } from '$lib/constants';
export const getMemories = async (token: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/memories`, {
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();
})
.catch((err) => {
error = err.detail;
console.log(err);
return null;
});
if (error) {
throw error;
}
return res;
};
export const addNewMemory = async (token: string, content: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/memories/add`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `Bearer ${token}`
},
body: JSON.stringify({
content: content
})
})
.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 queryMemory = async (token: string, content: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/memories/query`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `Bearer ${token}`
},
body: JSON.stringify({
content: content
})
})
.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 deleteMemoryById = async (token: string, id: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/memories/${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 deleteMemoriesByUserId = async (token: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/memories/user`, {
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.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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