Commit 1bacd5d9 authored by Jun Siang Cheah's avatar Jun Siang Cheah
Browse files

Merge branch 'dev' into feat/model-config

parents 5d3eddf7 4edef531
# Pull Request Checklist
- [ ] **Target branch:** Pull requests should target the `dev` branch.
- [ ] **Description:** Briefly describe the changes in this pull request.
### Note to first-time contributors: Please open a discussion post in [Discussions](https://github.com/open-webui/open-webui/discussions) and describe your changes before submitting a pull request.
**Before submitting, make sure you've checked the following:**
- [ ] **Target branch:** Please verify that the pull request targets the `dev` branch.
- [ ] **Description:** Provide a concise description of the changes made in this pull request.
- [ ] **Changelog:** Ensure a changelog entry following the format of [Keep a Changelog](https://keepachangelog.com/) is added at the bottom of the PR description.
- [ ] **Documentation:** Have you updated relevant documentation [Open WebUI Docs](https://github.com/open-webui/docs), or other documentation sources?
- [ ] **Dependencies:** Are there any new dependencies? Have you updated the dependency versions in the documentation?
- [ ] **Testing:** Have you written and run sufficient tests for the changes?
- [ ] **Code Review:** Have you self-reviewed your code and addressed any coding standard issues?
- [ ] **Label title:** Ensure the pull request title is labeled properly using one of the following:
- [ ] **Testing:** Have you written and run sufficient tests for validating the changes?
- [ ] **Code review:** Have you performed a self-review of your code, addressing any coding standard issues and ensuring adherence to the project's coding standards?
- [ ] **Label:** To cleary categorize this pull request, assign a relevant label to the pull request title, using one of the following:
- **BREAKING CHANGE**: Significant changes that may affect compatibility
- **build**: Changes that affect the build system or external dependencies
- **ci**: Changes to our continuous integration processes or workflows
......@@ -26,7 +30,7 @@
### Description
- [Briefly describe the changes made in this pull request, including any relevant motivation and impact.]
- [Concisely describe the changes made in this pull request, including any relevant motivation and impact (e.g., fixing a bug, adding a feature, or improving performance)]
### Added
......@@ -62,3 +66,7 @@
- [Insert any additional context, notes, or explanations for the changes]
- [Reference any related issues, commits, or other relevant information]
### Screenshots or Videos
- [Attach any relevant screenshots or videos demonstrating the changes]
......@@ -16,6 +16,10 @@ __pycache__/
# C extensions
*.so
# Pyodide distribution
static/pyodide/*
!static/pyodide/pyodide-lock.json
# Distribution / packaging
.Python
build/
......
......@@ -310,3 +310,7 @@ dist
# cypress artifacts
cypress/videos
cypress/screenshots
/static/*
\ No newline at end of file
......@@ -11,6 +11,9 @@ ARG USE_CUDA_VER=cu121
# IMPORTANT: If you change the embedding model (sentence-transformers/all-MiniLM-L6-v2) and vice versa, you aren't able to use RAG Chat with your previous documents loaded in the WebUI! You need to re-embed them.
ARG USE_EMBEDDING_MODEL=sentence-transformers/all-MiniLM-L6-v2
ARG USE_RERANKING_MODEL=""
# Override at your own risk - non-root configurations are untested
ARG UID=0
ARG GID=0
######## WebUI frontend ########
FROM --platform=$BUILDPLATFORM node:21-alpine3.19 as build
......@@ -32,6 +35,8 @@ ARG USE_OLLAMA
ARG USE_CUDA_VER
ARG USE_EMBEDDING_MODEL
ARG USE_RERANKING_MODEL
ARG UID
ARG GID
## Basis ##
ENV ENV=prod \
......@@ -76,9 +81,20 @@ ENV HF_HOME="/app/backend/data/cache/embedding/models"
WORKDIR /app/backend
ENV HOME /root
# Create user and group if not root
RUN if [ $UID -ne 0 ]; then \
if [ $GID -ne 0 ]; then \
addgroup --gid $GID app; \
fi; \
adduser --uid $UID --gid $GID --home $HOME --disabled-password --no-create-home app; \
fi
RUN mkdir -p $HOME/.cache/chroma
RUN echo -n 00000000-0000-0000-0000-000000000000 > $HOME/.cache/chroma/telemetry_user_id
# Make sure the user has access to the app and root directory
RUN chown -R $UID:$GID /app $HOME
RUN if [ "$USE_OLLAMA" = "true" ]; then \
apt-get update && \
# Install pandoc and netcat
......@@ -102,7 +118,7 @@ RUN if [ "$USE_OLLAMA" = "true" ]; then \
fi
# install python dependencies
COPY ./backend/requirements.txt ./requirements.txt
COPY --chown=$UID:$GID ./backend/requirements.txt ./requirements.txt
RUN pip3 install uv && \
if [ "$USE_CUDA" = "true" ]; then \
......@@ -125,16 +141,17 @@ RUN pip3 install uv && \
# COPY --from=build /app/onnx /root/.cache/chroma/onnx_models/all-MiniLM-L6-v2/onnx
# copy built frontend files
COPY --from=build /app/build /app/build
COPY --from=build /app/CHANGELOG.md /app/CHANGELOG.md
COPY --from=build /app/package.json /app/package.json
COPY --chown=$UID:$GID --from=build /app/build /app/build
COPY --chown=$UID:$GID --from=build /app/CHANGELOG.md /app/CHANGELOG.md
COPY --chown=$UID:$GID --from=build /app/package.json /app/package.json
# copy backend files
COPY ./backend .
COPY --chown=$UID:$GID ./backend .
EXPOSE 8080
HEALTHCHECK CMD curl --silent --fail http://localhost:8080/health | jq -e '.status == true' || exit 1
USER $UID:$GID
CMD [ "bash", "start.sh"]
......@@ -397,7 +397,7 @@ def generate_image(
user=Depends(get_current_user),
):
width, height = tuple(map(int, app.state.config.IMAGE_SIZE).split("x"))
width, height = tuple(map(int, app.state.config.IMAGE_SIZE.split("x")))
r = None
try:
......
......@@ -75,6 +75,10 @@ with open(LITELLM_CONFIG_DIR, "r") as file:
litellm_config = yaml.safe_load(file)
app.state.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER.value
app.state.MODEL_FILTER_LIST = MODEL_FILTER_LIST.value
app.state.ENABLE = ENABLE_LITELLM
app.state.CONFIG = litellm_config
app.state.MODEL_CONFIG = MODEL_CONFIG.value.get("litellm", [])
......@@ -152,10 +156,6 @@ async def shutdown_litellm_background():
background_process = None
app.state.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER
app.state.MODEL_FILTER_LIST = MODEL_FILTER_LIST
@app.get("/")
async def get_status():
return {"status": True}
......
......@@ -65,8 +65,8 @@ app.add_middleware(
app.state.config = AppConfig()
app.state.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER
app.state.MODEL_FILTER_LIST = MODEL_FILTER_LIST
app.state.config.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER
app.state.config.MODEL_FILTER_LIST = MODEL_FILTER_LIST
app.state.MODEL_CONFIG = MODEL_CONFIG.value.get("ollama", [])
app.state.config.OLLAMA_BASE_URLS = OLLAMA_BASE_URLS
......@@ -126,8 +126,9 @@ async def cancel_ollama_request(request_id: str, user=Depends(get_current_user))
async def fetch_url(url):
timeout = aiohttp.ClientTimeout(total=5)
try:
async with aiohttp.ClientSession() as session:
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.get(url) as response:
return await response.json()
except Exception as e:
......@@ -190,11 +191,12 @@ async def get_ollama_tags(
if url_idx == None:
models = await get_all_models()
if app.state.ENABLE_MODEL_FILTER:
if app.state.config.ENABLE_MODEL_FILTER:
if user.role == "user":
models["models"] = list(
filter(
lambda model: model["name"] in app.state.MODEL_FILTER_LIST,
lambda model: model["name"]
in app.state.config.MODEL_FILTER_LIST,
models["models"],
)
)
......@@ -1058,11 +1060,12 @@ async def get_openai_models(
if url_idx == None:
models = await get_all_models()
if app.state.ENABLE_MODEL_FILTER:
if app.state.config.ENABLE_MODEL_FILTER:
if user.role == "user":
models["models"] = list(
filter(
lambda model: model["name"] in app.state.MODEL_FILTER_LIST,
lambda model: model["name"]
in app.state.config.MODEL_FILTER_LIST,
models["models"],
)
)
......
......@@ -21,6 +21,7 @@ from utils.utils import (
)
from config import (
SRC_LOG_LEVELS,
ENABLE_OPENAI_API,
OPENAI_API_BASE_URLS,
OPENAI_API_KEYS,
CACHE_DIR,
......@@ -47,12 +48,15 @@ app.add_middleware(
allow_headers=["*"],
)
app.state.config = AppConfig()
app.state.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER
app.state.MODEL_FILTER_LIST = MODEL_FILTER_LIST
app.state.config.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER
app.state.config.MODEL_FILTER_LIST = MODEL_FILTER_LIST
app.state.MODEL_CONFIG = MODEL_CONFIG.value.get("openai", [])
app.state.config.ENABLE_OPENAI_API = ENABLE_OPENAI_API
app.state.config.OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS
app.state.config.OPENAI_API_KEYS = OPENAI_API_KEYS
......@@ -70,6 +74,21 @@ async def check_url(request: Request, call_next):
return response
@app.get("/config")
async def get_config(user=Depends(get_admin_user)):
return {"ENABLE_OPENAI_API": app.state.config.ENABLE_OPENAI_API}
class OpenAIConfigForm(BaseModel):
enable_openai_api: Optional[bool] = None
@app.post("/config/update")
async def update_config(form_data: OpenAIConfigForm, user=Depends(get_admin_user)):
app.state.config.ENABLE_OPENAI_API = form_data.enable_openai_api
return {"ENABLE_OPENAI_API": app.state.config.ENABLE_OPENAI_API}
class UrlsUpdateForm(BaseModel):
urls: List[str]
......@@ -166,11 +185,15 @@ async def speech(request: Request, user=Depends(get_verified_user)):
async def fetch_url(url, key):
timeout = aiohttp.ClientTimeout(total=5)
try:
headers = {"Authorization": f"Bearer {key}"}
async with aiohttp.ClientSession() as session:
async with session.get(url, headers=headers) as response:
return await response.json()
if key != "":
headers = {"Authorization": f"Bearer {key}"}
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.get(url, headers=headers) as response:
return await response.json()
else:
return None
except Exception as e:
# Handle connection error here
log.error(f"Connection error: {e}")
......@@ -202,7 +225,7 @@ async def get_all_models():
if (
len(app.state.config.OPENAI_API_KEYS) == 1
and app.state.config.OPENAI_API_KEYS[0] == ""
):
) or not app.state.config.ENABLE_OPENAI_API:
models = {"data": []}
else:
tasks = [
......@@ -248,11 +271,11 @@ def add_custom_info_to_model(model: dict):
async def get_models(url_idx: Optional[int] = None, user=Depends(get_current_user)):
if url_idx == None:
models = await get_all_models()
if app.state.ENABLE_MODEL_FILTER:
if app.state.config.ENABLE_MODEL_FILTER:
if user.role == "user":
models["data"] = list(
filter(
lambda model: model["id"] in app.state.MODEL_FILTER_LIST,
lambda model: model["id"] in app.state.config.MODEL_FILTER_LIST,
models["data"],
)
)
......
......@@ -433,12 +433,12 @@ async def update_query_settings(
form_data: QuerySettingsForm, user=Depends(get_admin_user)
):
app.state.config.RAG_TEMPLATE = (
form_data.template if form_data.template else RAG_TEMPLATE,
form_data.template if form_data.template else RAG_TEMPLATE
)
app.state.config.TOP_K = form_data.k if form_data.k else 4
app.state.config.RELEVANCE_THRESHOLD = form_data.r if form_data.r else 0.0
app.state.config.ENABLE_RAG_HYBRID_SEARCH = (
form_data.hybrid if form_data.hybrid else False,
form_data.hybrid if form_data.hybrid else False
)
return {
"status": True,
......
......@@ -11,8 +11,9 @@ import logging
from apps.web.models.users import UserModel, UserUpdateForm, UserRoleUpdateForm, Users
from apps.web.models.auths import Auths
from apps.web.models.chats import Chats
from utils.utils import get_current_user, get_password_hash, get_admin_user
from utils.utils import get_verified_user, get_password_hash, get_admin_user
from constants import ERROR_MESSAGES
from config import SRC_LOG_LEVELS
......@@ -67,6 +68,41 @@ async def update_user_role(form_data: UserRoleUpdateForm, user=Depends(get_admin
)
############################
# GetUserById
############################
class UserResponse(BaseModel):
name: str
profile_image_url: str
@router.get("/{user_id}", response_model=UserResponse)
async def get_user_by_id(user_id: str, user=Depends(get_verified_user)):
if user_id.startswith("shared-"):
chat_id = user_id.replace("shared-", "")
chat = Chats.get_chat_by_id(chat_id)
if chat:
user_id = chat.user_id
else:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.USER_NOT_FOUND,
)
user = Users.get_user_by_id(user_id)
if user:
return UserResponse(name=user.name, profile_image_url=user.profile_image_url)
else:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.USER_NOT_FOUND,
)
############################
# UpdateUserById
############################
......
......@@ -417,6 +417,14 @@ OLLAMA_BASE_URLS = PersistentConfig(
# 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_BASE_URL = os.environ.get("OPENAI_API_BASE_URL", "")
......
......@@ -120,6 +120,18 @@ app.state.config.WEBHOOK_URL = WEBHOOK_URL
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):
async def dispatch(self, request: Request, call_next):
return_citations = False
......@@ -280,14 +292,14 @@ class ModelFilterConfigForm(BaseModel):
async def update_model_filter_config(
form_data: ModelFilterConfigForm, user=Depends(get_admin_user)
):
app.state.config.ENABLE_MODEL_FILTER, form_data.enabled
app.state.config.MODEL_FILTER_LIST, form_data.models
app.state.config.ENABLE_MODEL_FILTER = form_data.enabled
app.state.config.MODEL_FILTER_LIST = form_data.models
ollama_app.state.ENABLE_MODEL_FILTER = app.state.config.ENABLE_MODEL_FILTER
ollama_app.state.MODEL_FILTER_LIST = app.state.config.MODEL_FILTER_LIST
ollama_app.state.config.ENABLE_MODEL_FILTER = app.state.config.ENABLE_MODEL_FILTER
ollama_app.state.config.MODEL_FILTER_LIST = app.state.config.MODEL_FILTER_LIST
openai_app.state.ENABLE_MODEL_FILTER = app.state.config.ENABLE_MODEL_FILTER
openai_app.state.MODEL_FILTER_LIST = app.state.config.MODEL_FILTER_LIST
openai_app.state.config.ENABLE_MODEL_FILTER = app.state.config.ENABLE_MODEL_FILTER
openai_app.state.config.MODEL_FILTER_LIST = app.state.config.MODEL_FILTER_LIST
litellm_app.state.ENABLE_MODEL_FILTER = app.state.config.ENABLE_MODEL_FILTER
litellm_app.state.MODEL_FILTER_LIST = app.state.config.MODEL_FILTER_LIST
......
......@@ -8,6 +8,7 @@
"name": "open-webui",
"version": "0.1.124",
"dependencies": {
"@pyscript/core": "^0.4.32",
"@sveltejs/adapter-node": "^1.3.1",
"async": "^3.2.5",
"bits-ui": "^0.19.7",
......@@ -22,6 +23,7 @@
"js-sha256": "^0.10.1",
"katex": "^0.16.9",
"marked": "^9.1.0",
"pyodide": "^0.26.0-alpha.4",
"svelte-sonner": "^0.3.19",
"tippy.js": "^6.3.7",
"uuid": "^9.0.1"
......@@ -890,6 +892,19 @@
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@pyscript/core": {
"version": "0.4.32",
"resolved": "https://registry.npmjs.org/@pyscript/core/-/core-0.4.32.tgz",
"integrity": "sha512-WQATzPp1ggf871+PukCmTypzScXkEB1EWD/vg5GNxpM96N6rDPqQ13msuA5XvwU01ZVhL8HHSFDLk4IfaXNGWg==",
"dependencies": {
"@ungap/with-resolvers": "^0.1.0",
"basic-devtools": "^0.1.6",
"polyscript": "^0.12.8",
"sticky-module": "^0.1.1",
"to-json-callback": "^0.1.1",
"type-checked-collections": "^0.1.7"
}
},
"node_modules/@rollup/plugin-commonjs": {
"version": "25.0.7",
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.7.tgz",
......@@ -1605,8 +1620,12 @@
"node_modules/@ungap/structured-clone": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
"dev": true
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ=="
},
"node_modules/@ungap/with-resolvers": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@ungap/with-resolvers/-/with-resolvers-0.1.0.tgz",
"integrity": "sha512-g7f0IkJdPW2xhY7H4iE72DAsIyfuwEFc6JWc2tYFwKDMWWAF699vGjrM348cwQuOXgHpe1gWFe+Eiyjx/ewvvw=="
},
"node_modules/@vitest/expect": {
"version": "1.6.0",
......@@ -1713,6 +1732,11 @@
"@types/estree": "^1.0.0"
}
},
"node_modules/@webreflection/fetch": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/@webreflection/fetch/-/fetch-0.1.5.tgz",
"integrity": "sha512-zCcqCJoNLvdeF41asAK71XPlwSPieeRDsE09albBunJEksuYPYNillKNQjf8p5BqSoTKTuKrW3lUm3MNodUC4g=="
},
"node_modules/acorn": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
......@@ -2027,6 +2051,11 @@
"dev": 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": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
......@@ -2047,6 +2076,11 @@
}
]
},
"node_modules/basic-devtools": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/basic-devtools/-/basic-devtools-0.1.6.tgz",
"integrity": "sha512-g9zJ63GmdUesS3/Fwv0B5SYX6nR56TQXmGr+wE5PRTNCnGQMYWhUx/nZB/mMWnQJVLPPAp89oxDNlasdtNkW5Q=="
},
"node_modules/bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
......@@ -2661,6 +2695,28 @@
"@types/estree": "^1.0.0"
}
},
"node_modules/codedent": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/codedent/-/codedent-0.1.2.tgz",
"integrity": "sha512-qEqzcy5viM3UoCN0jYHZeXZoyd4NZQzYFg0kOBj8O1CgoGG9WYYTF+VeQRsN0OSKFjF3G1u4WDUOtOsWEx6N2w==",
"dependencies": {
"plain-tag": "^0.1.3"
}
},
"node_modules/coincident": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/coincident/-/coincident-1.2.3.tgz",
"integrity": "sha512-Uxz3BMTWIslzeWjuQnizGWVg0j6khbvHUQ8+5BdM7WuJEm4ALXwq3wluYoB+uF68uPBz/oUOeJnYURKyfjexlA==",
"dependencies": {
"@ungap/structured-clone": "^1.2.0",
"@ungap/with-resolvers": "^0.1.0",
"gc-hook": "^0.3.1",
"proxy-target": "^3.0.2"
},
"optionalDependencies": {
"ws": "^8.16.0"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
......@@ -4001,6 +4057,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/gc-hook": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/gc-hook/-/gc-hook-0.3.1.tgz",
"integrity": "sha512-E5M+O/h2o7eZzGhzRZGex6hbB3k4NWqO0eA+OzLRLXxhdbYPajZnynPwAtphnh+cRHPwsj5Z80dqZlfI4eK55A=="
},
"node_modules/get-func-name": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
......@@ -4328,6 +4389,11 @@
"node": ">=12.0.0"
}
},
"node_modules/html-escaper": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz",
"integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ=="
},
"node_modules/htmlparser2": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
......@@ -5838,6 +5904,29 @@
"pathe": "^1.1.2"
}
},
"node_modules/plain-tag": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/plain-tag/-/plain-tag-0.1.3.tgz",
"integrity": "sha512-yyVAOFKTAElc7KdLt2+UKGExNYwYb/Y/WE9i+1ezCQsJE8gbKSjewfpRqK2nQgZ4d4hhAAGgDCOcIZVilqE5UA=="
},
"node_modules/polyscript": {
"version": "0.12.8",
"resolved": "https://registry.npmjs.org/polyscript/-/polyscript-0.12.8.tgz",
"integrity": "sha512-kcG3W9jU/s1sYjWOTAa2jAh5D2jm3zJRi+glSTsC+lA3D1b/Sd67pEIGpyL9bWNKYSimqAx4se6jAhQjJZ7+jQ==",
"dependencies": {
"@ungap/structured-clone": "^1.2.0",
"@ungap/with-resolvers": "^0.1.0",
"@webreflection/fetch": "^0.1.5",
"basic-devtools": "^0.1.6",
"codedent": "^0.1.2",
"coincident": "^1.2.3",
"gc-hook": "^0.3.1",
"html-escaper": "^3.0.3",
"proxy-target": "^3.0.2",
"sticky-module": "^0.1.1",
"to-json-callback": "^0.1.1"
}
},
"node_modules/postcss": {
"version": "8.4.38",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
......@@ -6151,6 +6240,11 @@
"integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==",
"dev": true
},
"node_modules/proxy-target": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/proxy-target/-/proxy-target-3.0.2.tgz",
"integrity": "sha512-FFE1XNwXX/FNC3/P8HiKaJSy/Qk68RitG/QEcLy/bVnTAPlgTAWPZKh0pARLAnpfXQPKyalBhk009NRTgsk8vQ=="
},
"node_modules/psl": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
......@@ -6176,6 +6270,18 @@
"node": ">=6"
}
},
"node_modules/pyodide": {
"version": "0.26.0-alpha.4",
"resolved": "https://registry.npmjs.org/pyodide/-/pyodide-0.26.0-alpha.4.tgz",
"integrity": "sha512-Ixuczq99DwhQlE+Bt0RaS6Ln9MHSZOkbU6iN8azwaeorjHtr7ukaxh+FeTxViFrp2y+ITyKgmcobY+JnBPcULw==",
"dependencies": {
"base-64": "^1.0.0",
"ws": "^8.5.0"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/qs": {
"version": "6.10.4",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.10.4.tgz",
......@@ -6858,6 +6964,11 @@
"integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==",
"dev": true
},
"node_modules/sticky-module": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/sticky-module/-/sticky-module-0.1.1.tgz",
"integrity": "sha512-IuYgnyIMUx/m6rtu14l/LR2MaqOLtpXcWkxPmtPsiScRHEo+S4Tojk+DWFHOncSdFX/OsoLOM4+T92yOmI1AMw=="
},
"node_modules/stream-composer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/stream-composer/-/stream-composer-1.0.2.tgz",
......@@ -7520,6 +7631,11 @@
"node": ">=14.14"
}
},
"node_modules/to-json-callback": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/to-json-callback/-/to-json-callback-0.1.1.tgz",
"integrity": "sha512-BzOeinTT3NjE+FJ2iCvWB8HvyuyBzoH3WlSnJ+AYVC4tlePyZWSYdkQIFOARWiq0t35/XhmI0uQsFiUsRksRqg=="
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
......@@ -7629,6 +7745,11 @@
"node": ">= 0.8.0"
}
},
"node_modules/type-checked-collections": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/type-checked-collections/-/type-checked-collections-0.1.7.tgz",
"integrity": "sha512-fLIydlJy7IG9XL4wjRwEcKhxx/ekLXiWiMvcGo01cOMF+TN+5ZqajM1mRNRz2bNNi1bzou2yofhjZEQi7kgl9A=="
},
"node_modules/type-detect": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
......@@ -8883,6 +9004,26 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/ws": {
"version": "8.17.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz",
"integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==",
"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/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
......
......@@ -3,8 +3,8 @@
"version": "0.1.124",
"private": true,
"scripts": {
"dev": "vite dev --host",
"build": "vite build",
"dev": "npm run pyodide:fetch && vite dev --host",
"build": "npm run pyodide:fetch && vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
......@@ -16,7 +16,8 @@
"format:backend": "black . --exclude \"/venv/\"",
"i18n:parse": "i18next --config i18next-parser.config.ts && prettier --write \"src/lib/i18n/**/*.{js,json}\"",
"cy:open": "cypress open",
"test:frontend": "vitest"
"test:frontend": "vitest",
"pyodide:fetch": "node scripts/prepare-pyodide.js"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^2.0.0",
......@@ -47,6 +48,7 @@
},
"type": "module",
"dependencies": {
"@pyscript/core": "^0.4.32",
"@sveltejs/adapter-node": "^1.3.1",
"async": "^3.2.5",
"bits-ui": "^0.19.7",
......@@ -61,6 +63,7 @@
"js-sha256": "^0.10.1",
"katex": "^0.16.9",
"marked": "^9.1.0",
"pyodide": "^0.26.0-alpha.4",
"svelte-sonner": "^0.3.19",
"tippy.js": "^6.3.7",
"uuid": "^9.0.1"
......
const packages = [
'requests',
'beautifulsoup4',
'numpy',
'pandas',
'matplotlib',
'scikit-learn',
'scipy',
'regex'
];
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();
......@@ -12,6 +12,7 @@
title="Open WebUI"
href="/opensearch.xml"
/>
<script>
// On page load or when changing themes, best to add inline in `head` to avoid FOUC
(() => {
......
import { OPENAI_API_BASE_URL } from '$lib/constants';
import { promptTemplate } from '$lib/utils';
export const getOpenAIConfig = async (token: string = '') => {
let error = null;
const res = await fetch(`${OPENAI_API_BASE_URL}/config`, {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
...(token && { authorization: `Bearer ${token}` })
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((err) => {
console.log(err);
if ('detail' in err) {
error = err.detail;
} else {
error = 'Server connection failed';
}
return null;
});
if (error) {
throw error;
}
return res;
};
export const updateOpenAIConfig = async (token: string = '', enable_openai_api: boolean) => {
let error = null;
const res = await fetch(`${OPENAI_API_BASE_URL}/config/update`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
...(token && { authorization: `Bearer ${token}` })
},
body: JSON.stringify({
enable_openai_api: enable_openai_api
})
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((err) => {
console.log(err);
if ('detail' in err) {
error = err.detail;
} else {
error = 'Server connection failed';
}
return null;
});
if (error) {
throw error;
}
return res;
};
export const getOpenAIUrls = async (token: string = '') => {
let error = null;
......
......@@ -115,6 +115,33 @@ export const getUsers = async (token: string) => {
return res ? res : [];
};
export const getUserById = async (token: string, userId: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/users/${userId}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((err) => {
console.log(err);
error = err.detail;
return null;
});
if (error) {
throw error;
}
return res;
};
export const deleteUserById = async (token: string, userId: string) => {
let error = null;
......
......@@ -327,7 +327,6 @@
};
onMount(() => {
console.log(document.getElementById('sidebar'));
window.setTimeout(() => chatTextAreaElement?.focus(), 0);
const dropZone = document.querySelector('body');
......@@ -512,6 +511,7 @@
>
<div class="flex items-center gap-2 text-sm dark:text-gray-500">
<img
crossorigin="anonymous"
alt="model profile"
class="size-5 max-w-[28px] object-cover rounded-full"
src={$modelfiles.find((modelfile) => modelfile.tagName === selectedModel.id)
......@@ -600,7 +600,8 @@
}}
/>
<form
class=" flex flex-col relative w-full rounded-3xl px-1.5 border border-gray-100 dark:border-gray-850 bg-white dark:bg-gray-900 dark:text-gray-100"
dir={$settings?.chatDirection}
class=" flex flex-col relative w-full rounded-3xl px-1.5 bg-gray-50 dark:bg-gray-850 dark:text-gray-100"
on:submit|preventDefault={() => {
submitPrompt(prompt, user);
}}
......@@ -770,7 +771,7 @@
<textarea
id="chat-textarea"
bind:this={chatTextAreaElement}
class="scrollbar-hidden dark:bg-gray-900 dark:text-gray-100 outline-none w-full py-3 px-3 {fileUploadEnabled
class="scrollbar-hidden bg-gray-50 dark:bg-gray-850 dark:text-gray-100 outline-none w-full py-3 px-3 {fileUploadEnabled
? ''
: ' pl-4'} rounded-xl resize-none h-[48px]"
placeholder={chatInputPlaceholder !== ''
......
<script lang="ts">
import { v4 as uuidv4 } from 'uuid';
import { chats, config, modelfiles, settings, user } from '$lib/stores';
import { chats, config, modelfiles, settings, user as _user } from '$lib/stores';
import { tick, getContext } from 'svelte';
import { toast } from 'svelte-sonner';
......@@ -22,8 +22,9 @@
export let continueGeneration: Function;
export let regenerateResponse: Function;
export let user = $_user;
export let prompt;
export let suggestionPrompts;
export let suggestionPrompts = [];
export let processing = '';
export let bottomPadding = false;
export let autoScroll;
......@@ -294,7 +295,7 @@
{#if message.role === 'user'}
<UserMessage
on:delete={() => messageDeleteHandler(message.id)}
user={$user}
{user}
{readOnly}
{message}
isFirstMessage={messageIdx === 0}
......
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