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 # Pull Request Checklist
- [ ] **Target branch:** Pull requests should target the `dev` branch. ### 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.
- [ ] **Description:** Briefly describe the changes in this 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. - [ ] **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? - [ ] **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? - [ ] **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? - [ ] **Testing:** Have you written and run sufficient tests for validating the changes?
- [ ] **Code Review:** Have you self-reviewed your code and addressed any coding standard issues? - [ ] **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 title:** Ensure the pull request title is labeled properly using one of the following: - [ ] **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 - **BREAKING CHANGE**: Significant changes that may affect compatibility
- **build**: Changes that affect the build system or external dependencies - **build**: Changes that affect the build system or external dependencies
- **ci**: Changes to our continuous integration processes or workflows - **ci**: Changes to our continuous integration processes or workflows
...@@ -26,7 +30,7 @@ ...@@ -26,7 +30,7 @@
### Description ### 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 ### Added
...@@ -62,3 +66,7 @@ ...@@ -62,3 +66,7 @@
- [Insert any additional context, notes, or explanations for the changes] - [Insert any additional context, notes, or explanations for the changes]
- [Reference any related issues, commits, or other relevant information] - [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__/ ...@@ -16,6 +16,10 @@ __pycache__/
# C extensions # C extensions
*.so *.so
# Pyodide distribution
static/pyodide/*
!static/pyodide/pyodide-lock.json
# Distribution / packaging # Distribution / packaging
.Python .Python
build/ build/
......
...@@ -310,3 +310,7 @@ dist ...@@ -310,3 +310,7 @@ dist
# cypress artifacts # cypress artifacts
cypress/videos cypress/videos
cypress/screenshots cypress/screenshots
/static/*
\ No newline at end of file
...@@ -11,6 +11,9 @@ ARG USE_CUDA_VER=cu121 ...@@ -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. # 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_EMBEDDING_MODEL=sentence-transformers/all-MiniLM-L6-v2
ARG USE_RERANKING_MODEL="" ARG USE_RERANKING_MODEL=""
# Override at your own risk - non-root configurations are untested
ARG UID=0
ARG GID=0
######## WebUI frontend ######## ######## WebUI frontend ########
FROM --platform=$BUILDPLATFORM node:21-alpine3.19 as build FROM --platform=$BUILDPLATFORM node:21-alpine3.19 as build
...@@ -32,6 +35,8 @@ ARG USE_OLLAMA ...@@ -32,6 +35,8 @@ ARG USE_OLLAMA
ARG USE_CUDA_VER ARG USE_CUDA_VER
ARG USE_EMBEDDING_MODEL ARG USE_EMBEDDING_MODEL
ARG USE_RERANKING_MODEL ARG USE_RERANKING_MODEL
ARG UID
ARG GID
## Basis ## ## Basis ##
ENV ENV=prod \ ENV ENV=prod \
...@@ -76,9 +81,20 @@ ENV HF_HOME="/app/backend/data/cache/embedding/models" ...@@ -76,9 +81,20 @@ ENV HF_HOME="/app/backend/data/cache/embedding/models"
WORKDIR /app/backend WORKDIR /app/backend
ENV HOME /root 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 mkdir -p $HOME/.cache/chroma
RUN echo -n 00000000-0000-0000-0000-000000000000 > $HOME/.cache/chroma/telemetry_user_id 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 \ RUN if [ "$USE_OLLAMA" = "true" ]; then \
apt-get update && \ apt-get update && \
# Install pandoc and netcat # Install pandoc and netcat
...@@ -102,7 +118,7 @@ RUN if [ "$USE_OLLAMA" = "true" ]; then \ ...@@ -102,7 +118,7 @@ RUN if [ "$USE_OLLAMA" = "true" ]; then \
fi fi
# install python dependencies # install python dependencies
COPY ./backend/requirements.txt ./requirements.txt COPY --chown=$UID:$GID ./backend/requirements.txt ./requirements.txt
RUN pip3 install uv && \ RUN pip3 install uv && \
if [ "$USE_CUDA" = "true" ]; then \ if [ "$USE_CUDA" = "true" ]; then \
...@@ -125,16 +141,17 @@ RUN pip3 install uv && \ ...@@ -125,16 +141,17 @@ RUN pip3 install uv && \
# COPY --from=build /app/onnx /root/.cache/chroma/onnx_models/all-MiniLM-L6-v2/onnx # COPY --from=build /app/onnx /root/.cache/chroma/onnx_models/all-MiniLM-L6-v2/onnx
# copy built frontend files # copy built frontend files
COPY --from=build /app/build /app/build COPY --chown=$UID:$GID --from=build /app/build /app/build
COPY --from=build /app/CHANGELOG.md /app/CHANGELOG.md COPY --chown=$UID:$GID --from=build /app/CHANGELOG.md /app/CHANGELOG.md
COPY --from=build /app/package.json /app/package.json COPY --chown=$UID:$GID --from=build /app/package.json /app/package.json
# copy backend files # copy backend files
COPY ./backend . COPY --chown=$UID:$GID ./backend .
EXPOSE 8080 EXPOSE 8080
HEALTHCHECK CMD curl --silent --fail http://localhost:8080/health | jq -e '.status == true' || exit 1 HEALTHCHECK CMD curl --silent --fail http://localhost:8080/health | jq -e '.status == true' || exit 1
USER $UID:$GID
CMD [ "bash", "start.sh"] CMD [ "bash", "start.sh"]
...@@ -397,7 +397,7 @@ def generate_image( ...@@ -397,7 +397,7 @@ def generate_image(
user=Depends(get_current_user), 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 r = None
try: try:
......
...@@ -75,6 +75,10 @@ with open(LITELLM_CONFIG_DIR, "r") as file: ...@@ -75,6 +75,10 @@ with open(LITELLM_CONFIG_DIR, "r") as file:
litellm_config = yaml.safe_load(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.ENABLE = ENABLE_LITELLM
app.state.CONFIG = litellm_config app.state.CONFIG = litellm_config
app.state.MODEL_CONFIG = MODEL_CONFIG.value.get("litellm", []) app.state.MODEL_CONFIG = MODEL_CONFIG.value.get("litellm", [])
...@@ -152,10 +156,6 @@ async def shutdown_litellm_background(): ...@@ -152,10 +156,6 @@ async def shutdown_litellm_background():
background_process = None background_process = None
app.state.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER
app.state.MODEL_FILTER_LIST = MODEL_FILTER_LIST
@app.get("/") @app.get("/")
async def get_status(): async def get_status():
return {"status": True} return {"status": True}
......
...@@ -65,8 +65,8 @@ app.add_middleware( ...@@ -65,8 +65,8 @@ app.add_middleware(
app.state.config = AppConfig() app.state.config = AppConfig()
app.state.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER app.state.config.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER
app.state.MODEL_FILTER_LIST = MODEL_FILTER_LIST app.state.config.MODEL_FILTER_LIST = MODEL_FILTER_LIST
app.state.MODEL_CONFIG = MODEL_CONFIG.value.get("ollama", []) app.state.MODEL_CONFIG = MODEL_CONFIG.value.get("ollama", [])
app.state.config.OLLAMA_BASE_URLS = OLLAMA_BASE_URLS 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)) ...@@ -126,8 +126,9 @@ async def cancel_ollama_request(request_id: str, user=Depends(get_current_user))
async def fetch_url(url): async def fetch_url(url):
timeout = aiohttp.ClientTimeout(total=5)
try: try:
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.get(url) as response: async with session.get(url) as response:
return await response.json() return await response.json()
except Exception as e: except Exception as e:
...@@ -190,11 +191,12 @@ async def get_ollama_tags( ...@@ -190,11 +191,12 @@ async def get_ollama_tags(
if url_idx == None: if url_idx == None:
models = await get_all_models() models = await get_all_models()
if app.state.ENABLE_MODEL_FILTER: if app.state.config.ENABLE_MODEL_FILTER:
if user.role == "user": if user.role == "user":
models["models"] = list( models["models"] = list(
filter( filter(
lambda model: model["name"] in app.state.MODEL_FILTER_LIST, lambda model: model["name"]
in app.state.config.MODEL_FILTER_LIST,
models["models"], models["models"],
) )
) )
...@@ -1058,11 +1060,12 @@ async def get_openai_models( ...@@ -1058,11 +1060,12 @@ async def get_openai_models(
if url_idx == None: if url_idx == None:
models = await get_all_models() models = await get_all_models()
if app.state.ENABLE_MODEL_FILTER: if app.state.config.ENABLE_MODEL_FILTER:
if user.role == "user": if user.role == "user":
models["models"] = list( models["models"] = list(
filter( filter(
lambda model: model["name"] in app.state.MODEL_FILTER_LIST, lambda model: model["name"]
in app.state.config.MODEL_FILTER_LIST,
models["models"], models["models"],
) )
) )
......
...@@ -21,6 +21,7 @@ from utils.utils import ( ...@@ -21,6 +21,7 @@ from utils.utils import (
) )
from config import ( from config import (
SRC_LOG_LEVELS, SRC_LOG_LEVELS,
ENABLE_OPENAI_API,
OPENAI_API_BASE_URLS, OPENAI_API_BASE_URLS,
OPENAI_API_KEYS, OPENAI_API_KEYS,
CACHE_DIR, CACHE_DIR,
...@@ -47,12 +48,15 @@ app.add_middleware( ...@@ -47,12 +48,15 @@ app.add_middleware(
allow_headers=["*"], allow_headers=["*"],
) )
app.state.config = AppConfig() app.state.config = AppConfig()
app.state.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER app.state.config.ENABLE_MODEL_FILTER = ENABLE_MODEL_FILTER
app.state.MODEL_FILTER_LIST = MODEL_FILTER_LIST app.state.config.MODEL_FILTER_LIST = MODEL_FILTER_LIST
app.state.MODEL_CONFIG = MODEL_CONFIG.value.get("openai", []) 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_BASE_URLS = OPENAI_API_BASE_URLS
app.state.config.OPENAI_API_KEYS = OPENAI_API_KEYS app.state.config.OPENAI_API_KEYS = OPENAI_API_KEYS
...@@ -70,6 +74,21 @@ async def check_url(request: Request, call_next): ...@@ -70,6 +74,21 @@ async def check_url(request: Request, call_next):
return response 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): class UrlsUpdateForm(BaseModel):
urls: List[str] urls: List[str]
...@@ -166,11 +185,15 @@ async def speech(request: Request, user=Depends(get_verified_user)): ...@@ -166,11 +185,15 @@ async def speech(request: Request, user=Depends(get_verified_user)):
async def fetch_url(url, key): async def fetch_url(url, key):
timeout = aiohttp.ClientTimeout(total=5)
try: try:
if key != "":
headers = {"Authorization": f"Bearer {key}"} headers = {"Authorization": f"Bearer {key}"}
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.get(url, headers=headers) as response: async with session.get(url, headers=headers) as response:
return await response.json() return await response.json()
else:
return None
except Exception as e: except Exception as e:
# Handle connection error here # Handle connection error here
log.error(f"Connection error: {e}") log.error(f"Connection error: {e}")
...@@ -202,7 +225,7 @@ async def get_all_models(): ...@@ -202,7 +225,7 @@ async def get_all_models():
if ( if (
len(app.state.config.OPENAI_API_KEYS) == 1 len(app.state.config.OPENAI_API_KEYS) == 1
and app.state.config.OPENAI_API_KEYS[0] == "" and app.state.config.OPENAI_API_KEYS[0] == ""
): ) or not app.state.config.ENABLE_OPENAI_API:
models = {"data": []} models = {"data": []}
else: else:
tasks = [ tasks = [
...@@ -248,11 +271,11 @@ def add_custom_info_to_model(model: dict): ...@@ -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)): async def get_models(url_idx: Optional[int] = None, user=Depends(get_current_user)):
if url_idx == None: if url_idx == None:
models = await get_all_models() models = await get_all_models()
if app.state.ENABLE_MODEL_FILTER: if app.state.config.ENABLE_MODEL_FILTER:
if user.role == "user": if user.role == "user":
models["data"] = list( models["data"] = list(
filter( filter(
lambda model: model["id"] in app.state.MODEL_FILTER_LIST, lambda model: model["id"] in app.state.config.MODEL_FILTER_LIST,
models["data"], models["data"],
) )
) )
......
...@@ -433,12 +433,12 @@ async def update_query_settings( ...@@ -433,12 +433,12 @@ async def update_query_settings(
form_data: QuerySettingsForm, user=Depends(get_admin_user) form_data: QuerySettingsForm, user=Depends(get_admin_user)
): ):
app.state.config.RAG_TEMPLATE = ( 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.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.RELEVANCE_THRESHOLD = form_data.r if form_data.r else 0.0
app.state.config.ENABLE_RAG_HYBRID_SEARCH = ( 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 { return {
"status": True, "status": True,
......
...@@ -11,8 +11,9 @@ import logging ...@@ -11,8 +11,9 @@ import logging
from apps.web.models.users import UserModel, UserUpdateForm, UserRoleUpdateForm, Users from apps.web.models.users import UserModel, UserUpdateForm, UserRoleUpdateForm, Users
from apps.web.models.auths import Auths 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 constants import ERROR_MESSAGES
from config import SRC_LOG_LEVELS from config import SRC_LOG_LEVELS
...@@ -67,6 +68,41 @@ async def update_user_role(form_data: UserRoleUpdateForm, user=Depends(get_admin ...@@ -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 # UpdateUserById
############################ ############################
......
...@@ -417,6 +417,14 @@ OLLAMA_BASE_URLS = PersistentConfig( ...@@ -417,6 +417,14 @@ OLLAMA_BASE_URLS = PersistentConfig(
# 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", "")
......
...@@ -120,6 +120,18 @@ app.state.config.WEBHOOK_URL = WEBHOOK_URL ...@@ -120,6 +120,18 @@ 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
...@@ -280,14 +292,14 @@ class ModelFilterConfigForm(BaseModel): ...@@ -280,14 +292,14 @@ 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.config.ENABLE_MODEL_FILTER, form_data.enabled app.state.config.ENABLE_MODEL_FILTER = form_data.enabled
app.state.config.MODEL_FILTER_LIST, form_data.models app.state.config.MODEL_FILTER_LIST = form_data.models
ollama_app.state.ENABLE_MODEL_FILTER = app.state.config.ENABLE_MODEL_FILTER ollama_app.state.config.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.MODEL_FILTER_LIST = app.state.config.MODEL_FILTER_LIST
openai_app.state.ENABLE_MODEL_FILTER = app.state.config.ENABLE_MODEL_FILTER openai_app.state.config.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.MODEL_FILTER_LIST = app.state.config.MODEL_FILTER_LIST
litellm_app.state.ENABLE_MODEL_FILTER = app.state.config.ENABLE_MODEL_FILTER litellm_app.state.ENABLE_MODEL_FILTER = app.state.config.ENABLE_MODEL_FILTER
litellm_app.state.MODEL_FILTER_LIST = app.state.config.MODEL_FILTER_LIST litellm_app.state.MODEL_FILTER_LIST = app.state.config.MODEL_FILTER_LIST
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
"name": "open-webui", "name": "open-webui",
"version": "0.1.124", "version": "0.1.124",
"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",
...@@ -22,6 +23,7 @@ ...@@ -22,6 +23,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"
...@@ -890,6 +892,19 @@ ...@@ -890,6 +892,19 @@
"url": "https://opencollective.com/popperjs" "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": { "node_modules/@rollup/plugin-commonjs": {
"version": "25.0.7", "version": "25.0.7",
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.7.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.7.tgz",
...@@ -1605,8 +1620,12 @@ ...@@ -1605,8 +1620,12 @@
"node_modules/@ungap/structured-clone": { "node_modules/@ungap/structured-clone": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ=="
"dev": true },
"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": { "node_modules/@vitest/expect": {
"version": "1.6.0", "version": "1.6.0",
...@@ -1713,6 +1732,11 @@ ...@@ -1713,6 +1732,11 @@
"@types/estree": "^1.0.0" "@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": { "node_modules/acorn": {
"version": "8.11.3", "version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
...@@ -2027,6 +2051,11 @@ ...@@ -2027,6 +2051,11 @@
"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",
...@@ -2047,6 +2076,11 @@ ...@@ -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": { "node_modules/bcrypt-pbkdf": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
...@@ -2661,6 +2695,28 @@ ...@@ -2661,6 +2695,28 @@
"@types/estree": "^1.0.0" "@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": { "node_modules/color-convert": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
...@@ -4001,6 +4057,11 @@ ...@@ -4001,6 +4057,11 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/get-func-name": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
...@@ -4328,6 +4389,11 @@ ...@@ -4328,6 +4389,11 @@
"node": ">=12.0.0" "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": { "node_modules/htmlparser2": {
"version": "8.0.2", "version": "8.0.2",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
...@@ -5838,6 +5904,29 @@ ...@@ -5838,6 +5904,29 @@
"pathe": "^1.1.2" "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": { "node_modules/postcss": {
"version": "8.4.38", "version": "8.4.38",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
...@@ -6151,6 +6240,11 @@ ...@@ -6151,6 +6240,11 @@
"integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==", "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==",
"dev": true "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": { "node_modules/psl": {
"version": "1.9.0", "version": "1.9.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
...@@ -6176,6 +6270,18 @@ ...@@ -6176,6 +6270,18 @@
"node": ">=6" "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": { "node_modules/qs": {
"version": "6.10.4", "version": "6.10.4",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.10.4.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.4.tgz",
...@@ -6858,6 +6964,11 @@ ...@@ -6858,6 +6964,11 @@
"integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==",
"dev": true "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": { "node_modules/stream-composer": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/stream-composer/-/stream-composer-1.0.2.tgz", "resolved": "https://registry.npmjs.org/stream-composer/-/stream-composer-1.0.2.tgz",
...@@ -7520,6 +7631,11 @@ ...@@ -7520,6 +7631,11 @@
"node": ">=14.14" "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": { "node_modules/to-regex-range": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
...@@ -7629,6 +7745,11 @@ ...@@ -7629,6 +7745,11 @@
"node": ">= 0.8.0" "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": { "node_modules/type-detect": {
"version": "4.0.8", "version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
...@@ -8883,6 +9004,26 @@ ...@@ -8883,6 +9004,26 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" "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": { "node_modules/xtend": {
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
......
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
"version": "0.1.124", "version": "0.1.124",
"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",
...@@ -16,7 +16,8 @@ ...@@ -16,7 +16,8 @@
"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" "test:frontend": "vitest",
"pyodide:fetch": "node scripts/prepare-pyodide.js"
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/adapter-auto": "^2.0.0", "@sveltejs/adapter-auto": "^2.0.0",
...@@ -47,6 +48,7 @@ ...@@ -47,6 +48,7 @@
}, },
"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",
...@@ -61,6 +63,7 @@ ...@@ -61,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'
];
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 @@ ...@@ -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 { OPENAI_API_BASE_URL } from '$lib/constants'; import { OPENAI_API_BASE_URL } from '$lib/constants';
import { promptTemplate } from '$lib/utils'; 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 = '') => { export const getOpenAIUrls = async (token: string = '') => {
let error = null; let error = null;
......
...@@ -115,6 +115,33 @@ export const getUsers = async (token: string) => { ...@@ -115,6 +115,33 @@ export const getUsers = async (token: string) => {
return res ? res : []; 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) => { export const deleteUserById = async (token: string, userId: string) => {
let error = null; let error = null;
......
...@@ -327,7 +327,6 @@ ...@@ -327,7 +327,6 @@
}; };
onMount(() => { onMount(() => {
console.log(document.getElementById('sidebar'));
window.setTimeout(() => chatTextAreaElement?.focus(), 0); window.setTimeout(() => chatTextAreaElement?.focus(), 0);
const dropZone = document.querySelector('body'); const dropZone = document.querySelector('body');
...@@ -512,6 +511,7 @@ ...@@ -512,6 +511,7 @@
> >
<div class="flex items-center gap-2 text-sm dark:text-gray-500"> <div class="flex items-center gap-2 text-sm dark:text-gray-500">
<img <img
crossorigin="anonymous"
alt="model profile" alt="model profile"
class="size-5 max-w-[28px] object-cover rounded-full" class="size-5 max-w-[28px] object-cover rounded-full"
src={$modelfiles.find((modelfile) => modelfile.tagName === selectedModel.id) src={$modelfiles.find((modelfile) => modelfile.tagName === selectedModel.id)
...@@ -600,7 +600,8 @@ ...@@ -600,7 +600,8 @@
}} }}
/> />
<form <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={() => { on:submit|preventDefault={() => {
submitPrompt(prompt, user); submitPrompt(prompt, user);
}} }}
...@@ -770,7 +771,7 @@ ...@@ -770,7 +771,7 @@
<textarea <textarea
id="chat-textarea" id="chat-textarea"
bind:this={chatTextAreaElement} 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]" : ' pl-4'} rounded-xl resize-none h-[48px]"
placeholder={chatInputPlaceholder !== '' placeholder={chatInputPlaceholder !== ''
......
<script lang="ts"> <script lang="ts">
import { v4 as uuidv4 } from 'uuid'; 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 { tick, getContext } from 'svelte';
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
...@@ -22,8 +22,9 @@ ...@@ -22,8 +22,9 @@
export let continueGeneration: Function; export let continueGeneration: Function;
export let regenerateResponse: Function; export let regenerateResponse: Function;
export let user = $_user;
export let prompt; export let prompt;
export let suggestionPrompts; export let suggestionPrompts = [];
export let processing = ''; export let processing = '';
export let bottomPadding = false; export let bottomPadding = false;
export let autoScroll; export let autoScroll;
...@@ -294,7 +295,7 @@ ...@@ -294,7 +295,7 @@
{#if message.role === 'user'} {#if message.role === 'user'}
<UserMessage <UserMessage
on:delete={() => messageDeleteHandler(message.id)} on:delete={() => messageDeleteHandler(message.id)}
user={$user} {user}
{readOnly} {readOnly}
{message} {message}
isFirstMessage={messageIdx === 0} 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