diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 190270131..5a85d0879 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -4,7 +4,6 @@ about: Create a report to help us improve title: '' labels: '' assignees: '' - --- # Bug Report @@ -31,6 +30,7 @@ assignees: '' ## Reproduction Details **Confirmation:** + - [ ] I have read and followed all the instructions provided in the README.md. - [ ] I have reviewed the troubleshooting.md document. - [ ] I have included the browser console logs. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index bbcbbe7d6..2f28cead0 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -4,7 +4,6 @@ about: Suggest an idea for this project title: '' labels: '' assignees: '' - --- **Is your feature request related to a problem? Please describe.** diff --git a/.github/workflows/format-backend.yaml b/.github/workflows/format-backend.yaml new file mode 100644 index 000000000..9d767fa5b --- /dev/null +++ b/.github/workflows/format-backend.yaml @@ -0,0 +1,27 @@ +name: Python CI +on: + push: + branches: ['main'] + pull_request: +jobs: + build: + name: 'Format Backend' + env: + PUBLIC_API_BASE_URL: '' + runs-on: ubuntu-latest + strategy: + matrix: + node-version: + - latest + steps: + - uses: actions/checkout@v4 + - name: Use Python + uses: actions/setup-python@v4 + - name: Use Bun + uses: oven-sh/setup-bun@v1 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install yapf + - name: Format backend + run: bun run format:backend diff --git a/.github/workflows/format-build-frontend.yaml b/.github/workflows/format-build-frontend.yaml new file mode 100644 index 000000000..3d84d1827 --- /dev/null +++ b/.github/workflows/format-build-frontend.yaml @@ -0,0 +1,22 @@ +name: Bun CI +on: + push: + branches: ['main'] + pull_request: +jobs: + build: + name: 'Format & Build Frontend' + env: + PUBLIC_API_BASE_URL: '' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Use Bun + uses: oven-sh/setup-bun@v1 + - run: bun --version + - name: Install frontend dependencies + run: bun install --frozen-lockfile + - name: Format frontend + run: bun run format + - name: Build frontend + run: bun run build diff --git a/.github/workflows/lint-backend.disabled b/.github/workflows/lint-backend.disabled new file mode 100644 index 000000000..d220031cc --- /dev/null +++ b/.github/workflows/lint-backend.disabled @@ -0,0 +1,27 @@ +name: Python CI +on: + push: + branches: ['main'] + pull_request: +jobs: + build: + name: 'Lint Backend' + env: + PUBLIC_API_BASE_URL: '' + runs-on: ubuntu-latest + strategy: + matrix: + node-version: + - latest + steps: + - uses: actions/checkout@v4 + - name: Use Python + uses: actions/setup-python@v4 + - name: Use Bun + uses: oven-sh/setup-bun@v1 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pylint + - name: Lint backend + run: bun run lint:backend diff --git a/.github/workflows/lint-frontend.disabled b/.github/workflows/lint-frontend.disabled new file mode 100644 index 000000000..2c1cd3c5a --- /dev/null +++ b/.github/workflows/lint-frontend.disabled @@ -0,0 +1,21 @@ +name: Bun CI +on: + push: + branches: ['main'] + pull_request: +jobs: + build: + name: 'Lint Frontend' + env: + PUBLIC_API_BASE_URL: '' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Use Bun + uses: oven-sh/setup-bun@v1 + - run: bun --version + - name: Install frontend dependencies + run: bun install --frozen-lockfile + - run: bun run lint:frontend + - run: bun run lint:types + if: success() || failure() \ No newline at end of file diff --git a/.github/workflows/node.js.yaml b/.github/workflows/node.js.yaml deleted file mode 100644 index 20a04dc05..000000000 --- a/.github/workflows/node.js.yaml +++ /dev/null @@ -1,27 +0,0 @@ -name: Node.js CI -on: - push: - branches: ['main'] - pull_request: -jobs: - build: - name: 'Fmt, Lint, & Build' - env: - PUBLIC_API_BASE_URL: '' - runs-on: ubuntu-latest - strategy: - matrix: - node-version: - - latest - steps: - - uses: actions/checkout@v3 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node-version }} - - run: node --version - - run: npm clean-install - - run: npm run fmt - #- run: npm run lint - #- run: npm run lint:types - - run: npm run build diff --git a/README.md b/README.md index 808740ff8..d18b0653c 100644 --- a/README.md +++ b/README.md @@ -198,9 +198,15 @@ While we strongly recommend using our convenient Docker container installation f The Ollama Web UI consists of two primary components: the frontend and the backend (which serves as a reverse proxy, handling static frontend files, and additional features). Both need to be running concurrently for the development environment. -**Warning: Backend Dependency for Proper Functionality** +> [!IMPORTANT] +> The backend is required for proper functionality -### TL;DR 🚀 +### Requirements 📦 + +- 🐰 [Bun](https://bun.sh) >= 1.0.21 or 🐢 [Node.js](https://nodejs.org/en) >= 20.10 +- 🐍 [Python](https://python.org) >= 3.11 + +### Build and Install 🛠️ Run the following commands to install: @@ -211,10 +217,14 @@ cd ollama-webui/ # Copying required .env file cp -RPp example.env .env -# Building Frontend +# Building Frontend Using Node npm i npm run build +# or Building Frontend Using Bun +# bun install +# bun run build + # Serving Frontend with the Backend cd ./backend pip install -r requirements.txt diff --git a/backend/apps/ollama/main.py b/backend/apps/ollama/main.py index f77f7ea12..9310ea85b 100644 --- a/backend/apps/ollama/main.py +++ b/backend/apps/ollama/main.py @@ -30,7 +30,8 @@ async def get_ollama_api_url(user=Depends(get_current_user)): if user and user.role == "admin": return {"OLLAMA_API_BASE_URL": app.state.OLLAMA_API_BASE_URL} else: - raise HTTPException(status_code=401, detail=ERROR_MESSAGES.ACCESS_PROHIBITED) + raise HTTPException(status_code=401, + detail=ERROR_MESSAGES.ACCESS_PROHIBITED) class UrlUpdateForm(BaseModel): @@ -38,14 +39,14 @@ class UrlUpdateForm(BaseModel): @app.post("/url/update") -async def update_ollama_api_url( - form_data: UrlUpdateForm, user=Depends(get_current_user) -): +async def update_ollama_api_url(form_data: UrlUpdateForm, + user=Depends(get_current_user)): if user and user.role == "admin": app.state.OLLAMA_API_BASE_URL = form_data.url return {"OLLAMA_API_BASE_URL": app.state.OLLAMA_API_BASE_URL} else: - raise HTTPException(status_code=401, detail=ERROR_MESSAGES.ACCESS_PROHIBITED) + raise HTTPException(status_code=401, + detail=ERROR_MESSAGES.ACCESS_PROHIBITED) @app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE"]) @@ -58,11 +59,11 @@ async def proxy(path: str, request: Request, user=Depends(get_current_user)): if user.role in ["user", "admin"]: if path in ["pull", "delete", "push", "copy", "create"]: if user.role != "admin": - raise HTTPException( - status_code=401, detail=ERROR_MESSAGES.ACCESS_PROHIBITED - ) + raise HTTPException(status_code=401, + detail=ERROR_MESSAGES.ACCESS_PROHIBITED) else: - raise HTTPException(status_code=401, detail=ERROR_MESSAGES.ACCESS_PROHIBITED) + raise HTTPException(status_code=401, + detail=ERROR_MESSAGES.ACCESS_PROHIBITED) headers.pop("Host", None) headers.pop("Authorization", None) diff --git a/backend/apps/ollama/old_main.py b/backend/apps/ollama/old_main.py index 2e6800930..f809d4420 100644 --- a/backend/apps/ollama/old_main.py +++ b/backend/apps/ollama/old_main.py @@ -1,11 +1,9 @@ from flask import Flask, request, Response, jsonify from flask_cors import CORS - import requests import json - from apps.web.models.users import Users from constants import ERROR_MESSAGES from utils.utils import decode_token @@ -77,7 +75,9 @@ def update_ollama_api_url(): ) -@app.route("/", defaults={"path": ""}, methods=["GET", "POST", "PUT", "DELETE"]) +@app.route("/", + defaults={"path": ""}, + methods=["GET", "POST", "PUT", "DELETE"]) @app.route("/", methods=["GET", "POST", "PUT", "DELETE"]) def proxy(path): # Combine the base URL of the target server with the requested path @@ -106,13 +106,17 @@ def proxy(path): pass else: return ( - jsonify({"detail": ERROR_MESSAGES.ACCESS_PROHIBITED}), + jsonify({ + "detail": + ERROR_MESSAGES.ACCESS_PROHIBITED + }), 401, ) else: pass else: - return jsonify({"detail": ERROR_MESSAGES.ACCESS_PROHIBITED}), 401 + return jsonify( + {"detail": ERROR_MESSAGES.ACCESS_PROHIBITED}), 401 else: return jsonify({"detail": ERROR_MESSAGES.UNAUTHORIZED}), 401 else: @@ -162,12 +166,10 @@ def proxy(path): print(res) return ( - jsonify( - { - "detail": error_detail, - "message": str(e), - } - ), + jsonify({ + "detail": error_detail, + "message": str(e), + }), 400, ) diff --git a/backend/apps/openai/main.py b/backend/apps/openai/main.py index 03d4621b8..ef9330c55 100644 --- a/backend/apps/openai/main.py +++ b/backend/apps/openai/main.py @@ -37,16 +37,19 @@ async def get_openai_url(user=Depends(get_current_user)): if user and user.role == "admin": return {"OPENAI_API_BASE_URL": app.state.OPENAI_API_BASE_URL} else: - raise HTTPException(status_code=401, detail=ERROR_MESSAGES.ACCESS_PROHIBITED) + raise HTTPException(status_code=401, + detail=ERROR_MESSAGES.ACCESS_PROHIBITED) @app.post("/url/update") -async def update_openai_url(form_data: UrlUpdateForm, user=Depends(get_current_user)): +async def update_openai_url(form_data: UrlUpdateForm, + user=Depends(get_current_user)): if user and user.role == "admin": app.state.OPENAI_API_BASE_URL = form_data.url return {"OPENAI_API_BASE_URL": app.state.OPENAI_API_BASE_URL} else: - raise HTTPException(status_code=401, detail=ERROR_MESSAGES.ACCESS_PROHIBITED) + raise HTTPException(status_code=401, + detail=ERROR_MESSAGES.ACCESS_PROHIBITED) @app.get("/key") @@ -54,16 +57,19 @@ async def get_openai_key(user=Depends(get_current_user)): if user and user.role == "admin": return {"OPENAI_API_KEY": app.state.OPENAI_API_KEY} else: - raise HTTPException(status_code=401, detail=ERROR_MESSAGES.ACCESS_PROHIBITED) + raise HTTPException(status_code=401, + detail=ERROR_MESSAGES.ACCESS_PROHIBITED) @app.post("/key/update") -async def update_openai_key(form_data: KeyUpdateForm, user=Depends(get_current_user)): +async def update_openai_key(form_data: KeyUpdateForm, + user=Depends(get_current_user)): if user and user.role == "admin": app.state.OPENAI_API_KEY = form_data.key return {"OPENAI_API_KEY": app.state.OPENAI_API_KEY} else: - raise HTTPException(status_code=401, detail=ERROR_MESSAGES.ACCESS_PROHIBITED) + raise HTTPException(status_code=401, + detail=ERROR_MESSAGES.ACCESS_PROHIBITED) @app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE"]) @@ -72,9 +78,11 @@ async def proxy(path: str, request: Request, user=Depends(get_current_user)): print(target_url, app.state.OPENAI_API_KEY) if user.role not in ["user", "admin"]: - raise HTTPException(status_code=401, detail=ERROR_MESSAGES.ACCESS_PROHIBITED) + raise HTTPException(status_code=401, + detail=ERROR_MESSAGES.ACCESS_PROHIBITED) if app.state.OPENAI_API_KEY == "": - raise HTTPException(status_code=401, detail=ERROR_MESSAGES.API_KEY_NOT_FOUND) + raise HTTPException(status_code=401, + detail=ERROR_MESSAGES.API_KEY_NOT_FOUND) body = await request.body() # headers = dict(request.headers) @@ -117,8 +125,8 @@ async def proxy(path: str, request: Request, user=Depends(get_current_user)): if "openai" in app.state.OPENAI_API_BASE_URL and path == "models": response_data["data"] = list( - filter(lambda model: "gpt" in model["id"], response_data["data"]) - ) + filter(lambda model: "gpt" in model["id"], + response_data["data"])) return response_data except Exception as e: diff --git a/backend/apps/web/main.py b/backend/apps/web/main.py index 153b5dcbb..0030c25ed 100644 --- a/backend/apps/web/main.py +++ b/backend/apps/web/main.py @@ -22,10 +22,11 @@ app.add_middleware( app.include_router(auths.router, prefix="/auths", tags=["auths"]) app.include_router(users.router, prefix="/users", tags=["users"]) app.include_router(chats.router, prefix="/chats", tags=["chats"]) -app.include_router(modelfiles.router, prefix="/modelfiles", tags=["modelfiles"]) +app.include_router(modelfiles.router, + prefix="/modelfiles", + tags=["modelfiles"]) app.include_router(prompts.router, prefix="/prompts", tags=["prompts"]) - app.include_router(configs.router, prefix="/configs", tags=["configs"]) app.include_router(utils.router, prefix="/utils", tags=["utils"]) diff --git a/backend/apps/web/models/auths.py b/backend/apps/web/models/auths.py index 800750c3d..68b284ea5 100644 --- a/backend/apps/web/models/auths.py +++ b/backend/apps/web/models/auths.py @@ -4,7 +4,6 @@ import time import uuid from peewee import * - from apps.web.models.users import UserModel, Users from utils.utils import ( verify_password, @@ -76,20 +75,26 @@ class SignupForm(BaseModel): class AuthsTable: + def __init__(self, db): self.db = db self.db.create_tables([Auth]) - def insert_new_auth( - self, email: str, password: str, name: str, role: str = "pending" - ) -> Optional[UserModel]: + def insert_new_auth(self, + email: str, + password: str, + name: str, + role: str = "pending") -> Optional[UserModel]: print("insert_new_auth") id = str(uuid.uuid4()) - auth = AuthModel( - **{"id": id, "email": email, "password": password, "active": True} - ) + auth = AuthModel(**{ + "id": id, + "email": email, + "password": password, + "active": True + }) result = Auth.create(**auth.model_dump()) user = Users.insert_new_user(id, name, email, role) @@ -99,7 +104,8 @@ class AuthsTable: else: return None - def authenticate_user(self, email: str, password: str) -> Optional[UserModel]: + def authenticate_user(self, email: str, + password: str) -> Optional[UserModel]: print("authenticate_user", email) try: auth = Auth.get(Auth.email == email, Auth.active == True) @@ -131,7 +137,8 @@ class AuthsTable: if result: # Delete Auth query = Auth.delete().where(Auth.id == id) - query.execute() # Remove the rows, return number of rows removed. + query.execute( + ) # Remove the rows, return number of rows removed. return True else: diff --git a/backend/apps/web/models/chats.py b/backend/apps/web/models/chats.py index ebc17d9a5..bc4659de3 100644 --- a/backend/apps/web/models/chats.py +++ b/backend/apps/web/models/chats.py @@ -3,14 +3,12 @@ from typing import List, Union, Optional from peewee import * from playhouse.shortcuts import model_to_dict - import json import uuid import time from apps.web.internal.db import DB - #################### # Chat DB Schema #################### @@ -62,23 +60,23 @@ class ChatTitleIdResponse(BaseModel): class ChatTable: + def __init__(self, db): self.db = db db.create_tables([Chat]) - def insert_new_chat(self, user_id: str, form_data: ChatForm) -> Optional[ChatModel]: + def insert_new_chat(self, user_id: str, + form_data: ChatForm) -> Optional[ChatModel]: id = str(uuid.uuid4()) chat = ChatModel( **{ "id": id, "user_id": user_id, - "title": form_data.chat["title"] - if "title" in form_data.chat - else "New Chat", + "title": form_data.chat["title"] if "title" in + form_data.chat else "New Chat", "chat": json.dumps(form_data.chat), "timestamp": int(time.time()), - } - ) + }) result = Chat.create(**chat.model_dump()) return chat if result else None @@ -111,27 +109,25 @@ class ChatTable: except: return None - def get_chat_lists_by_user_id( - self, user_id: str, skip: int = 0, limit: int = 50 - ) -> List[ChatModel]: + def get_chat_lists_by_user_id(self, + user_id: str, + skip: int = 0, + limit: int = 50) -> List[ChatModel]: return [ - ChatModel(**model_to_dict(chat)) - for chat in Chat.select() - .where(Chat.user_id == user_id) - .order_by(Chat.timestamp.desc()) + ChatModel(**model_to_dict(chat)) for chat in Chat.select().where( + Chat.user_id == user_id).order_by(Chat.timestamp.desc()) # .limit(limit) # .offset(skip) ] def get_all_chats_by_user_id(self, user_id: str) -> List[ChatModel]: return [ - ChatModel(**model_to_dict(chat)) - for chat in Chat.select() - .where(Chat.user_id == user_id) - .order_by(Chat.timestamp.desc()) + ChatModel(**model_to_dict(chat)) for chat in Chat.select().where( + Chat.user_id == user_id).order_by(Chat.timestamp.desc()) ] - def get_chat_by_id_and_user_id(self, id: str, user_id: str) -> Optional[ChatModel]: + def get_chat_by_id_and_user_id(self, id: str, + user_id: str) -> Optional[ChatModel]: try: chat = Chat.get(Chat.id == id, Chat.user_id == user_id) return ChatModel(**model_to_dict(chat)) @@ -146,7 +142,8 @@ class ChatTable: def delete_chat_by_id_and_user_id(self, id: str, user_id: str) -> bool: try: - query = Chat.delete().where((Chat.id == id) & (Chat.user_id == user_id)) + query = Chat.delete().where((Chat.id == id) + & (Chat.user_id == user_id)) query.execute() # Remove the rows, return number of rows removed. return True diff --git a/backend/apps/web/models/modelfiles.py b/backend/apps/web/models/modelfiles.py index 8231d8dff..51eccfb5e 100644 --- a/backend/apps/web/models/modelfiles.py +++ b/backend/apps/web/models/modelfiles.py @@ -58,13 +58,14 @@ class ModelfileResponse(BaseModel): class ModelfilesTable: + def __init__(self, db): self.db = db self.db.create_tables([Modelfile]) def insert_new_modelfile( - self, user_id: str, form_data: ModelfileForm - ) -> Optional[ModelfileModel]: + self, user_id: str, + form_data: ModelfileForm) -> Optional[ModelfileModel]: if "tagName" in form_data.modelfile: modelfile = ModelfileModel( **{ @@ -72,8 +73,7 @@ class ModelfilesTable: "tag_name": form_data.modelfile["tagName"], "modelfile": json.dumps(form_data.modelfile), "timestamp": int(time.time()), - } - ) + }) try: result = Modelfile.create(**modelfile.model_dump()) @@ -87,28 +87,29 @@ class ModelfilesTable: else: return None - def get_modelfile_by_tag_name(self, tag_name: str) -> Optional[ModelfileModel]: + def get_modelfile_by_tag_name(self, + tag_name: str) -> Optional[ModelfileModel]: try: modelfile = Modelfile.get(Modelfile.tag_name == tag_name) return ModelfileModel(**model_to_dict(modelfile)) except: return None - def get_modelfiles(self, skip: int = 0, limit: int = 50) -> List[ModelfileResponse]: + def get_modelfiles(self, + skip: int = 0, + limit: int = 50) -> List[ModelfileResponse]: return [ ModelfileResponse( **{ **model_to_dict(modelfile), - "modelfile": json.loads(modelfile.modelfile), - } - ) - for modelfile in Modelfile.select() + "modelfile": + json.loads(modelfile.modelfile), + }) for modelfile in Modelfile.select() # .limit(limit).offset(skip) ] def update_modelfile_by_tag_name( - self, tag_name: str, modelfile: dict - ) -> Optional[ModelfileModel]: + self, tag_name: str, modelfile: dict) -> Optional[ModelfileModel]: try: query = Modelfile.update( modelfile=json.dumps(modelfile), diff --git a/backend/apps/web/models/prompts.py b/backend/apps/web/models/prompts.py index bb0710b60..044a3697c 100644 --- a/backend/apps/web/models/prompts.py +++ b/backend/apps/web/models/prompts.py @@ -47,13 +47,13 @@ class PromptForm(BaseModel): class PromptsTable: + def __init__(self, db): self.db = db self.db.create_tables([Prompt]) - def insert_new_prompt( - self, user_id: str, form_data: PromptForm - ) -> Optional[PromptModel]: + def insert_new_prompt(self, user_id: str, + form_data: PromptForm) -> Optional[PromptModel]: prompt = PromptModel( **{ "user_id": user_id, @@ -61,8 +61,7 @@ class PromptsTable: "title": form_data.title, "content": form_data.content, "timestamp": int(time.time()), - } - ) + }) try: result = Prompt.create(**prompt.model_dump()) @@ -82,14 +81,13 @@ class PromptsTable: def get_prompts(self) -> List[PromptModel]: return [ - PromptModel(**model_to_dict(prompt)) - for prompt in Prompt.select() + PromptModel(**model_to_dict(prompt)) for prompt in Prompt.select() # .limit(limit).offset(skip) ] def update_prompt_by_command( - self, command: str, form_data: PromptForm - ) -> Optional[PromptModel]: + self, command: str, + form_data: PromptForm) -> Optional[PromptModel]: try: query = Prompt.update( title=form_data.title, diff --git a/backend/apps/web/models/users.py b/backend/apps/web/models/users.py index b1de7c338..dc4808820 100644 --- a/backend/apps/web/models/users.py +++ b/backend/apps/web/models/users.py @@ -8,7 +8,6 @@ from utils.misc import get_gravatar_url from apps.web.internal.db import DB from apps.web.models.chats import Chats - #################### # User DB Schema #################### @@ -46,13 +45,16 @@ class UserRoleUpdateForm(BaseModel): class UsersTable: + def __init__(self, db): self.db = db self.db.create_tables([User]) - def insert_new_user( - self, id: str, name: str, email: str, role: str = "pending" - ) -> Optional[UserModel]: + def insert_new_user(self, + id: str, + name: str, + email: str, + role: str = "pending") -> Optional[UserModel]: user = UserModel( **{ "id": id, @@ -61,8 +63,7 @@ class UsersTable: "role": role, "profile_image_url": get_gravatar_url(email), "timestamp": int(time.time()), - } - ) + }) result = User.create(**user.model_dump()) if result: return user @@ -92,7 +93,8 @@ class UsersTable: def get_num_users(self) -> Optional[int]: return User.select().count() - def update_user_role_by_id(self, id: str, role: str) -> Optional[UserModel]: + def update_user_role_by_id(self, id: str, + role: str) -> Optional[UserModel]: try: query = User.update(role=role).where(User.id == id) query.execute() @@ -110,7 +112,8 @@ class UsersTable: if result: # Delete User query = User.delete().where(User.id == id) - query.execute() # Remove the rows, return number of rows removed. + query.execute( + ) # Remove the rows, return number of rows removed. return True else: diff --git a/backend/apps/web/routers/auths.py b/backend/apps/web/routers/auths.py index 714982e34..f245601dc 100644 --- a/backend/apps/web/routers/auths.py +++ b/backend/apps/web/routers/auths.py @@ -8,7 +8,6 @@ from pydantic import BaseModel import time import uuid - from apps.web.models.auths import ( SigninForm, SignupForm, @@ -19,12 +18,10 @@ from apps.web.models.auths import ( ) from apps.web.models.users import Users - from utils.utils import get_password_hash, get_current_user, create_token from utils.misc import get_gravatar_url, validate_email_format from constants import ERROR_MESSAGES - router = APIRouter() ############################ @@ -49,9 +46,8 @@ async def get_session_user(user=Depends(get_current_user)): @router.post("/update/password", response_model=bool) -async def update_password( - form_data: UpdatePasswordForm, session_user=Depends(get_current_user) -): +async def update_password(form_data: UpdatePasswordForm, + session_user=Depends(get_current_user)): if session_user: user = Auths.authenticate_user(session_user.email, form_data.password) @@ -101,9 +97,8 @@ async def signup(request: Request, form_data: SignupForm): try: role = "admin" if Users.get_num_users() == 0 else "pending" hashed = get_password_hash(form_data.password) - user = Auths.insert_new_auth( - form_data.email.lower(), hashed, form_data.name, role - ) + user = Auths.insert_new_auth(form_data.email.lower(), + hashed, form_data.name, role) if user: token = create_token(data={"email": user.email}) @@ -120,14 +115,15 @@ async def signup(request: Request, form_data: SignupForm): } else: raise HTTPException( - 500, detail=ERROR_MESSAGES.CREATE_USER_ERROR - ) + 500, detail=ERROR_MESSAGES.CREATE_USER_ERROR) except Exception as err: - raise HTTPException(500, detail=ERROR_MESSAGES.DEFAULT(err)) + raise HTTPException(500, + detail=ERROR_MESSAGES.DEFAULT(err)) else: raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN) else: - raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_EMAIL_FORMAT) + raise HTTPException(400, + detail=ERROR_MESSAGES.INVALID_EMAIL_FORMAT) else: raise HTTPException(400, detail=ERROR_MESSAGES.ACCESS_PROHIBITED) diff --git a/backend/apps/web/routers/chats.py b/backend/apps/web/routers/chats.py index 0eec45536..f061b145b 100644 --- a/backend/apps/web/routers/chats.py +++ b/backend/apps/web/routers/chats.py @@ -17,8 +17,7 @@ from apps.web.models.chats import ( ) from utils.utils import ( - bearer_scheme, -) + bearer_scheme, ) from constants import ERROR_MESSAGES router = APIRouter() @@ -30,8 +29,7 @@ router = APIRouter() @router.get("/", response_model=List[ChatTitleIdResponse]) async def get_user_chats( - user=Depends(get_current_user), skip: int = 0, limit: int = 50 -): + user=Depends(get_current_user), skip: int = 0, limit: int = 50): return Chats.get_chat_lists_by_user_id(user.id, skip, limit) @@ -43,8 +41,9 @@ async def get_user_chats( @router.get("/all", response_model=List[ChatResponse]) async def get_all_user_chats(user=Depends(get_current_user)): return [ - ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)}) - for chat in Chats.get_all_chats_by_user_id(user.id) + ChatResponse(**{ + **chat.model_dump(), "chat": json.loads(chat.chat) + }) for chat in Chats.get_all_chats_by_user_id(user.id) ] @@ -69,11 +68,12 @@ async def get_chat_by_id(id: str, user=Depends(get_current_user)): chat = Chats.get_chat_by_id_and_user_id(id, user.id) if chat: - return ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)}) + return ChatResponse(**{ + **chat.model_dump(), "chat": json.loads(chat.chat) + }) else: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND - ) + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, + detail=ERROR_MESSAGES.NOT_FOUND) ############################ @@ -82,15 +82,17 @@ async def get_chat_by_id(id: str, user=Depends(get_current_user)): @router.post("/{id}", response_model=Optional[ChatResponse]) -async def update_chat_by_id( - id: str, form_data: ChatForm, user=Depends(get_current_user) -): +async def update_chat_by_id(id: str, + form_data: ChatForm, + user=Depends(get_current_user)): chat = Chats.get_chat_by_id_and_user_id(id, user.id) if chat: updated_chat = {**json.loads(chat.chat), **form_data.chat} chat = Chats.update_chat_by_id(id, updated_chat) - return ChatResponse(**{**chat.model_dump(), "chat": json.loads(chat.chat)}) + return ChatResponse(**{ + **chat.model_dump(), "chat": json.loads(chat.chat) + }) else: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, diff --git a/backend/apps/web/routers/configs.py b/backend/apps/web/routers/configs.py index b57fae3d5..4dfe79fd7 100644 --- a/backend/apps/web/routers/configs.py +++ b/backend/apps/web/routers/configs.py @@ -10,7 +10,6 @@ import uuid from apps.web.models.users import Users - from utils.utils import get_password_hash, get_current_user, create_token from utils.misc import get_gravatar_url, validate_email_format from constants import ERROR_MESSAGES @@ -28,9 +27,9 @@ class SetDefaultModelsForm(BaseModel): @router.post("/default/models", response_model=str) -async def set_global_default_models( - request: Request, form_data: SetDefaultModelsForm, user=Depends(get_current_user) -): +async def set_global_default_models(request: Request, + form_data: SetDefaultModelsForm, + user=Depends(get_current_user)): if user.role == "admin": request.app.state.DEFAULT_MODELS = form_data.models return request.app.state.DEFAULT_MODELS diff --git a/backend/apps/web/routers/modelfiles.py b/backend/apps/web/routers/modelfiles.py index 841d534d9..0af9ca0f5 100644 --- a/backend/apps/web/routers/modelfiles.py +++ b/backend/apps/web/routers/modelfiles.py @@ -24,7 +24,9 @@ router = APIRouter() @router.get("/", response_model=List[ModelfileResponse]) -async def get_modelfiles(skip: int = 0, limit: int = 50, user=Depends(get_current_user)): +async def get_modelfiles(skip: int = 0, + limit: int = 50, + user=Depends(get_current_user)): return Modelfiles.get_modelfiles(skip, limit) @@ -34,9 +36,8 @@ async def get_modelfiles(skip: int = 0, limit: int = 50, user=Depends(get_curren @router.post("/create", response_model=Optional[ModelfileResponse]) -async def create_new_modelfile( - form_data: ModelfileForm, user=Depends(get_current_user) -): +async def create_new_modelfile(form_data: ModelfileForm, + user=Depends(get_current_user)): if user.role != "admin": raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, @@ -49,9 +50,9 @@ async def create_new_modelfile( return ModelfileResponse( **{ **modelfile.model_dump(), - "modelfile": json.loads(modelfile.modelfile), - } - ) + "modelfile": + json.loads(modelfile.modelfile), + }) else: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, @@ -65,16 +66,17 @@ async def create_new_modelfile( @router.post("/", response_model=Optional[ModelfileResponse]) -async def get_modelfile_by_tag_name(form_data: ModelfileTagNameForm, user=Depends(get_current_user)): +async def get_modelfile_by_tag_name(form_data: ModelfileTagNameForm, + user=Depends(get_current_user)): modelfile = Modelfiles.get_modelfile_by_tag_name(form_data.tag_name) if modelfile: return ModelfileResponse( **{ **modelfile.model_dump(), - "modelfile": json.loads(modelfile.modelfile), - } - ) + "modelfile": + json.loads(modelfile.modelfile), + }) else: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, @@ -88,9 +90,8 @@ async def get_modelfile_by_tag_name(form_data: ModelfileTagNameForm, user=Depend @router.post("/update", response_model=Optional[ModelfileResponse]) -async def update_modelfile_by_tag_name( - form_data: ModelfileUpdateForm, user=Depends(get_current_user) -): +async def update_modelfile_by_tag_name(form_data: ModelfileUpdateForm, + user=Depends(get_current_user)): if user.role != "admin": raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, @@ -104,15 +105,14 @@ async def update_modelfile_by_tag_name( } modelfile = Modelfiles.update_modelfile_by_tag_name( - form_data.tag_name, updated_modelfile - ) + form_data.tag_name, updated_modelfile) return ModelfileResponse( **{ **modelfile.model_dump(), - "modelfile": json.loads(modelfile.modelfile), - } - ) + "modelfile": + json.loads(modelfile.modelfile), + }) else: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, @@ -126,9 +126,8 @@ async def update_modelfile_by_tag_name( @router.delete("/delete", response_model=bool) -async def delete_modelfile_by_tag_name( - form_data: ModelfileTagNameForm, user=Depends(get_current_user) -): +async def delete_modelfile_by_tag_name(form_data: ModelfileTagNameForm, + user=Depends(get_current_user)): if user.role != "admin": raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, diff --git a/backend/apps/web/routers/prompts.py b/backend/apps/web/routers/prompts.py index 5a002c941..e812555d0 100644 --- a/backend/apps/web/routers/prompts.py +++ b/backend/apps/web/routers/prompts.py @@ -6,7 +6,6 @@ from fastapi import APIRouter from pydantic import BaseModel import json - from apps.web.models.prompts import Prompts, PromptForm, PromptModel from utils.utils import get_current_user @@ -30,7 +29,8 @@ async def get_prompts(user=Depends(get_current_user)): @router.post("/create", response_model=Optional[PromptModel]) -async def create_new_prompt(form_data: PromptForm, user=Depends(get_current_user)): +async def create_new_prompt(form_data: PromptForm, + user=Depends(get_current_user)): if user.role != "admin": raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, @@ -79,9 +79,9 @@ async def get_prompt_by_command(command: str, user=Depends(get_current_user)): @router.post("/{command}/update", response_model=Optional[PromptModel]) -async def update_prompt_by_command( - command: str, form_data: PromptForm, user=Depends(get_current_user) -): +async def update_prompt_by_command(command: str, + form_data: PromptForm, + user=Depends(get_current_user)): if user.role != "admin": raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, @@ -104,7 +104,8 @@ async def update_prompt_by_command( @router.delete("/{command}/delete", response_model=bool) -async def delete_prompt_by_command(command: str, user=Depends(get_current_user)): +async def delete_prompt_by_command(command: str, + user=Depends(get_current_user)): if user.role != "admin": raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, diff --git a/backend/apps/web/routers/users.py b/backend/apps/web/routers/users.py index b3b552682..3281ac65e 100644 --- a/backend/apps/web/routers/users.py +++ b/backend/apps/web/routers/users.py @@ -11,11 +11,9 @@ import uuid from apps.web.models.users import UserModel, UserRoleUpdateForm, Users from apps.web.models.auths import Auths - from utils.utils import get_current_user from constants import ERROR_MESSAGES - router = APIRouter() ############################ @@ -24,7 +22,9 @@ router = APIRouter() @router.get("/", response_model=List[UserModel]) -async def get_users(skip: int = 0, limit: int = 50, user=Depends(get_current_user)): +async def get_users(skip: int = 0, + limit: int = 50, + user=Depends(get_current_user)): if user.role != "admin": raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, @@ -39,9 +39,8 @@ async def get_users(skip: int = 0, limit: int = 50, user=Depends(get_current_use @router.post("/update/role", response_model=Optional[UserModel]) -async def update_user_role( - form_data: UserRoleUpdateForm, user=Depends(get_current_user) -): +async def update_user_role(form_data: UserRoleUpdateForm, + user=Depends(get_current_user)): if user.role != "admin": raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, diff --git a/backend/apps/web/routers/utils.py b/backend/apps/web/routers/utils.py index e9986bab4..588ef5a63 100644 --- a/backend/apps/web/routers/utils.py +++ b/backend/apps/web/routers/utils.py @@ -9,12 +9,10 @@ import os import aiohttp import json - from utils.misc import calculate_sha256 from config import OLLAMA_API_BASE_URL - router = APIRouter() @@ -42,7 +40,10 @@ def parse_huggingface_url(hf_url): return None -async def download_file_stream(url, file_path, file_name, chunk_size=1024 * 1024): +async def download_file_stream(url, + file_path, + file_name, + chunk_size=1024 * 1024): done = False if os.path.exists(file_path): @@ -56,7 +57,8 @@ async def download_file_stream(url, file_path, file_name, chunk_size=1024 * 1024 async with aiohttp.ClientSession(timeout=timeout) as session: async with session.get(url, headers=headers) as response: - total_size = int(response.headers.get("content-length", 0)) + current_size + total_size = int(response.headers.get("content-length", + 0)) + current_size with open(file_path, "ab+") as file: async for data in response.content.iter_chunked(chunk_size): @@ -89,9 +91,7 @@ async def download_file_stream(url, file_path, file_name, chunk_size=1024 * 1024 @router.get("/download") -async def download( - url: str, -): +async def download(url: str, ): # url = "https://huggingface.co/TheBloke/stablelm-zephyr-3b-GGUF/resolve/main/stablelm-zephyr-3b.Q2_K.gguf" file_name = parse_huggingface_url(url) @@ -161,4 +161,5 @@ async def upload(file: UploadFile = File(...)): res = {"error": str(e)} yield f"data: {json.dumps(res)}\n\n" - return StreamingResponse(file_write_stream(), media_type="text/event-stream") + return StreamingResponse(file_write_stream(), + media_type="text/event-stream") diff --git a/backend/config.py b/backend/config.py index 16e7eeb2f..4c518d139 100644 --- a/backend/config.py +++ b/backend/config.py @@ -19,15 +19,13 @@ ENV = os.environ.get("ENV", "dev") # OLLAMA_API_BASE_URL #################################### -OLLAMA_API_BASE_URL = os.environ.get( - "OLLAMA_API_BASE_URL", "http://localhost:11434/api" -) +OLLAMA_API_BASE_URL = os.environ.get("OLLAMA_API_BASE_URL", + "http://localhost:11434/api") if ENV == "prod": if OLLAMA_API_BASE_URL == "/ollama/api": OLLAMA_API_BASE_URL = "http://host.docker.internal:11434/api" - #################################### # OPENAI_API #################################### diff --git a/backend/constants.py b/backend/constants.py index e51ecddab..c3fd0dc5f 100644 --- a/backend/constants.py +++ b/backend/constants.py @@ -6,6 +6,7 @@ class MESSAGES(str, Enum): class ERROR_MESSAGES(str, Enum): + def __str__(self) -> str: return super().__str__() @@ -29,8 +30,7 @@ class ERROR_MESSAGES(str, Enum): UNAUTHORIZED = "401 Unauthorized" ACCESS_PROHIBITED = "You do not have permission to access this resource. Please contact your administrator for assistance." ACTION_PROHIBITED = ( - "The requested action has been restricted as a security measure." - ) + "The requested action has been restricted as a security measure.") NOT_FOUND = "We could not find what you're looking for :/" USER_NOT_FOUND = "We could not find what you're looking for :/" API_KEY_NOT_FOUND = "Oops! It looks like there's a hiccup. The API key is missing. Please make sure to provide a valid API key to access this feature." diff --git a/backend/main.py b/backend/main.py index a97e6e85a..0315e5f51 100644 --- a/backend/main.py +++ b/backend/main.py @@ -14,6 +14,7 @@ import time class SPAStaticFiles(StaticFiles): + async def get_response(self, path: str, scope): try: return await super().get_response(path, scope) @@ -51,4 +52,6 @@ app.mount("/api/v1", webui_app) app.mount("/ollama/api", ollama_app) app.mount("/openai/api", openai_app) -app.mount("/", SPAStaticFiles(directory="../build", html=True), name="spa-static-files") +app.mount("/", + SPAStaticFiles(directory="../build", html=True), + name="spa-static-files") diff --git a/backend/utils/utils.py b/backend/utils/utils.py index f98644f58..ad77e0c7a 100644 --- a/backend/utils/utils.py +++ b/backend/utils/utils.py @@ -23,16 +23,16 @@ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") def verify_password(plain_password, hashed_password): - return ( - pwd_context.verify(plain_password, hashed_password) if hashed_password else None - ) + return (pwd_context.verify(plain_password, hashed_password) + if hashed_password else None) def get_password_hash(password): return pwd_context.hash(password) -def create_token(data: dict, expires_delta: Union[timedelta, None] = None) -> str: +def create_token(data: dict, + expires_delta: Union[timedelta, None] = None) -> str: payload = data.copy() if expires_delta: @@ -45,17 +45,20 @@ def create_token(data: dict, expires_delta: Union[timedelta, None] = None) -> st def decode_token(token: str) -> Optional[dict]: try: - decoded = jwt.decode(token, JWT_SECRET_KEY, options={"verify_signature": False}) + decoded = jwt.decode(token, + JWT_SECRET_KEY, + options={"verify_signature": False}) return decoded except Exception as e: return None def extract_token_from_auth_header(auth_header: str): - return auth_header[len("Bearer ") :] + return auth_header[len("Bearer "):] -def get_current_user(auth_token: HTTPAuthorizationCredentials = Depends(HTTPBearer())): +def get_current_user(auth_token: HTTPAuthorizationCredentials = Depends( + HTTPBearer())): data = decode_token(auth_token.credentials) if data != None and "email" in data: user = Users.get_user_by_email(data["email"]) diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 000000000..cd3b910aa Binary files /dev/null and b/bun.lockb differ diff --git a/docker-compose.data.yaml b/docker-compose.data.yaml index 57d6fc372..a8f9f77b0 100644 --- a/docker-compose.data.yaml +++ b/docker-compose.data.yaml @@ -3,4 +3,4 @@ version: '3.8' services: ollama: volumes: - - ${OLLAMA_DATA_DIR-./ollama-data}:/root/.ollama \ No newline at end of file + - ${OLLAMA_DATA_DIR-./ollama-data}:/root/.ollama diff --git a/docker-compose.yaml b/docker-compose.yaml index 47263b0ab..7cd1bde0c 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -25,7 +25,7 @@ services: ports: - ${OLLAMA_WEBUI_PORT-3000}:8080 environment: - - "OLLAMA_API_BASE_URL=http://ollama:11434/api" + - 'OLLAMA_API_BASE_URL=http://ollama:11434/api' extra_hosts: - host.docker.internal:host-gateway restart: unless-stopped diff --git a/package-lock.json b/package-lock.json index af8790a04..166f5ce8c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,12 +22,13 @@ "devDependencies": { "@sveltejs/adapter-auto": "^2.0.0", "@sveltejs/adapter-static": "^2.0.3", - "@sveltejs/kit": "^1.20.4", + "@sveltejs/kit": "^1.30.0", "@tailwindcss/typography": "^0.5.10", - "@typescript-eslint/eslint-plugin": "^6.0.0", - "@typescript-eslint/parser": "^6.0.0", + "@types/bun": "latest", + "@typescript-eslint/eslint-plugin": "^6.17.0", + "@typescript-eslint/parser": "^6.17.0", "autoprefixer": "^10.4.16", - "eslint": "^8.28.0", + "eslint": "^8.56.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-svelte": "^2.30.0", "postcss": "^8.4.31", @@ -429,9 +430,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -452,9 +453,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz", - "integrity": "sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -469,12 +470,12 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", + "@humanwhocodes/object-schema": "^2.0.1", "debug": "^4.1.1", "minimatch": "^3.0.5" }, @@ -496,9 +497,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, "node_modules/@jridgewell/gen-mapping": { @@ -783,12 +784,12 @@ } }, "node_modules/@sveltejs/kit": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.26.0.tgz", - "integrity": "sha512-CV/AlTziC05yrz7UjVqEd0pH6+2dnrbmcnHGr2d3jXtmOgzNnlDkXtX8g3BfJ6nntsPD+0jtS2PzhvRHblRz4A==", + "version": "1.30.3", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.30.3.tgz", + "integrity": "sha512-0DzVXfU4h+tChFvoc8C61IqErCyskD4ydSIDjpKS2lYlEzIYrtYrY7juSqACFxqcvZAnOEXvSY+zZ8br0+ZMMg==", "hasInstallScript": true, "dependencies": { - "@sveltejs/vite-plugin-svelte": "^2.4.1", + "@sveltejs/vite-plugin-svelte": "^2.5.0", "@types/cookie": "^0.5.1", "cookie": "^0.5.0", "devalue": "^4.3.1", @@ -809,14 +810,14 @@ "node": "^16.14 || >=18" }, "peerDependencies": { - "svelte": "^3.54.0 || ^4.0.0-next.0", + "svelte": "^3.54.0 || ^4.0.0-next.0 || ^5.0.0-next.0", "vite": "^4.0.0" } }, "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.4.6.tgz", - "integrity": "sha512-zO79p0+DZnXPnF0ltIigWDx/ux7Ni+HRaFOw720Qeivc1azFUrJxTl0OryXVibYNx1hCboGia1NRV3x8RNv4cA==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.5.3.tgz", + "integrity": "sha512-erhNtXxE5/6xGZz/M9eXsmI7Pxa6MS7jyTy06zN3Ck++ldrppOnOlJwHHTsMC7DHDQdgUp4NAc4cDNQ9eGdB/w==", "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^1.0.4", "debug": "^4.3.4", @@ -830,7 +831,7 @@ "node": "^14.18.0 || >= 16" }, "peerDependencies": { - "svelte": "^3.54.0 || ^4.0.0", + "svelte": "^3.54.0 || ^4.0.0 || ^5.0.0-next.0", "vite": "^4.0.0" } }, @@ -878,6 +879,15 @@ "node": ">=4" } }, + "node_modules/@types/bun": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.0.0.tgz", + "integrity": "sha512-TPI/aImv/fSo0SWlt29wq0tWRqQOWsC4FOXYeUK0Ni6tAS+FqJZ2p7QCGY4hmHaHQeE2KhKJ6Qn9k3kvFfXD3Q==", + "dev": true, + "dependencies": { + "bun-types": "1.0.18" + } + }, "node_modules/@types/cookie": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.2.tgz", @@ -889,9 +899,9 @@ "integrity": "sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==" }, "node_modules/@types/json-schema": { - "version": "7.0.13", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", - "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, "node_modules/@types/pug": { @@ -906,22 +916,22 @@ "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==" }, "node_modules/@types/semver": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz", - "integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==", + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.4.tgz", - "integrity": "sha512-DAbgDXwtX+pDkAHwiGhqP3zWUGpW49B7eqmgpPtg+BKJXwdct79ut9+ifqOFPJGClGKSHXn2PTBatCnldJRUoA==", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.17.0.tgz", + "integrity": "sha512-Vih/4xLXmY7V490dGwBQJTpIZxH4ZFH6eCVmQ4RFkB+wmaCTDAx4dtgoWwMNGKLkqRY1L6rPqzEbjorRnDo4rQ==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.7.4", - "@typescript-eslint/type-utils": "6.7.4", - "@typescript-eslint/utils": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4", + "@typescript-eslint/scope-manager": "6.17.0", + "@typescript-eslint/type-utils": "6.17.0", + "@typescript-eslint/utils": "6.17.0", + "@typescript-eslint/visitor-keys": "6.17.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -947,15 +957,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.4.tgz", - "integrity": "sha512-I5zVZFY+cw4IMZUeNCU7Sh2PO5O57F7Lr0uyhgCJmhN/BuTlnc55KxPonR4+EM3GBdfiCyGZye6DgMjtubQkmA==", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.17.0.tgz", + "integrity": "sha512-C4bBaX2orvhK+LlwrY8oWGmSl4WolCfYm513gEccdWZj0CwGadbIADb0FtVEcI+WzUyjyoBj2JRP8g25E6IB8A==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.7.4", - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/typescript-estree": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4", + "@typescript-eslint/scope-manager": "6.17.0", + "@typescript-eslint/types": "6.17.0", + "@typescript-eslint/typescript-estree": "6.17.0", + "@typescript-eslint/visitor-keys": "6.17.0", "debug": "^4.3.4" }, "engines": { @@ -975,13 +985,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.4.tgz", - "integrity": "sha512-SdGqSLUPTXAXi7c3Ob7peAGVnmMoGzZ361VswK2Mqf8UOYcODiYvs8rs5ILqEdfvX1lE7wEZbLyELCW+Yrql1A==", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.17.0.tgz", + "integrity": "sha512-RX7a8lwgOi7am0k17NUO0+ZmMOX4PpjLtLRgLmT1d3lBYdWH4ssBUbwdmc5pdRX8rXon8v9x8vaoOSpkHfcXGA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4" + "@typescript-eslint/types": "6.17.0", + "@typescript-eslint/visitor-keys": "6.17.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -992,13 +1002,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.7.4.tgz", - "integrity": "sha512-n+g3zi1QzpcAdHFP9KQF+rEFxMb2KxtnJGID3teA/nxKHOVi3ylKovaqEzGBbVY2pBttU6z85gp0D00ufLzViQ==", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.17.0.tgz", + "integrity": "sha512-hDXcWmnbtn4P2B37ka3nil3yi3VCQO2QEB9gBiHJmQp5wmyQWqnjA85+ZcE8c4FqnaB6lBwMrPkgd4aBYz3iNg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.7.4", - "@typescript-eslint/utils": "6.7.4", + "@typescript-eslint/typescript-estree": "6.17.0", + "@typescript-eslint/utils": "6.17.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -1019,9 +1029,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.4.tgz", - "integrity": "sha512-o9XWK2FLW6eSS/0r/tgjAGsYasLAnOWg7hvZ/dGYSSNjCh+49k5ocPN8OmG5aZcSJ8pclSOyVKP2x03Sj+RrCA==", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.17.0.tgz", + "integrity": "sha512-qRKs9tvc3a4RBcL/9PXtKSehI/q8wuU9xYJxe97WFxnzH8NWWtcW3ffNS+EWg8uPvIerhjsEZ+rHtDqOCiH57A==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -1032,16 +1042,17 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.4.tgz", - "integrity": "sha512-ty8b5qHKatlNYd9vmpHooQz3Vki3gG+3PchmtsA4TgrZBKWHNjWfkQid7K7xQogBqqc7/BhGazxMD5vr6Ha+iQ==", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.17.0.tgz", + "integrity": "sha512-gVQe+SLdNPfjlJn5VNGhlOhrXz4cajwFd5kAgWtZ9dCZf4XJf8xmgCTLIqec7aha3JwgLI2CK6GY1043FRxZwg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4", + "@typescript-eslint/types": "6.17.0", + "@typescript-eslint/visitor-keys": "6.17.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", + "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" }, @@ -1058,18 +1069,42 @@ } } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/utils": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.7.4.tgz", - "integrity": "sha512-PRQAs+HUn85Qdk+khAxsVV+oULy3VkbH3hQ8hxLRJXWBEd7iI+GbQxH5SEUSH7kbEoTp6oT1bOwyga24ELALTA==", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.17.0.tgz", + "integrity": "sha512-LofsSPjN/ITNkzV47hxas2JCsNCEnGhVvocfyOcLzT9c/tSZE7SfhS/iWtzP1lKNOEfLhRTZz6xqI8N2RzweSQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.7.4", - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/typescript-estree": "6.7.4", + "@typescript-eslint/scope-manager": "6.17.0", + "@typescript-eslint/types": "6.17.0", + "@typescript-eslint/typescript-estree": "6.17.0", "semver": "^7.5.4" }, "engines": { @@ -1084,12 +1119,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.4.tgz", - "integrity": "sha512-pOW37DUhlTZbvph50x5zZCkFn3xzwkGtNoJHzIM3svpiSkJzwOYr/kVBaXmf+RAQiUDs1AHEZVNPg6UJCJpwRA==", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.17.0.tgz", + "integrity": "sha512-H6VwB/k3IuIeQOyYczyyKN8wH6ed8EwliaYHLxOIhyF0dYEIsN8+Bk3GE19qafeMKyZJJHP8+O1HiFhFLUNKSg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.4", + "@typescript-eslint/types": "6.17.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -1100,6 +1135,12 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "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 + }, "node_modules/acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", @@ -1341,6 +1382,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bun-types": { + "version": "1.0.18", + "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.0.18.tgz", + "integrity": "sha512-1XZ7AxOF8oO8FZtw1xj006JAKxEjulK3dUhsktZVN95vXBlsf4NIjQxfistVdpt24v3H2I9BwHp+UU+gXSSpAw==", + "dev": true + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1688,18 +1735,19 @@ } }, "node_modules/eslint": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.51.0.tgz", - "integrity": "sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.51.0", - "@humanwhocodes/config-array": "^0.11.11", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.56.0", + "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -2071,9 +2119,9 @@ } }, "node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -3025,9 +3073,9 @@ } }, "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "engines": { "node": ">=6" @@ -3886,11 +3934,11 @@ } }, "node_modules/vitefu": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.4.tgz", - "integrity": "sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz", + "integrity": "sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==", "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0" + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" }, "peerDependenciesMeta": { "vite": { @@ -4116,9 +4164,9 @@ "dev": true }, "@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "requires": { "ajv": "^6.12.4", @@ -4133,9 +4181,9 @@ } }, "@eslint/js": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz", - "integrity": "sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", "dev": true }, "@fastify/busboy": { @@ -4144,12 +4192,12 @@ "integrity": "sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ==" }, "@humanwhocodes/config-array": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "dev": true, "requires": { - "@humanwhocodes/object-schema": "^1.2.1", + "@humanwhocodes/object-schema": "^2.0.1", "debug": "^4.1.1", "minimatch": "^3.0.5" } @@ -4161,9 +4209,9 @@ "dev": true }, "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, "@jridgewell/gen-mapping": { @@ -4366,11 +4414,11 @@ "requires": {} }, "@sveltejs/kit": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.26.0.tgz", - "integrity": "sha512-CV/AlTziC05yrz7UjVqEd0pH6+2dnrbmcnHGr2d3jXtmOgzNnlDkXtX8g3BfJ6nntsPD+0jtS2PzhvRHblRz4A==", + "version": "1.30.3", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.30.3.tgz", + "integrity": "sha512-0DzVXfU4h+tChFvoc8C61IqErCyskD4ydSIDjpKS2lYlEzIYrtYrY7juSqACFxqcvZAnOEXvSY+zZ8br0+ZMMg==", "requires": { - "@sveltejs/vite-plugin-svelte": "^2.4.1", + "@sveltejs/vite-plugin-svelte": "^2.5.0", "@types/cookie": "^0.5.1", "cookie": "^0.5.0", "devalue": "^4.3.1", @@ -4386,9 +4434,9 @@ } }, "@sveltejs/vite-plugin-svelte": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.4.6.tgz", - "integrity": "sha512-zO79p0+DZnXPnF0ltIigWDx/ux7Ni+HRaFOw720Qeivc1azFUrJxTl0OryXVibYNx1hCboGia1NRV3x8RNv4cA==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.5.3.tgz", + "integrity": "sha512-erhNtXxE5/6xGZz/M9eXsmI7Pxa6MS7jyTy06zN3Ck++ldrppOnOlJwHHTsMC7DHDQdgUp4NAc4cDNQ9eGdB/w==", "requires": { "@sveltejs/vite-plugin-svelte-inspector": "^1.0.4", "debug": "^4.3.4", @@ -4431,6 +4479,15 @@ } } }, + "@types/bun": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.0.0.tgz", + "integrity": "sha512-TPI/aImv/fSo0SWlt29wq0tWRqQOWsC4FOXYeUK0Ni6tAS+FqJZ2p7QCGY4hmHaHQeE2KhKJ6Qn9k3kvFfXD3Q==", + "dev": true, + "requires": { + "bun-types": "1.0.18" + } + }, "@types/cookie": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.2.tgz", @@ -4442,9 +4499,9 @@ "integrity": "sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==" }, "@types/json-schema": { - "version": "7.0.13", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", - "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, "@types/pug": { @@ -4459,22 +4516,22 @@ "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==" }, "@types/semver": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz", - "integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==", + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.4.tgz", - "integrity": "sha512-DAbgDXwtX+pDkAHwiGhqP3zWUGpW49B7eqmgpPtg+BKJXwdct79ut9+ifqOFPJGClGKSHXn2PTBatCnldJRUoA==", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.17.0.tgz", + "integrity": "sha512-Vih/4xLXmY7V490dGwBQJTpIZxH4ZFH6eCVmQ4RFkB+wmaCTDAx4dtgoWwMNGKLkqRY1L6rPqzEbjorRnDo4rQ==", "dev": true, "requires": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.7.4", - "@typescript-eslint/type-utils": "6.7.4", - "@typescript-eslint/utils": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4", + "@typescript-eslint/scope-manager": "6.17.0", + "@typescript-eslint/type-utils": "6.17.0", + "@typescript-eslint/utils": "6.17.0", + "@typescript-eslint/visitor-keys": "6.17.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -4484,86 +4541,113 @@ } }, "@typescript-eslint/parser": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.4.tgz", - "integrity": "sha512-I5zVZFY+cw4IMZUeNCU7Sh2PO5O57F7Lr0uyhgCJmhN/BuTlnc55KxPonR4+EM3GBdfiCyGZye6DgMjtubQkmA==", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.17.0.tgz", + "integrity": "sha512-C4bBaX2orvhK+LlwrY8oWGmSl4WolCfYm513gEccdWZj0CwGadbIADb0FtVEcI+WzUyjyoBj2JRP8g25E6IB8A==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "6.7.4", - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/typescript-estree": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4", + "@typescript-eslint/scope-manager": "6.17.0", + "@typescript-eslint/types": "6.17.0", + "@typescript-eslint/typescript-estree": "6.17.0", + "@typescript-eslint/visitor-keys": "6.17.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.4.tgz", - "integrity": "sha512-SdGqSLUPTXAXi7c3Ob7peAGVnmMoGzZ361VswK2Mqf8UOYcODiYvs8rs5ILqEdfvX1lE7wEZbLyELCW+Yrql1A==", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.17.0.tgz", + "integrity": "sha512-RX7a8lwgOi7am0k17NUO0+ZmMOX4PpjLtLRgLmT1d3lBYdWH4ssBUbwdmc5pdRX8rXon8v9x8vaoOSpkHfcXGA==", "dev": true, "requires": { - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4" + "@typescript-eslint/types": "6.17.0", + "@typescript-eslint/visitor-keys": "6.17.0" } }, "@typescript-eslint/type-utils": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.7.4.tgz", - "integrity": "sha512-n+g3zi1QzpcAdHFP9KQF+rEFxMb2KxtnJGID3teA/nxKHOVi3ylKovaqEzGBbVY2pBttU6z85gp0D00ufLzViQ==", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.17.0.tgz", + "integrity": "sha512-hDXcWmnbtn4P2B37ka3nil3yi3VCQO2QEB9gBiHJmQp5wmyQWqnjA85+ZcE8c4FqnaB6lBwMrPkgd4aBYz3iNg==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "6.7.4", - "@typescript-eslint/utils": "6.7.4", + "@typescript-eslint/typescript-estree": "6.17.0", + "@typescript-eslint/utils": "6.17.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" } }, "@typescript-eslint/types": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.4.tgz", - "integrity": "sha512-o9XWK2FLW6eSS/0r/tgjAGsYasLAnOWg7hvZ/dGYSSNjCh+49k5ocPN8OmG5aZcSJ8pclSOyVKP2x03Sj+RrCA==", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.17.0.tgz", + "integrity": "sha512-qRKs9tvc3a4RBcL/9PXtKSehI/q8wuU9xYJxe97WFxnzH8NWWtcW3ffNS+EWg8uPvIerhjsEZ+rHtDqOCiH57A==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.4.tgz", - "integrity": "sha512-ty8b5qHKatlNYd9vmpHooQz3Vki3gG+3PchmtsA4TgrZBKWHNjWfkQid7K7xQogBqqc7/BhGazxMD5vr6Ha+iQ==", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.17.0.tgz", + "integrity": "sha512-gVQe+SLdNPfjlJn5VNGhlOhrXz4cajwFd5kAgWtZ9dCZf4XJf8xmgCTLIqec7aha3JwgLI2CK6GY1043FRxZwg==", "dev": true, "requires": { - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4", + "@typescript-eslint/types": "6.17.0", + "@typescript-eslint/visitor-keys": "6.17.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", + "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } } }, "@typescript-eslint/utils": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.7.4.tgz", - "integrity": "sha512-PRQAs+HUn85Qdk+khAxsVV+oULy3VkbH3hQ8hxLRJXWBEd7iI+GbQxH5SEUSH7kbEoTp6oT1bOwyga24ELALTA==", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.17.0.tgz", + "integrity": "sha512-LofsSPjN/ITNkzV47hxas2JCsNCEnGhVvocfyOcLzT9c/tSZE7SfhS/iWtzP1lKNOEfLhRTZz6xqI8N2RzweSQ==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.7.4", - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/typescript-estree": "6.7.4", + "@typescript-eslint/scope-manager": "6.17.0", + "@typescript-eslint/types": "6.17.0", + "@typescript-eslint/typescript-estree": "6.17.0", "semver": "^7.5.4" } }, "@typescript-eslint/visitor-keys": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.4.tgz", - "integrity": "sha512-pOW37DUhlTZbvph50x5zZCkFn3xzwkGtNoJHzIM3svpiSkJzwOYr/kVBaXmf+RAQiUDs1AHEZVNPg6UJCJpwRA==", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.17.0.tgz", + "integrity": "sha512-H6VwB/k3IuIeQOyYczyyKN8wH6ed8EwliaYHLxOIhyF0dYEIsN8+Bk3GE19qafeMKyZJJHP8+O1HiFhFLUNKSg==", "dev": true, "requires": { - "@typescript-eslint/types": "6.7.4", + "@typescript-eslint/types": "6.17.0", "eslint-visitor-keys": "^3.4.1" } }, + "@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 + }, "acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", @@ -4720,6 +4804,12 @@ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==" }, + "bun-types": { + "version": "1.0.18", + "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.0.18.tgz", + "integrity": "sha512-1XZ7AxOF8oO8FZtw1xj006JAKxEjulK3dUhsktZVN95vXBlsf4NIjQxfistVdpt24v3H2I9BwHp+UU+gXSSpAw==", + "dev": true + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -4969,18 +5059,19 @@ "dev": true }, "eslint": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.51.0.tgz", - "integrity": "sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.51.0", - "@humanwhocodes/config-array": "^0.11.11", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.56.0", + "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -5251,9 +5342,9 @@ } }, "globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -5905,9 +5996,9 @@ "requires": {} }, "punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true }, "queue-microtask": { @@ -6430,9 +6521,9 @@ } }, "vitefu": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.4.tgz", - "integrity": "sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz", + "integrity": "sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==", "requires": {} }, "which": { diff --git a/package.json b/package.json index 9c3b6ddfd..456b5d06e 100644 --- a/package.json +++ b/package.json @@ -8,22 +8,23 @@ "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", - "lint": "npm run eslint", + "lint": "npm run lint:frontend ; npm run lint:types ; npm run lint:backend", + "lint:frontend": "eslint . --fix", "lint:types": "npm run check", - "fmt": "npm run prettier:svelte && npm run prettier", - "eslint": "npx -p eslint@8 -- eslint .", - "prettier:svelte": "npx -p prettier@2 -- prettier --plugin-search-dir . --write .", - "prettier": "npx -p prettier@2 -- prettier --write '**/*.{js,css,md,html,json}'" + "lint:backend": "pylint backend/", + "format": "prettier --plugin-search-dir --write '**/*.{js,ts,svelte,css,md,html,json}'", + "format:backend": "yapf --recursive backend -p -i" }, "devDependencies": { "@sveltejs/adapter-auto": "^2.0.0", "@sveltejs/adapter-static": "^2.0.3", - "@sveltejs/kit": "^1.20.4", + "@sveltejs/kit": "^1.30.0", "@tailwindcss/typography": "^0.5.10", - "@typescript-eslint/eslint-plugin": "^6.0.0", - "@typescript-eslint/parser": "^6.0.0", + "@types/bun": "latest", + "@typescript-eslint/eslint-plugin": "^6.17.0", + "@typescript-eslint/parser": "^6.17.0", "autoprefixer": "^10.4.16", - "eslint": "^8.28.0", + "eslint": "^8.56.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-svelte": "^2.30.0", "postcss": "^8.4.31", diff --git a/src/lib/apis/openai/index.ts b/src/lib/apis/openai/index.ts index dcd927104..edebf693c 100644 --- a/src/lib/apis/openai/index.ts +++ b/src/lib/apis/openai/index.ts @@ -197,7 +197,7 @@ export const getOpenAIModelsDirect = async ( throw error; } - let models = Array.isArray(res) ? res : res?.data ?? null; + const models = Array.isArray(res) ? res : res?.data ?? null; return models .map((model) => ({ name: model.id, external: true })) diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index 344672da5..2029604e0 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -21,7 +21,7 @@ export const splitStream = (splitOn) => { }; export const convertMessagesToHistory = (messages) => { - let history = { + const history = { messages: {}, currentId: null }; @@ -114,7 +114,7 @@ export const checkVersion = (required, current) => { export const findWordIndices = (text) => { const regex = /\[([^\]]+)\]/g; - let matches = []; + const matches = []; let match; while ((match = regex.exec(text)) !== null) { diff --git a/static/manifest.json b/static/manifest.json index e386dc56e..6d8665f8d 100644 --- a/static/manifest.json +++ b/static/manifest.json @@ -1,16 +1,16 @@ { - "name": "Ollama Web UI", - "short_name": "Ollama", - "start_url": "/", - "display": "standalone", - "background_color": "#343541", - "theme_color": "#343541", - "orientation": "portrait-primary", - "icons": [ - { - "src": "/favicon.png", - "type": "image/png", - "sizes": "844x884" - } - ] -} \ No newline at end of file + "name": "Ollama Web UI", + "short_name": "Ollama", + "start_url": "/", + "display": "standalone", + "background_color": "#343541", + "theme_color": "#343541", + "orientation": "portrait-primary", + "icons": [ + { + "src": "/favicon.png", + "type": "image/png", + "sizes": "844x884" + } + ] +}