Compare commits

..

1 Commits

Author SHA1 Message Date
Juan Calderon-Perez
f70b91c2cb
Revert "Add support for sidebar menu (#1075)"
This reverts commit 9fb78e64be8c5666509f1de8ed695612a7661e4b.
2024-02-13 22:25:30 -05:00
42 changed files with 2533 additions and 3571 deletions

View File

@ -1,15 +0,0 @@
name: Sweep Issue
title: 'Sweep: '
description: For small bugs, features, refactors, and tests to be handled by Sweep, an AI-powered junior developer.
labels: sweep
body:
- type: textarea
id: description
attributes:
label: Details
description: Tell Sweep where and what to edit and provide enough context for a new developer to the codebase
placeholder: |
Unit Tests: Write unit tests for <FILE>. Test each function in the file. Make sure to test edge cases.
Bugs: The bug might be in <FILE>. Here are the logs: ...
Features: the new endpoint should use the ... class from <FILE> because it contains ... logic.
Refactors: We are migrating this function to ... version because ...

View File

@ -52,7 +52,6 @@ jobs:
- uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: 'pip' # caching pip dependencies
- name: Install dependencies with poetry
working-directory: ./api
run: |
@ -61,7 +60,7 @@ jobs:
- name: Run unit tests
working-directory: ./api
run: |
poetry run python -m pytest -v --color=yes
poetry run python -m pytest
check-sh-files:
runs-on: ubuntu-latest
steps:
@ -80,17 +79,16 @@ jobs:
- uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: 'pip' # caching pip dependencies
- name: Run ruff check
uses: chartboost/ruff-action@v1
with:
src: "./api"
args: "check --verbose"
- name: Run ruff format check
uses: chartboost/ruff-action@v1
args: "--verbose"
- name: Run black check
uses: psf/black@stable
with:
options: "--check --diff --verbose"
src: "./api"
args: "format --check --verbose"
check-web-code:
runs-on: ubuntu-latest
steps:

View File

@ -58,7 +58,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Publish Docker Image
uses: docker/build-push-action@v6
uses: docker/build-push-action@v5
with:
context: .
push: ${{ github.event_name != 'pull_request' }}

View File

@ -37,7 +37,7 @@ jobs:
fetch-depth: 0
- name: Set up Helm
uses: azure/setup-helm@v4
uses: azure/setup-helm@v3
with:
version: v3.12.3
@ -63,7 +63,7 @@ jobs:
- name: Create kind cluster
if: steps.list-changed.outputs.changed == 'true'
uses: helm/kind-action@v1.10.0
uses: helm/kind-action@v1.9.0
- name: Run chart-testing (install)
if: steps.list-changed.outputs.changed == 'true'

1
.gitignore vendored
View File

@ -12,4 +12,3 @@ api/static/*
**/node_modules/
**/dist
**/.mypy_cache/
.vscode

View File

@ -34,7 +34,7 @@ COPY vendor/requirements.txt /usr/src/app/requirements.txt
# Install api dependencies
RUN apt-get update \
&& apt-get install -y --no-install-recommends dumb-init libgomp1 musl-dev \
&& apt-get install -y --no-install-recommends dumb-init \
&& pip install --no-cache-dir ./api \
&& pip install -r /usr/src/app/requirements.txt \
&& apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* \
@ -45,8 +45,7 @@ RUN apt-get update \
&& mkdir -p /data/db \
&& mkdir -p /usr/src/app/weights \
&& echo "appendonly yes" >> /etc/redis/redis.conf \
&& echo "dir /data/db/" >> /etc/redis/redis.conf \
&& ln -s /usr/lib/x86_64-linux-musl/libc.so /lib/libc.musl-x86_64.so.1
&& echo "dir /data/db/" >> /etc/redis/redis.conf
EXPOSE 8008
ENTRYPOINT ["/usr/bin/dumb-init", "--"]

View File

@ -17,7 +17,7 @@ ENV NODE_ENV='development'
# Install dependencies
RUN apt-get update \
&& apt-get install -y --no-install-recommends dumb-init musl-dev
&& apt-get install -y --no-install-recommends dumb-init
# Copy database, source code, and scripts
COPY --from=redis /usr/local/bin/redis-server /usr/local/bin/redis-server
@ -36,8 +36,7 @@ RUN npm ci \
&& mkdir -p /data/db \
&& mkdir -p /usr/src/app/weights \
&& echo "appendonly yes" >> /etc/redis/redis.conf \
&& echo "dir /data/db/" >> /etc/redis/redis.conf \
&& ln -s /usr/lib/x86_64-linux-musl/libc.so /lib/libc.musl-x86_64.so.1
&& echo "dir /data/db/" >> /etc/redis/redis.conf
EXPOSE 8008
EXPOSE 9124

View File

@ -45,20 +45,9 @@ volumes:
Then, just visit http://localhost:8008, You can find the API documentation at http://localhost:8008/api/docs
### 🌍 Environment Variables
The following Environment Variables are available:
| Variable Name | Description | Default Value |
|-----------------------|---------------------------------------------------------|--------------------------------------|
| `SERGE_DATABASE_URL` | Database connection string | `sqlite:////data/db/sql_app.db` |
| `SERGE_JWT_SECRET` | Key for auth token encryption. Use a random string | `uF7FGN5uzfGdFiPzR` |
| `SERGE_SESSION_EXPIRY`| Duration in minutes before a user must reauthenticate | `60` |
| `NODE_ENV` | Node.js running environment | `production` |
## 🖥️ Windows
Ensure you have Docker Desktop installed, WSL2 configured, and enough free RAM to run models.
Ensure you have Docker Desktop installed, WSL2 configured, and enough free RAM to run models.
## ☁️ Kubernetes
@ -69,48 +58,36 @@ Instructions for setting up Serge on Kubernetes can be found in the [wiki](https
| Category | Models |
|:-------------:|:-------|
| **Alfred** | 40B-1023 |
| **BioMistral** | 7B |
| **Code** | 13B, 33B |
| **CodeLLaMA** | 7B, 7B-Instruct, 7B-Python, 13B, 13B-Instruct, 13B-Python, 34B, 34B-Instruct, 34B-Python |
| **Codestral** | 22B v0.1 |
| **Gemma** | 2B, 1.1-2B-Instruct, 7B, 1.1-7B-Instruct |
| **Gorilla** | Falcon-7B-HF-v0, 7B-HF-v1, Openfunctions-v1, Openfunctions-v2 |
| **Falcon** | 7B, 7B-Instruct, 40B, 40B-Instruct |
| **LLaMA 2** | 7B, 7B-Chat, 7B-Coder, 13B, 13B-Chat, 70B, 70B-Chat, 70B-OASST |
| **LLaMA 3** | 11B-Instruct, 13B-Instruct, 16B-Instruct |
| **LLaMA Pro** | 8B, 8B-Instruct |
| **LLaMA 2** | 7B, 7B-Chat, 7B-Coder, 13B, 13B-Chat, 70B, 70B-Chat, 70B-OASST |
| **Med42** | 70B |
| **Medalpaca** | 13B |
| **Medicine** | Chat, LLM |
| **Medicine-LLM** | 13B |
| **Meditron** | 7B, 7B-Chat, 70B |
| **Meta-LlaMA-3** | 8B, 8B-Instruct, 70B, 70B-Instruct |
| **Mistral** | 7B-V0.1, 7B-Instruct-v0.2, 7B-OpenOrca |
| **MistralLite** | 7B |
| **Mixtral** | 8x7B-v0.1, 8x7B-Dolphin-2.7, 8x7B-Instruct-v0.1 |
| **Neural-Chat** | 7B-v3.3 |
| **Notus** | 7B-v1 |
| **Notux** | 8x7b-v1 |
| **Nous-Hermes 2** | Mistral-7B-DPO, Mixtral-8x7B-DPO, Mistral-8x7B-SFT |
| **OpenChat** | 7B-v3.5-1210 |
| **OpenCodeInterpreter** | DS-6.7B, DS-33B, CL-7B, CL-13B, CL-70B |
| **OpenLLaMA** | 3B-v2, 7B-v2, 13B-v2 |
| **Orca 2** | 7B, 13B |
| **Phi 2** | 2.7B |
| **Phi 3** | mini-4k-instruct, medium-4k-instruct, medium-128k-instruct |
| **Python Code** | 13B, 33B |
| **PsyMedRP** | 13B-v1, 20B-v1 |
| **Starling LM** | 7B-Alpha |
| **SOLAR** | 10.7B-v1.0, 10.7B-instruct-v1.0 |
| **TinyLlama** | 1.1B |
| **Vicuna** | 7B-v1.5, 13B-v1.5, 33B-v1.3, 33B-Coder |
| **WizardLM** | 2-7B, 13B-v1.2, 70B-v1.0 |
| **WizardLM** | 7B-v1.0, 13B-v1.2, 70B-v1.0 |
| **Zephyr** | 3B, 7B-Alpha, 7B-Beta |
Additional models can be requested by opening a GitHub issue. Other models are also available at [Serge Models](https://github.com/Smartappli/serge-models).
## ⚠️ Memory Usage
LLaMA will crash if you don't have enough available memory for the model
LLaMA will crash if you don't have enough available memory for the model:
## 💬 Support
@ -130,29 +107,3 @@ git clone https://github.com/serge-chat/serge.git
cd serge/
docker compose -f docker-compose.dev.yml up --build
```
The solution will accept a python debugger session on port 5678. Example launch.json for VSCode:
```json
{
"version": "0.2.0",
"configurations": [
{
"name": "Remote Debug",
"type": "python",
"request": "attach",
"connect": {
"host": "localhost",
"port": 5678
},
"pathMappings": [
{
"localRoot": "${workspaceFolder}/api",
"remoteRoot": "/usr/src/app/api/"
}
],
"justMyCode": false
}
]
}
```

4
api/.gitignore vendored
View File

@ -157,6 +157,4 @@ cython_debug/
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
*.db
#.idea/

1346
api/poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -21,29 +21,53 @@ requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.poetry.dependencies]
python=">=3.10,<4.0"
python=">=3.9,<4.0"
asyncio = "^3.4.3"
packaging = "^24.1"
pydantic = "^1.10.17"
packaging = "^23.2"
pydantic = "^1.10.14"
python-dotenv = "^1.0.1"
python-multipart = "^0.0.9"
pyyaml = "^6.0"
rfc3986 = "^2.0.0"
sentencepiece = "^0.1.99"
sniffio = "^1.3.0"
sse-starlette = "^1.8.2"
starlette = "^0.26.1"
typing-extensions = "^4.12.2"
urllib3 = "^2.2.2"
toml = "^0.10.2"
tqdm = "^4.66.2"
typing-extensions = "^4.9.0"
ujson = "^5.9.0"
urllib3 = "^2.2.0"
uvloop = "^0.19.0"
watchfiles = "^0.21.0"
websockets = "^12.0"
anyio = "^4.2.0"
certifi = "^2024.2.2"
charset-normalizer = "^3.3.2"
click = "^8.1.7"
email-validator = "^2.0.0"
fastapi = "^0.95.1"
huggingface-hub = "^0.24.5"
requests = "^2.32.3"
filelock = "^3.13.1"
h11 = "^0.14.0"
httpcore = "^1.0.2"
httptools = "^0.6.1"
huggingface-hub = "^0.20.3"
idna = "^3.6"
itsdangerous = "^2.1.2"
jinja2 = "^3.1.3"
markupsafe = "^2.1.5"
motor = "^3.3.2"
orjson = "^3.9.13"
dnspython = "^2.5.0"
lazy-model = "^0.2.0"
requests = "^2.31.0"
numpy = "^1.25.2"
langchain = "^0.0.180"
loguru = "^0.7.2"
redis = {extras = ["hiredis"], version = "^5.0.8"}
pytest = "^8.3.2"
hypercorn = {extras = ["trio"], version = "^0.17.3"}
redis = {extras = ["hiredis"], version = "^5.0.1"}
pytest = "^8.0.0"
hypercorn = {extras = ["trio"], version = "^0.16.0"}
pyjwt = "^2.9.0"
python-jose = {extras = ["cryptography"], version = "^3.3.0"}
aiofiles = "^24.1.0"
python-multipart = "^0.0.9"
debugpy = "^1.8.5"
sqlalchemy = "^2.0.32"
[tool.ruff]
# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default.
select = ["E", "F"]
@ -94,3 +118,6 @@ target-version = "py311"
# Unlike Flake8, default to a complexity level of 10.
max-complexity = 10
[tool.black]
line-length = 150
target-version = ['py311']

View File

@ -1,110 +0,0 @@
import logging
import uuid
from typing import List, Optional
from serge.schema import user as user_schema
from serge.utils.security import get_password_hash
from sqlalchemy.orm import Session
from serge.models import user as user_model
def get_user(db: Session, username: str) -> Optional[user_schema.User]:
return Mappers.user_db_to_view(
db.query(user_model.User).filter(user_model.User.username == username).first(),
include_auth=True,
)
def get_user_by_email(db: Session, email: str) -> Optional[user_schema.User]:
return Mappers.user_db_to_view(db.query(user_model.User).filter(user_model.User.email == email).first())
def get_users(db: Session, skip: int = 0, limit: int = 100) -> List[user_schema.User]:
return [Mappers.user_db_to_view(u) for u in db.query(user_model.User).offset(skip).limit(limit).all()]
def create_user(db: Session, ua: user_schema.UserAuth) -> Optional[user_schema.User]:
# Check already exists
if get_user(db, ua.username):
logging.error(f"Tried to create new user, but already exists: {ua.username}")
return None
match ua.auth_type:
case 1:
ua.secret = get_password_hash(ua.secret)
case _: # Todo: More auth types
return None
db_user, db_user_auth = Mappers.user_view_to_db(None, ua)
db.add(db_user_auth)
db.add(db_user)
db.commit()
return Mappers.user_db_to_view(db_user)
def update_user(db: Session, u: user_schema.User) -> Optional[user_schema.User]:
user = db.query(user_model.User).filter(user_model.User.username == u.username).first()
if not user:
return None
for k, v in u.dict().items():
if k in ["auth", "chats"]:
continue
setattr(user, k, v)
db.commit()
return user
def create_chat(db: Session, chat: user_schema.Chat):
c = user_model.Chat(owner=chat.owner, chat_id=chat.chat_id)
db.add(c)
db.commit()
def remove_chat(db: Session, chat: user_schema.Chat):
c = db.query(user_model.Chat).filter(user_model.Chat.chat_id == chat.chat_id).one()
db.delete(c)
db.commit()
class Mappers:
@staticmethod
def user_db_to_view(u: user_model.User, include_auth=False) -> user_schema.User:
if not u:
return None
auths = chats = []
if include_auth:
auths = u.auth
# u.auth = []
chats = u.chats
# u.chats = []
app_user = user_schema.User(**{k: v for k, v in u.__dict__.items() if not k.startswith("_") and k not in ["chats", "auth"]})
app_user.auth = [user_schema.UserAuth(username=u.username, secret=x.secret, auth_type=x.auth_type) for x in auths]
app_user.chats = [user_schema.Chat(chat_id=x.chat_id, owner=x.owner) for x in chats]
return app_user
@staticmethod
def user_view_to_db(
u: Optional[user_schema.User] = None, ua: Optional[user_schema.UserAuth] = None
) -> (user_model.User, Optional[user_model.UserAuth]):
assert u or ua, "One of User or UserAuth must be passed"
if not u: # Creating a new user
u = user_schema.User(id=uuid.uuid4(), username=ua.username)
auth = []
if ua:
auth = Mappers.user_auth_view_to_db(ua, u.id)
user = user_model.User(**u.dict())
if auth:
user.auth.append(auth)
for chat in u.chats:
user.chats.append(user_model.Chat(chat_id=chat.chat_id))
return (user, auth)
@staticmethod
def user_auth_view_to_db(ua: user_schema.UserAuth, user_id: uuid.UUID) -> user_model.UserAuth:
if not ua:
return None
return user_model.UserAuth(secret=ua.secret, auth_type=ua.auth_type, user_id=user_id)

View File

@ -15,22 +15,6 @@
}
]
},
{
"name": "BioMistral",
"models": [
{
"name": "BioMistral-7B",
"repo": "BioMistral/BioMistral-7B-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "ggml-model-Q4_K_M.gguf",
"disk_space": 4368439424.0
}
]
}
]
},
{
"name": "Code",
"models": [
@ -161,23 +145,7 @@
]
}
]
},
{
"name": "Codesstral",
"models": [
{
"name": "Codestral-22B-v0.1",
"repo": "bartowski/Codestral-22B-v0.1-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "Codestral-22B-v0.1-Q4_K_M.gguf",
"disk_space": 15722553696.0
}
]
}
]
},
},
{
"name": "Falcon",
"models": [
@ -227,104 +195,6 @@
}
]
},
{
"name": "Gemma",
"models": [
{
"name": "Gemma-2B",
"repo": "brittlewis12/gemma-2b-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "gemma-2b.Q4_K_M.gguf",
"disk_space": 1495245728.0
}
]
},
{
"name": "Gemma-1_1-2B-Instruct",
"repo": "brittlewis12/gemma-1.1-2b-it-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "gemma-1.1-2b-it.Q4_K_M.gguf",
"disk_space": 1630263200.0
}
]
},
{
"name": "Gemma-7B",
"repo": "brittlewis12/gemma-7b-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "gemma-7b.Q4_K_M.gguf",
"disk_space": 5127231648.0
}
]
},
{
"name": "Gemma-1_1-7B-Instruct",
"repo": "brittlewis12/gemma-1.1-7b-it-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "gemma-1.1-7b-it.Q4_K_M.gguf",
"disk_space": 5329759200.0
}
]
}
]
},
{
"name": "Gorilla",
"models": [
{
"name": "Gorilla-7B Falcon",
"repo": "gorilla-llm/gorilla-falcon-7b-hf-v0-gguf",
"files": [
{
"name": "q4_K_M",
"filename": "gorilla-falcon-7b-hf-v0-q4_K_M.gguf",
"disk_space": 4975125696.0
}
]
},
{
"name": "Gorilla-7B",
"repo": "gorilla-llm/gorilla-7b-hf-v1-gguf",
"files": [
{
"name": "q4_K_M",
"filename": "gorilla-7b-hf-v1-q4_K_M.gguf",
"disk_space": 4081004288.0
}
]
},
{
"name": "Gorilla OpenFunctions V1",
"repo": "gorilla-llm/gorilla-openfunctions-v1-gguf",
"files": [
{
"name": "q4_K_M",
"filename": "gorilla-openfunctions-v1-q4_K_M.gguf",
"disk_space": 4081004288.0
}
]
},
{
"name": "Gorilla OpenFunctions V2",
"repo": "gorilla-llm/gorilla-openfunctions-v2-gguf",
"files": [
{
"name": "q4_K_M",
"filename": "gorilla-openfunctions-v2-q4_K_M.gguf",
"disk_space": 4223770912.0
}
]
}
]
},
{
"name": "LLaMA_2",
"models": [
@ -417,71 +287,6 @@
]
}
]
},
{
"name": "LLaMA_3",
"models": [
{
"name": "Llama-3-11B-Instruct-v0.1",
"repo": "MaziyarPanahi/Llama-3-11B-Instruct-v0.1-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "Llama-3-11B-Instruct-v0.1.Q4_K_M.gguf",
"disk_space": 8200021632.0
}
]
},
{
"name": "Llama-3-13B-Instruct-v0.1",
"repo": "MaziyarPanahi/Llama-3-13B-Instruct-v0.1-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "Llama-3-13B-Instruct-v0.1.Q4_K_M.gguf",
"disk_space": 8061089600.0
}
]
},
{
"name": "Llama-3-16B-Instruct-v0.1",
"repo": "MaziyarPanahi/Llama-3-16B-Instruct-v0.1-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "Llama-3-16B-Instruct-v0.1.Q4_K_M.gguf",
"disk_space": 10154318048.0
}
]
}
]
},
{
"name": "LLaMA-Pro",
"models": [
{
"name": "Llama-Pro-8B",
"repo": "TheBloke/LLaMA-Pro-8B-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "llama-pro-8b.Q4_K_M.gguf",
"disk_space": 5055758336.0
}
]
},
{
"name": "Llama-Pro-8B-Instruct",
"repo": "TheBloke/LLaMA-Pro-8B-Instruct-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "llama-pro-8b-instruct.Q4_K_M.gguf",
"disk_space": 5055758688.0
}
]
}
]
},
{
"name": "Med42",
@ -516,32 +321,10 @@
]
},
{
"name": "Medicine",
"name": "medicine-LLM",
"models": [
{
"name": "Medicine-Chat",
"repo": "TheBloke/medicine-chat-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "medicine-chat.Q4_K_M.gguf",
"disk_space": 4081010048.0
}
]
},
{
"name": "Medicine-LLM",
"repo": "TheBloke/medicine-LLM-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "medicine-llm.Q4_K_M.gguf",
"disk_space": 4081009920.0
}
]
},
{
"name": "Medicine-LLM-13B",
"name": "Medicine LLM 13B",
"repo": "TheBloke/medicine-LLM-13B-GGUF",
"files": [
{
@ -552,7 +335,7 @@
]
}
]
},
},
{
"name": "Meditron",
"models": [
@ -590,56 +373,7 @@
]
}
]
},
{
"name": "Meta-Llama-3",
"models": [
{
"name": "Meta-Llama-3-8B",
"repo": "QuantFactory/Meta-Llama-3-8B-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "Meta-Llama-3-8B.Q4_K_M.gguf",
"disk_space": 4921246944.0
}
]
},
{
"name": "Meta-Llama-3-8B-Instruct",
"repo": "QuantFactory/Meta-Llama-3-8B-Instruct-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "Meta-Llama-3-8B-Instruct.Q4_K_M.gguf",
"disk_space": 4921246944.0
}
]
},
{
"name": "Meta-Llama-3-70B",
"repo": "NousResearch/Meta-Llama-3-70B-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "Meta-Llama-3-70B-Q4_K_M.gguf",
"disk_space": 42520906176.0
}
]
},
{
"name": "Meta-Llama-3-70B-Instruct",
"repo": "QuantFactory/Meta-Llama-3-70B-Instruct-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "Meta-Llama-3-70B-Instruct.Q4_K_M.gguf",
"disk_space": 42520906208.0
}
]
}
]
},
},
{
"name": "Mistral",
"models": [
@ -780,44 +514,6 @@
}
]
},
{
"name": "Nous-Hermes-2",
"models": [
{
"name": "Nous-Hermes-2-Mistral-7B-DPO",
"repo": "NousResearch/Nous-Hermes-2-Mistral-7B-DPO-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "Nous-Hermes-2-Mistral-7B-DPO.Q4_K_M.gguf",
"disk_space": 4368450560.0
}
]
},
{
"name": "Nous-Hermes-2-Mistral-7B-DPO",
"repo": "TheBloke/Nous-Hermes-2-Mixtral-8x7B-DPO-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "nous-hermes-2-mixtral-8x7b-dpo.Q4_K_M.gguf",
"disk_space": 28446421792.0
}
]
},
{
"name": "Nous-Hermes-2-Mistral-7B-SFT",
"repo": "NousResearch/Nous-Hermes-2-Mixtral-8x7B-SFT-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "Nous-Hermes-2-Mixtral-8x7B-SFT.Q4_K_M.gguf",
"disk_space": 28446421760.0
}
]
}
]
},
{
"name": "OpenChat",
"models": [
@ -833,67 +529,7 @@
]
}
]
},
{
"name": "OpenCodeInterpreter",
"models": [
{
"name": "OpenCodeInterpreter-DS-6.7B",
"repo": "LoneStriker/OpenCodeInterpreter-DS-6.7B-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "OpenCodeInterpreter-DS-6.7B-Q4_K_M.gguf",
"disk_space": 4083016864.0
}
]
},
{
"name": "OpenCodeInterpreter-DS-33B",
"repo": "LoneStriker/OpenCodeInterpreter-DS-33B-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "OpenCodeInterpreter-DS-33B-Q4_K_M.gguf",
"disk_space": 19940659200.0
}
]
},
{
"name": "OpenCodeInterpreter-CL-7B",
"repo": "LoneStriker/OpenCodeInterpreter-CL-7B-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "OpenCodeInterpreter-CL-7B-Q4_K_M.gguf",
"disk_space": 4081095360.0
}
]
},
{
"name": "OpenCodeInterpreter-CL-13B",
"repo": "LoneStriker/OpenCodeInterpreter-CL-13B-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "OpenCodeInterpreter-CL-13B-Q4_K_M.gguf",
"disk_space": 7866070048.0
}
]
},
{
"name": "OpenCodeInterpreter-CL-70B",
"repo": "LoneStriker/OpenCodeInterpreter-CL-70B-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "OpenCodeInterpreter-CL-70B-Q4_K_M.gguf",
"disk_space": 41423092096.0
}
]
}
]
},
},
{
"name": "OpenLLaMA",
"models": [
@ -972,39 +608,6 @@
"disk_space": 1789239136.0
}
]
},
{
"name": "Phi-3-mini-4k-instruct-v0_3",
"repo": "bartowski/Phi-3-mini-4k-instruct-v0.3-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "Phi-3-mini-4k-instruct-v0.3-Q4_K_M.gguf",
"disk_space": 2393231456.0
}
]
},
{
"name": "Phi-3-medium-4k-instruct",
"repo": "bartowski/Phi-3-medium-4k-instruct-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "Phi-3-medium-4k-instruct-Q4_K_M.gguf",
"disk_space": 8566820736.0
}
]
},
{
"name": "Phi-3-medium-128k-instruct",
"repo": "bartowski/Phi-3-medium-128k-instruct-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "Phi-3-medium-128k-instruct-Q4_K_M.gguf",
"disk_space": 8566821408.0
}
]
}
]
},
@ -1093,49 +696,6 @@
]
}
]
},
{
"name": "SOLAR",
"models": [
{
"name": "SOLAR-10.7B-V1_0",
"repo": "TheBloke/SOLAR-10.7B-v1.0-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "solar-10.7b-v1.0.Q4_K_M.gguf",
"disk_space": 6461667488.0
}
]
},
{
"name": "SOLAR-10.7B-instruct-V1_0",
"repo": "TheBloke/SOLAR-10.7B-Instruct-v1.0-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "solar-10.7b-instruct-v1.0.Q4_K_M.gguf",
"disk_space": 6461667936.0
}
]
}
]
},
{
"name": "Tinyllama",
"models": [
{
"name": "Tinyllama-1.1B-Chat-v1.0",
"repo": "TheBloke/TinyLlama-1.1B-Chat-v1.0-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "tinyllama-1.1b-chat-v1.0.Q4_K_M.gguf",
"disk_space": 668788096.0
}
]
}
]
},
{
"name": "Vicuna",
@ -1190,13 +750,13 @@
"name": "WizardLM",
"models": [
{
"name": "WizardLM-2-7B",
"repo": "MaziyarPanahi/WizardLM-2-7B-GGUF",
"name": "WizardLM-7B-v1_0",
"repo": "TheBloke/wizardLM-7B-GGUF",
"files": [
{
"name": "q4_K_M",
"filename": "WizardLM-2-7B.Q4_K_M.gguf",
"disk_space": 4368439008.0
"filename": "wizardLM-7B.Q4_K_M.gguf",
"disk_space": 4081009920.0
}
]
},

View File

@ -1,31 +0,0 @@
import logging
import uuid
from serge.models.settings import Settings
from serge.models.user import User, UserAuth
from sqlalchemy import create_engine
from sqlalchemy.orm import Session, sessionmaker
settings = Settings()
engine = create_engine(settings.SERGE_DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def seed_db(db: Session):
sys_u = db.query(User).filter(User.username == "system").first()
if sys_u:
return
system_user = User(
id=uuid.uuid4(),
username="system",
email="",
full_name="Default User",
theme_light=False,
default_prompt="Below is an instruction that describes a task. Write a response that appropriately completes the request.",
is_active=True,
auth=[UserAuth(secret="", auth_type=0)],
)
db.add(system_user)
db.commit()
logging.info("System user created")

View File

@ -5,16 +5,12 @@ from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from loguru import logger
from serge.database import SessionLocal, engine, seed_db
from starlette.responses import FileResponse
from serge.models.settings import Settings
from serge.routers.auth import auth_router
from serge.routers.chat import chat_router
from serge.routers.model import model_router
from serge.routers.ping import ping_router
from serge.routers.user import user_router
from starlette.responses import FileResponse
from serge.models import user as user_models
# Configure logging settings
@ -45,17 +41,12 @@ origins = [
"http://localhost:9124",
]
# Seed the database
user_models.Base.metadata.create_all(bind=engine)
app = FastAPI(title="Serge", version="0.0.1", description=description, tags_metadata=tags_metadata)
api_app = FastAPI(title="Serge API")
api_app.include_router(chat_router)
api_app.include_router(ping_router)
api_app.include_router(model_router)
api_app.include_router(auth_router)
api_app.include_router(user_router)
app.mount("/api", api_app)
# handle serving the frontend as static files in production
@ -92,9 +83,6 @@ async def start_database():
for file in files:
os.remove(WEIGHTS + file)
db = SessionLocal()
seed_db(db)
app.add_middleware(
CORSMiddleware,

View File

@ -32,5 +32,5 @@ class ChatParameters(BaseModel):
class Chat(BaseModel):
id: str = Field(default_factory=lambda: str(uuid4()))
created: datetime = Field(default_factory=datetime.now)
owner: str = Field("system")
params: ChatParameters

View File

@ -1,13 +1,8 @@
from os import getenv
from pydantic import BaseSettings
class Settings(BaseSettings):
SERGE_DATABASE_URL: str = getenv("SERGE_DATABASE_URL", "sqlite:////data/db/sql_app.db")
NODE_ENV: str = "development"
SERGE_JWT_SECRET: str = getenv("SERGE_JWT_SECRET", "uF7FGN5uzfGdFiPzR")
SERGE_SESSION_EXPIRY: int = getenv("SERGE_SESSION_EXPIRY", 60)
class Config:
orm_mode = True

View File

@ -1,40 +0,0 @@
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, Uuid
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Uuid, primary_key=True)
username = Column(String, unique=True, index=True)
email = Column(String)
full_name = Column(String)
theme_light = Column(Boolean)
default_prompt = Column(String)
is_active = Column(Boolean, default=True)
auth = relationship("UserAuth", back_populates="user", lazy="joined")
chats = relationship("Chat", back_populates="user", lazy="joined")
class Chat(Base):
__tablename__ = "chats"
id = Column(Integer, primary_key=True)
chat_id = Column(String, index=True)
owner = Column(String, ForeignKey("users.username"))
user = relationship("User", back_populates="chats")
class UserAuth(Base):
__tablename__ = "auth"
id = Column(Integer, primary_key=True)
secret = Column(String)
auth_type = Column(Integer)
user_id = Column(Uuid, ForeignKey("users.id"))
user = relationship("User", back_populates="auth")

View File

@ -1,108 +0,0 @@
import logging
from datetime import timedelta
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError
from serge.crud import get_user
from serge.database import SessionLocal
from serge.schema.user import Token, User
from serge.models.settings import Settings
from serge.utils.security import create_access_token, decode_access_token, verify_password
from sqlalchemy.orm import Session
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
settings = Settings()
auth_router = APIRouter(
prefix="/auth",
tags=["auth"],
)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
def authenticate_user(username: str, password: str, db: Session) -> Optional[User]:
user = get_user(db, username)
if not user:
return None
# Users may have multipe ways to authenticate
auths = [a.auth_type for a in user.auth]
if 0 in auths: # Default user, passwordless
return user
if 1 in auths: # Password auth
secret = [x for x in user.auth if x.auth_type == 1][0].secret
if verify_password(password, secret):
return user
if 2 in auths: # todo future auths
pass
return False
@auth_router.post("/token", response_model=Token)
async def login_for_access_token(
response: Response,
form_data: OAuth2PasswordRequestForm = Depends(),
db: Session = Depends(get_db),
):
user = authenticate_user(form_data.username, form_data.password, db)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=settings.SERGE_SESSION_EXPIRY)
access_token = create_access_token(data={"sub": user.username}, expires_delta=access_token_expires)
response.set_cookie(key="token", value=access_token, httponly=True, secure=True, samesite="strict")
return {"access_token": access_token, "token_type": "bearer"}
@auth_router.post("/logout")
async def logout(response: Response):
# Clear the token cookie by setting it to expire immediately
response.delete_cookie(key="token")
return {"message": "Logged out successfully"}
async def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)) -> User:
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
username = decode_access_token(token)
if username is None:
raise credentials_exception
except JWTError as e:
logging.exception(e)
raise credentials_exception
user = get_user(db, username)
if user is None:
raise credentials_exception
return user
async def get_current_active_user(request: Request, response: Response, db: Session = Depends(get_db)) -> User:
token = request.cookies.get("token")
if not token:
return get_user(db, "system")
u = None
try:
u = await get_current_user(token, db)
except HTTPException:
await logout(response)
u = get_user(db, "system")
return u

View File

@ -1,60 +1,25 @@
import os
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, status
from typing import Optional
from fastapi import APIRouter, HTTPException
from langchain.memory import RedisChatMessageHistory
from langchain.schema import AIMessage, HumanMessage, SystemMessage, messages_to_dict
from langchain.schema import SystemMessage, messages_to_dict, AIMessage, HumanMessage
from llama_cpp import Llama
from loguru import logger
from redis import Redis
from serge.crud import create_chat, remove_chat, update_user
from serge.database import SessionLocal
from serge.models.chat import Chat, ChatParameters
from serge.routers.auth import get_current_active_user
from serge.schema.user import Chat as UserChat
from serge.schema.user import User
from serge.utils.stream import get_prompt
from sqlalchemy.orm import Session
from sse_starlette.sse import EventSourceResponse
from serge.models.chat import Chat, ChatParameters
from serge.utils.stream import get_prompt
chat_router = APIRouter(
prefix="/chat",
tags=["chat"],
)
unauth_error = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Unauthorized",
headers={"WWW-Authenticate": "Bearer"},
)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
def _try_get_chat(client, chat_id):
if not client.sismember("chats", chat_id):
raise ValueError("Chat does not exist")
chat_raw = client.get(f"chat:{chat_id}")
chat = Chat.parse_raw(chat_raw)
# backwards compat
if not hasattr(chat, "owner"):
chat.owner = "system"
return chat
@chat_router.post("/")
async def create_new_chat(
u: User = Depends(get_current_active_user),
db: Session = Depends(get_db),
model: str = "7B",
temperature: float = 0.1,
top_k: int = 50,
@ -86,16 +51,11 @@ async def create_new_chat(
init_prompt=init_prompt,
)
# create the chat
chat = Chat(owner=u.username, params=params)
chat = Chat(params=params)
# store the parameters
client.set(f"chat:{chat.id}", chat.json())
uc = UserChat(chat_id=chat.id, owner=u.username)
create_chat(db, uc)
u.chats.append(uc)
update_user(db, u)
# create the message history
history = RedisChatMessageHistory(chat.id)
history.append(SystemMessage(content=init_prompt))
@ -107,11 +67,13 @@ async def create_new_chat(
@chat_router.get("/")
async def get_all_chats(u: User = Depends(get_current_active_user)):
async def get_all_chats():
res = []
client = Redis(host="localhost", port=6379, decode_responses=False)
ids = client.smembers("chats")
chats = sorted(
[await get_specific_chat(x.chat_id, u) for x in u.chats],
[await get_specific_chat(id.decode()) for id in ids],
key=lambda x: x["created"],
reverse=True,
)
@ -134,33 +96,39 @@ async def get_all_chats(u: User = Depends(get_current_active_user)):
@chat_router.get("/{chat_id}")
async def get_specific_chat(chat_id: str, u: User = Depends(get_current_active_user)):
async def get_specific_chat(chat_id: str):
client = Redis(host="localhost", port=6379, decode_responses=False)
if chat_id not in [x.chat_id for x in u.chats]:
raise unauth_error
if not client.sismember("chats", chat_id):
raise ValueError("Chat does not exist")
chat = _try_get_chat(client, chat_id)
chat_raw = client.get(f"chat:{chat_id}")
chat = Chat.parse_raw(chat_raw)
history = RedisChatMessageHistory(chat.id)
chat_dict = chat.dict()
chat_dict["history"] = messages_to_dict(history.messages)
return chat_dict
@chat_router.get("/{chat_id}/history")
async def get_chat_history(chat_id: str, u: User = Depends(get_current_active_user)):
if chat_id not in [x.chat_id for x in u.chats]:
raise unauth_error
async def get_chat_history(chat_id: str):
client = Redis(host="localhost", port=6379, decode_responses=False)
if not client.sismember("chats", chat_id):
raise ValueError("Chat does not exist")
history = RedisChatMessageHistory(chat_id)
return messages_to_dict(history.messages)
@chat_router.delete("/{chat_id}/prompt")
async def delete_prompt(chat_id: str, idx: int, u: User = Depends(get_current_active_user)):
if chat_id not in [x.chat_id for x in u.chats]:
raise unauth_error
async def delete_prompt(chat_id: str, idx: int):
client = Redis(host="localhost", port=6379, decode_responses=False)
if not client.sismember("chats", chat_id):
raise ValueError("Chat does not exist")
history = RedisChatMessageHistory(chat_id)
@ -178,17 +146,12 @@ async def delete_prompt(chat_id: str, idx: int, u: User = Depends(get_current_ac
@chat_router.delete("/{chat_id}")
async def delete_chat(chat_id: str, u: User = Depends(get_current_active_user), db: Session = Depends(get_db)):
async def delete_chat(chat_id: str):
client = Redis(host="localhost", port=6379, decode_responses=False)
if chat_id not in [x.chat_id for x in u.chats]:
raise unauth_error
if not client.sismember("chats", chat_id):
raise ValueError("Chat does not exist")
if cid := next((x for x in u.chats if x.chat_id == chat_id), None):
remove_chat(db, cid)
RedisChatMessageHistory(chat_id).clear()
client.delete(f"chat:{chat_id}")
@ -198,25 +161,23 @@ async def delete_chat(chat_id: str, u: User = Depends(get_current_active_user),
@chat_router.delete("/delete/all")
async def delete_all_chats(u: User = Depends(get_current_active_user), db: Session = Depends(get_db)):
[delete_chat(x.chat_id, u, db) for x in u.chats]
async def delete_all_chats():
client = Redis(host="localhost", port=6379, decode_responses=False)
client.flushdb()
return True
@chat_router.get("/{chat_id}/question")
async def stream_ask_a_question(chat_id: str, prompt: str, u: User = Depends(get_current_active_user)):
if chat_id not in [x.chat_id for x in u.chats]:
raise unauth_error
def stream_ask_a_question(chat_id: str, prompt: str):
logger.info("Starting redis client")
client = Redis(host="localhost", port=6379, decode_responses=False)
if not client.sismember("chats", chat_id):
raise ValueError("Chat does not exist")
logger.debug("creating chat")
chat = _try_get_chat(client, chat_id)
chat_raw = client.get(f"chat:{chat_id}")
chat = Chat.parse_raw(chat_raw)
logger.debug(chat.params)
logger.debug("creating history")
@ -262,7 +223,7 @@ async def stream_ask_a_question(chat_id: str, prompt: str, u: User = Depends(get
yield {"event": "message", "data": txt}
except Exception as e:
if type(e) is UnicodeDecodeError:
if type(e) == UnicodeDecodeError:
pass
else:
error = e.__str__()
@ -281,16 +242,15 @@ async def stream_ask_a_question(chat_id: str, prompt: str, u: User = Depends(get
@chat_router.post("/{chat_id}/question")
async def ask_a_question(chat_id: str, prompt: str, u: User = Depends(get_current_active_user)):
if chat_id not in [x.chat_id for x in u.chats]:
raise unauth_error
async def ask_a_question(chat_id: str, prompt: str):
client = Redis(host="localhost", port=6379, decode_responses=False)
if not client.sismember("chats", chat_id):
raise ValueError("Chat does not exist")
chat = _try_get_chat(client, chat_id)
chat_raw = client.get(f"chat:{chat_id}")
chat = Chat.parse_raw(chat_raw)
history = RedisChatMessageHistory(chat.id)
if len(prompt) > 0:

View File

@ -1,63 +0,0 @@
import logging
from fastapi import APIRouter, Depends, HTTPException, status
from serge.crud import create_user, update_user
from serge.database import SessionLocal
from serge.routers.auth import get_current_active_user
from serge.schema import user as user_schema
from sqlalchemy.orm import Session
user_router = APIRouter(
prefix="/user",
tags=["user"],
)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@user_router.get("/", response_model=user_schema.User)
async def get_user(u: user_schema.User = Depends(get_current_active_user)):
if not u:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
return u.to_public_dict()
@user_router.post("/create", response_model=user_schema.User)
async def create_user_with_pass(ua: user_schema.UserAuth, db: Session = Depends(get_db)):
try:
u = create_user(db, ua)
except Exception as e:
logging.exception(e)
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=f"Failed to create. {e}",
)
if not u:
raise HTTPException(
status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
detail="Could not create user",
)
return u.to_public_dict()
@user_router.put("/", response_model=user_schema.User)
async def self_update_user(
new_data: user_schema.User,
current: user_schema.User = Depends(get_current_active_user),
db: Session = Depends(get_db),
):
current.email = new_data.email
current.full_name = new_data.full_name
current.default_prompt = new_data.default_prompt
update_user(db, current)
return current.to_public_dict()

View File

@ -73,35 +73,16 @@
"Name": {
"type": "string",
"enum": [
"fp16",
"iq1_M",
"iq1_S",
"iq2_M",
"iq2_S",
"iq2_XS",
"iq2_XXS",
"iq3_M",
"iq3_S",
"iq3_XS",
"iq3_XXS",
"iq4_NL",
"iq4_XS",
"q2_K",
"q3_K_L",
"q3_K_M",
"q3_K_S",
"q4_0",
"q4_1",
"q4_K_M",
"q4_K_S",
"q5_0",
"q5_1",
"q5_K_M",
"q5_K_S",
"q6_K",
"q8_0",
"q8_1",
"q8_K"
"q8_0"
],
"title": "Name"
}

View File

@ -1,42 +0,0 @@
import uuid
from pydantic import BaseModel
class UserBase(BaseModel):
username: str
class UserAuth(UserBase):
secret: str
auth_type: int
class Chat(BaseModel):
chat_id: str
owner: str
class User(UserBase):
id: uuid.UUID
is_active: bool = True
email: str = ""
full_name: str = ""
theme_light: bool = False
default_prompt: str = "Below is an instruction that describes a task. Write a response that appropriately completes the request."
auth: list[UserAuth] = []
chats: list[Chat] = []
class Config:
orm_mode = True
def to_public_dict(self):
user_dict = self.dict()
for auth in user_dict["auth"]:
auth["secret"] = "********"
return user_dict
class Token(BaseModel):
access_token: str
token_type: str

View File

@ -1,56 +0,0 @@
import base64
import hashlib
import os
from datetime import datetime, timedelta
from typing import Optional
from fastapi import HTTPException, status
from jose import JWTError, jwt
from serge.models.settings import Settings
ALGORITHM = "HS256"
settings = Settings()
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
def verify_password(plain_password: str, hashed_password: str) -> bool:
salt_and_hash = base64.b64decode(hashed_password.encode("utf-8"))
salt = salt_and_hash[:16]
stored_password = salt_and_hash[16:]
new_hashed_password = hashlib.scrypt(plain_password.encode("utf-8"), salt=salt, n=8192, r=8, p=1, dklen=64)
return new_hashed_password == stored_password
def get_password_hash(password: str) -> str:
salt = os.urandom(16)
hashed_password = hashlib.scrypt(password.encode("utf-8"), salt=salt, n=8192, r=8, p=1, dklen=64)
salt_and_hash = salt + hashed_password
return base64.b64encode(salt_and_hash).decode("utf-8")
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=settings.SERGE_SESSION_EXPIRY)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, settings.SERGE_JWT_SECRET, algorithm=ALGORITHM)
return encoded_jwt
def decode_access_token(token: str):
try:
payload = jwt.decode(token, settings.SERGE_JWT_SECRET, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
return username
except JWTError:
raise credentials_exception

View File

@ -1,7 +1,7 @@
services:
serge:
restart: on-failure
build:
build:
context: .
dockerfile: Dockerfile.dev
volumes:
@ -11,9 +11,9 @@ services:
- weights:/usr/src/app/weights/
- /etc/localtime:/etc/localtime:ro
ports:
- 8008:8008
- 9124:9124
- 5678:5678
- "8008:8008"
- "9124:9124"
volumes:
datadb:
weights:

View File

@ -21,13 +21,12 @@ detect_cpu_features() {
}
# Check if the CPU architecture is aarch64/arm64
if [ "$cpu_arch" = "aarch64" ] || [ "$cpu_arch" = "arm64" ]; then
pip_command="python -m pip install -v llama-cpp-python==$LLAMA_PYTHON_VERSION --only-binary=:all: --extra-index-url=https://abetlen.github.io/llama-cpp-python/whl/cpu/"
if [ "$cpu_arch" = "aarch64" ]; then
pip_command="python -m pip install -v llama-cpp-python==$LLAMA_PYTHON_VERSION --only-binary=:all: --extra-index-url=https://gaby.github.io/arm64-wheels/"
else
# Use @smartappli provided wheels
#cpu_feature=$(detect_cpu_features)
#pip_command="python -m pip install -v llama-cpp-python==$LLAMA_PYTHON_VERSION --only-binary=:all: --extra-index-url=https://abetlen.github.io/llama-cpp-python/whl/cpu-$cpu_feature/"
pip_command="python -m pip install -v llama-cpp-python==$LLAMA_PYTHON_VERSION --only-binary=:all: --extra-index-url=https://abetlen.github.io/llama-cpp-python/whl/cpu/"
cpu_feature=$(detect_cpu_features)
pip_command="python -m pip install -v llama-cpp-python==$LLAMA_PYTHON_VERSION --only-binary=:all: --extra-index-url=https://smartappli.github.io/llama-cpp-python-cuBLAS-wheels/$cpu_feature/cpu"
fi
echo "Recommended install command for llama-cpp-python: $pip_command"
@ -52,11 +51,7 @@ redis_process=$!
# Start the API
cd /usr/src/app/api || exit 1
hypercorn_cmd="hypercorn src.serge.main:app --bind 0.0.0.0:8008"
if [ "$SERGE_ENABLE_IPV6" = true ] && [ "$SERGE_ENABLE_IPV4" != true ]; then
hypercorn_cmd="hypercorn src.serge.main:app --bind [::]:8008"
elif [ "$SERGE_ENABLE_IPV4" = true ] && [ "$SERGE_ENABLE_IPV6" = true ]; then
hypercorn_cmd="hypercorn src.serge.main:app --bind 0.0.0.0:8008 --bind [::]:8008"
fi
[ "$SERGE_ENABLE_IPV6" = true ] && hypercorn_cmd+=" --bind [::]:8008"
$hypercorn_cmd || {
echo 'Failed to start main app'

View File

@ -21,13 +21,12 @@ detect_cpu_features() {
}
# Check if the CPU architecture is aarch64/arm64
if [ "$cpu_arch" = "aarch64" ] || [ "$cpu_arch" = "arm64" ]; then
pip_command="python -m pip install -v llama-cpp-python==$LLAMA_PYTHON_VERSION --only-binary=:all: --extra-index-url=https://abetlen.github.io/llama-cpp-python/whl/cpu/"
if [ "$cpu_arch" = "aarch64" ]; then
pip_command="python -m pip install -v llama-cpp-python==$LLAMA_PYTHON_VERSION --only-binary=:all: --extra-index-url=https://gaby.github.io/arm64-wheels/"
else
# Use @smartappli provided wheels
#cpu_feature=$(detect_cpu_features)
#pip_command="python -m pip install -v llama-cpp-python==$LLAMA_PYTHON_VERSION --only-binary=:all: --extra-index-url=https://abetlen.github.io/llama-cpp-python/whl/cpu-$cpu_feature/"
pip_command="python -m pip install -v llama-cpp-python==$LLAMA_PYTHON_VERSION --only-binary=:all: --extra-index-url=https://abetlen.github.io/llama-cpp-python/whl/cpu/"
cpu_feature=$(detect_cpu_features)
pip_command="python -m pip install -v llama-cpp-python==$LLAMA_PYTHON_VERSION --only-binary=:all: --extra-index-url=https://smartappli.github.io/llama-cpp-python-cuBLAS-wheels/$cpu_feature/cpu"
fi
echo "Recommended install command for llama-cpp-python: $pip_command"
@ -57,16 +56,10 @@ redis-server /etc/redis/redis.conf &
cd /usr/src/app/web || exit 1
npm run dev -- --host 0.0.0.0 --port 8008 &
python -m pip install debugpy -t /tmp
# Start the API
cd /usr/src/app/api || exit 1
hypercorn_cmd="python /tmp/debugpy --listen 0.0.0.0:5678 -m hypercorn src.serge.main:api_app --reload --bind 0.0.0.0:9124"
if [ "$SERGE_ENABLE_IPV6" = true ] && [ "$SERGE_ENABLE_IPV4" != true ]; then
hypercorn_cmd="python /tmp/debugpy --listen 0.0.0.0:5678 -m hypercorn src.serge.main:api_app --reload --bind [::]:9124"
elif [ "$SERGE_ENABLE_IPV4" = true ] && [ "$SERGE_ENABLE_IPV6" = true ]; then
hypercorn_cmd="python /tmp/debugpy --listen 0.0.0.0:5678 -m hypercorn src.serge.main:api_app --reload --bind 0.0.0.0:9124 --bind [::]:9124"
fi
hypercorn_cmd="hypercorn src.serge.main:app --bind 0.0.0.0:8008"
[ "$SERGE_ENABLE_IPV6" = true ] && hypercorn_cmd+=" --bind [::]:8008"
$hypercorn_cmd || {
echo 'Failed to start main app'

View File

@ -1,3 +1,2 @@
LLAMA_PYTHON_VERSION=0.2.87
SERGE_ENABLE_IPV4=true
LLAMA_PYTHON_VERSION=0.2.39
SERGE_ENABLE_IPV6=false

View File

@ -1,3 +1,3 @@
typing-extensions>=4.12.2
numpy>=1.26.0,<2.0.0
diskcache>=5.6.3
typing-extensions>=4.5.0
numpy>=1.20.0
diskcache>=5.6.1

2067
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,41 +12,40 @@
"format": "prettier --write ."
},
"devDependencies": {
"@sveltejs/adapter-auto": "^3.2.2",
"@sveltejs/adapter-node": "^5.2.0",
"@sveltejs/adapter-static": "^3.0.2",
"@sveltejs/kit": "^2.5.20",
"@sveltejs/vite-plugin-svelte": "^3.1.1",
"@types/markdown-it": "^14.1.2",
"@typescript-eslint/eslint-plugin": "^7.17.0",
"@typescript-eslint/parser": "^7.18.0",
"autoprefixer": "^10.4.20",
"eslint": "^8.57.0",
"@sveltejs/adapter-auto": "^3.1.1",
"@sveltejs/adapter-node": "^4.0.1",
"@sveltejs/adapter-static": "^3.0.1",
"@sveltejs/kit": "^2.5.0",
"@sveltejs/vite-plugin-svelte": "^3.0.2",
"@types/markdown-it": "^13.0.7",
"@typescript-eslint/eslint-plugin": "^7.0.1",
"@typescript-eslint/parser": "^7.0.1",
"autoprefixer": "^10.4.17",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-svelte": "^2.43.0",
"eslint-plugin-vue": "^9.27.0",
"postcss": "^8.4.40",
"prettier": "3.3.3",
"prettier-plugin-svelte": "^3.2.6",
"svelte": "^4.2.18",
"svelte-check": "^3.8.5",
"tailwindcss": "^3.4.7",
"tslib": "^2.6.3",
"typescript": "^5.5.4",
"vite": "^5.4.1"
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-svelte": "^2.35.1",
"eslint-plugin-vue": "^9.21.1",
"postcss": "^8.4.35",
"prettier": "3.2.5",
"prettier-plugin-svelte": "^3.2.0",
"svelte": "^4.2.10",
"svelte-check": "^3.6.4",
"tailwindcss": "^3.4.1",
"tslib": "^2.6.2",
"typescript": "^5.3.3",
"vite": "^5.1.1"
},
"type": "module",
"dependencies": {
"@iconify/svelte": "^4.0.2",
"@iconify/svelte": "^3.1.6",
"@sveltestack/svelte-query": "^1.6.0",
"clipboard": "^2.0.11",
"daisyui": "^4.12.10",
"highlight.js": "^11.10.0",
"ioredis": "^5.4.1",
"markdown-it": "^14.1.0",
"markdown-it-highlightjs": "^4.1.0",
"prettier-plugin-tailwindcss": "^0.6.5"
"daisyui": "^4.7.2",
"highlight.js": "^11.9.0",
"markdown-it": "^14.0.0",
"markdown-it-highlightjs": "^4.0.1",
"prettier-plugin-tailwindcss": "^0.5.11"
}
}

View File

@ -18,7 +18,7 @@
width: auto;
}
markdown .hljs {
markdown. .hljs {
background: hsl(var(--b3)) !important;
}
@ -90,11 +90,9 @@ markdown .hljs {
.models-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 25px;
padding-left: 80px;
padding-right: 40px;
padding-top: 40px;
padding-bottom: 10px;
gap: 20px;
padding: 30px;
padding-top: 10px;
}
/* Model Accordion Styles */
@ -134,44 +132,9 @@ markdown .hljs {
}
.search-row {
position: fixed;
top: 5px;
left: 0;
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding-left: 80px;
padding-right: 40px;
padding-bottom: 0px;
}
main {
max-width: 600px;
margin: 0 auto;
padding: 1rem;
}
form {
display: flex;
flex-direction: column;
}
div {
margin-bottom: 1rem;
}
label {
display: block;
margin-bottom: 0.5rem;
}
input {
width: 100%;
padding: 0.5rem;
box-sizing: border-box;
}
button {
padding: 0.5rem 1rem;
padding: 0 2rem;
}

View File

@ -8,8 +8,6 @@
import { fly } from "svelte/transition";
export let data: PageData;
export let isSidebarOpen: boolean = true;
let models;
let modelAvailable: boolean;
const isLoading = false;
@ -20,14 +18,6 @@
let dataCht: Response | any = null;
const unsubscribe = newChat.subscribe((value) => (dataCht = value));
function toggleSidebar(): void {
isSidebarOpen = !isSidebarOpen;
}
function hideSidebar(): void {
isSidebarOpen = false;
}
onMount(() => {
theme = localStorage.getItem("data-theme") || "dark";
document.documentElement.setAttribute("data-theme", theme);
@ -120,49 +110,25 @@
});
</script>
<button
on:click={toggleSidebar}
class="border-base-content/[.2] btn btn-square z-10 my-1 mx-2 fixed border"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="inline-block w-5 h-5 stroke-current"
><path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"
></path></svg
>
</button>
<aside
class="border-base-content/[.2] fixed top-0 z-40 min-h-full border-r transition-all overflow-hidden aria-label=Sidebar"
class:left-0={isSidebarOpen}
class:-left-80={!isSidebarOpen}
id="default-sidebar"
class="border-base-content/[.2] fixed left-0 top-0 z-40 h-screen w-80 -translate-x-full border-r transition-transform overflow-hidden translate-x-0 aria-label=Sidebar"
>
<div
class="bg-base-200 relative h-screen py-1 px-2 overflow-hidden flex flex-col items-center justify-between"
>
<div class="w-full flex items-center pb-1">
<button
on:click={toggleSidebar}
class="border-base-content/[.2] btn btn-square border"
>
<div
class="w-full flex items-center border-b border-base-content/[.2] pb-1"
>
<button class="btn btn-ghost flex-shrink-0" on:click={goToHome}>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="inline-block w-5 h-5 stroke-current"
><path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"
></path></svg
fill="currentColor"
class="w-5 h-5"
>
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" />
</svg>
</button>
<button
disabled={isLoading || !modelAvailable}
@ -184,78 +150,75 @@
</svg>
<span>New Chat</span>
</button>
<button class="btn btn-ghost flex-shrink-0" on:click={goToHome}>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="w-5 h-5"
>
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" />
</svg>
<span class="sr-only">Home</span>
</button>
</div>
<ul
class="my-1 w-full flex-grow overflow-y-auto no-scrollbar firefox-no-scrollbar ie-edge-no-scrollbar"
class="my-1 w-full h-[85%] overflow-y-auto no-scrollbar firefox-no-scrollbar ie-edge-no-scrollbar"
>
{#if data && Symbol.iterator in Object(data.chats)}
{#each data.chats as chat (chat.id)}
<li in:fly={{ x: -100, duration: 900 }}>
<a
href={"/chat/" + chat.id}
class="group hover:from-base-100 hover:text-base-content flex items-center rounded-lg py-2 pl-2 text-base font-normal hover:bg-gradient-to-r hover:to-transparent"
class:bg-base-300={id === chat.id}
>
<div
class="flex w-full flex-col space-y-2 p-2 border-b border-gray-200 relative"
>
{#each data.chats as chat (chat.id)}
<li in:fly={{ x: -100, duration: 900 }}>
<a
href={"/chat/" + chat.id}
class="group hover:from-base-100 hover:text-base-content flex items-center rounded-lg py-2 pl-2 text-base font-normal hover:bg-gradient-to-r hover:to-transparent"
class:bg-base-300={id === chat.id}
>
<div class="flex w-full flex-col">
<div class="flex w-full flex-col items-start justify-start">
<div
class="flex w-full flex-col items-start justify-start space-y-1"
class="relative flex w-full flex-row items-center justify-between"
>
<div
class="flex w-full flex-row items-center justify-between"
>
<div class="flex flex-col space-y-1.5">
<p class="text-sm font-light max-w-[25ch] break-words">
{truncate(chat.subtitle, 100)}
</p>
<span
class="text-xs font-semibold max-w-[25ch] break-words"
>{chat.model}</span
>
<span class="text-xs"
>{timeSince(chat.created) + " ago"}</span
>
</div>
<div class="flex flex-col">
<p class="text-sm font-light">
{truncate(chat.subtitle, 42)}
</p>
<span class="text-xs font-semibold">{chat.model}</span>
<span class="text-xs"
>{timeSince(chat.created) + " ago"}</span
>
</div>
</div>
<div
class="absolute bottom-1.5 right-2 opacity-0 group-hover:opacity-100 transition-opacity duration-300"
>
{#if deleteConfirm}
<div class="flex flex-row items-center space-x-2">
<button
name="confirm-delete"
class="btn btn-sm btn"
on:click|preventDefault={() => deleteChat(chat.id)}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
width="16"
height="16"
<div
class="absolute right-0 opacity-0 group-hover:opacity-100 transition"
>
<!-- {#if $page.params.id === chat.id} -->
{#if deleteConfirm}
<div class="flex flex-row items-center">
<button
name="confirm-delete"
class="btn-ghost btn-sm btn"
on:click|preventDefault={() => deleteChat(chat.id)}
>
<path
class="fill-base-content"
d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm1.5 0a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm10.28-1.72-4.5 4.5a.75.75 0 0 1-1.06 0l-2-2a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018l1.47 1.47 3.97-3.97a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z"
/>
</svg>
</button>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
width="16"
height="16"
>
<path
class="fill-base-content"
d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm1.5 0a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm10.28-1.72-4.5 4.5a.75.75 0 0 1-1.06 0l-2-2a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018l1.47 1.47 3.97-3.97a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z"
/>
</svg>
</button>
<button
name="cancel-delete"
class="btn-ghost btn-sm btn"
on:click|preventDefault={toggleDeleteConfirm}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
width="16"
height="16"
>
<path
class="fill-base-content"
d="M2.344 2.343h-.001a8 8 0 0 1 11.314 11.314A8.002 8.002 0 0 1 .234 10.089a8 8 0 0 1 2.11-7.746Zm1.06 10.253a6.5 6.5 0 1 0 9.108-9.275 6.5 6.5 0 0 0-9.108 9.275ZM6.03 4.97 8 6.94l1.97-1.97a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l1.97 1.97a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-1.97 1.97a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734L6.94 8 4.97 6.03a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018Z"
/>
</svg>
</button>
</div>
{:else}
<button
name="cancel-delete"
class="btn btn-sm btn"
class="btn-ghost btn-sm btn"
on:click|preventDefault={toggleDeleteConfirm}
>
<svg
@ -266,189 +229,26 @@
>
<path
class="fill-base-content"
d="M2.344 2.343h-.001a8 8 0 0 1 11.314 11.314A8.002 8.002 0 0 1 .234 10.089a8 8 0 0 1 2.11-7.746Zm1.06 10.253a6.5 6.5 0 1 0 9.108-9.275 6.5 6.5 0 0 0-9.108 9.275ZM6.03 4.97 8 6.94l1.97-1.97a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l1.97 1.97a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-1.97 1.97a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734L6.94 8 4.97 6.03a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018Z"
d="M11 1.75V3h2.25a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1 0-1.5H5V1.75C5 .784 5.784 0 6.75 0h2.5C10.216 0 11 .784 11 1.75ZM4.496 6.675l.66 6.6a.25.25 0 0 0 .249.225h5.19a.25.25 0 0 0 .249-.225l.66-6.6a.75.75 0 0 1 1.492.149l-.66 6.6A1.748 1.748 0 0 1 10.595 15h-5.19a1.75 1.75 0 0 1-1.741-1.575l-.66-6.6a.75.75 0 1 1 1.492-.15ZM6.5 1.75V3h3V1.75a.25.25 0 0 0-.25-.25h-2.5a.25.25 0 0 0-.25.25Z"
/>
</svg>
</button>
</div>
{:else}
<button
class="btn btn-sm btn"
on:click|preventDefault={toggleDeleteConfirm}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
width="16"
height="16"
>
<path
class="fill-base-content"
d="M11 1.75V3h2.25a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1 0-1.5H5V1.75C5 .784 5.784 0 6.75 0h2.5C10.216 0 11 .784 11 1.75ZM4.496 6.675l.66 6.6a.25.25 0 0 0 .249.225h5.19a.25.25 0 0 0 .249-.225l.66-6.6a.75.75 0 0 1 1.492.149l-.66 6.6A1.748 1.748 0 0 1 10.595 15h-5.19a1.75 1.75 0 0 1-1.741-1.575l-.66-6.6a.75.75 0 1 1 1.492-.15ZM6.5 1.75V3h3V1.75a.25.25 0 0 0-.25-.25h-2.5a.25.25 0 0 0-.25.25Z"
/>
</svg>
</button>
{/if}
{/if}
<!-- {/if} -->
</div>
</div>
</div>
</a>
</li>
{/each}
{/if}
</div>
</a>
</li>
{/each}
</ul>
<div class="w-full border-t border-base-content/[.2] pt-1">
{#if data.userData?.username === "system"}
{#if deleteAllConfirm}
<button
name="login-btn"
class="btn btn-ghost w-full flex justify-start items-center p-2.5 text-left text-sm capitalize"
on:click={() => goto("/login")}
class="btn btn-ghost w-full flex flex-row justify-between items-center p-2.5 text-left text-sm capitalize"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
fill="currentColor"
class="mr-3"
viewBox="0 0 16 16"
>
<path
d="M12.5 16a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7m1.679-4.493-1.335 2.226a.75.75 0 0 1-1.174.144l-.774-.773a.5.5 0 0 1 .708-.708l.547.548 1.17-1.951a.5.5 0 1 1 .858.514M11 5a3 3 0 1 1-6 0 3 3 0 0 1 6 0M8 7a2 2 0 1 0 0-4 2 2 0 0 0 0 4"
/>
<path
d="M8.256 14a4.5 4.5 0 0 1-.229-1.004H3c.001-.246.154-.986.832-1.664C4.484 10.68 5.711 10 8 10q.39 0 .74.025c.226-.341.496-.65.804-.918Q8.844 9.002 8 9c-5 0-6 3-6 4s1 1 1 1z"
/>
</svg>
<span>Login</span>
</button>
<button
name="create-btn"
class="btn btn-ghost w-full flex justify-start items-center p-2.5 text-left text-sm capitalize"
on:click={() => goto("/signup")}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
fill="currentColor"
class="mr-3"
viewBox="0 0 16 16"
>
<path
d="M12.5 16a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7m.5-5v1h1a.5.5 0 0 1 0 1h-1v1a.5.5 0 0 1-1 0v-1h-1a.5.5 0 0 1 0-1h1v-1a.5.5 0 0 1 1 0m-2-6a3 3 0 1 1-6 0 3 3 0 0 1 6 0M8 7a2 2 0 1 0 0-4 2 2 0 0 0 0 4"
/>
<path
d="M8.256 14a4.5 4.5 0 0 1-.229-1.004H3c.001-.246.154-.986.832-1.664C4.484 10.68 5.711 10 8 10q.39 0 .74.025c.226-.341.496-.65.804-.918Q8.844 9.002 8 9c-5 0-6 3-6 4s1 1 1 1z"
/>
</svg>
<span>Create Account</span>
</button>
{:else}
<button
name="logout-btn"
class="btn btn-ghost w-full flex justify-start items-center p-2.5 text-left text-sm capitalize"
on:click={async () => {
const response = await fetch("/api/auth/logout", {
method: "POST",
});
data.userData = null;
window.location.href = "/";
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
fill="currentColor"
class="mr-3"
viewBox="0 0 16 16"
>
<path
d="M11 5a3 3 0 1 1-6 0 3 3 0 0 1 6 0M8 7a2 2 0 1 0 0-4 2 2 0 0 0 0 4m0 5.996V14H3s-1 0-1-1 1-4 6-4q.845.002 1.544.107a4.5 4.5 0 0 0-.803.918A11 11 0 0 0 8 10c-2.29 0-3.516.68-4.168 1.332-.678.678-.83 1.418-.832 1.664zM9 13a1 1 0 0 1 1-1v-1a2 2 0 1 1 4 0v1a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-4a1 1 0 0 1-1-1zm3-3a1 1 0 0 0-1 1v1h2v-1a1 1 0 0 0-1-1"
/>
</svg>
<span>Log Out</span>
</button>
<a
href="/account"
class="btn btn-ghost w-full flex justify-start items-center p-2.5 text-left text-sm capitalize"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
width="18"
height="18"
fill="currentColor"
class="mr-3"
>
<path
d="M8 0a8.2 8.2 0 0 1 .701.031C9.444.095 9.99.645 10.16 1.29l.288 1.107c.018.066.079.158.212.224.231.114.454.243.668.386.123.082.233.09.299.071l1.103-.303c.644-.176 1.392.021 1.82.63.27.385.506.792.704 1.218.315.675.111 1.422-.364 1.891l-.814.806c-.049.048-.098.147-.088.294.016.257.016.515 0 .772-.01.147.038.246.088.294l.814.806c.475.469.679 1.216.364 1.891a7.977 7.977 0 0 1-.704 1.217c-.428.61-1.176.807-1.82.63l-1.102-.302c-.067-.019-.177-.011-.3.071a5.909 5.909 0 0 1-.668.386c-.133.066-.194.158-.211.224l-.29 1.106c-.168.646-.715 1.196-1.458 1.26a8.006 8.006 0 0 1-1.402 0c-.743-.064-1.289-.614-1.458-1.26l-.289-1.106c-.018-.066-.079-.158-.212-.224a5.738 5.738 0 0 1-.668-.386c-.123-.082-.233-.09-.299-.071l-1.103.303c-.644.176-1.392-.021-1.82-.63a8.12 8.12 0 0 1-.704-1.218c-.315-.675-.111-1.422.363-1.891l.815-.806c.05-.048.098-.147.088-.294a6.214 6.214 0 0 1 0-.772c.01-.147-.038-.246-.088-.294l-.815-.806C.635 6.045.431 5.298.746 4.623a7.92 7.92 0 0 1 .704-1.217c.428-.61 1.176-.807 1.82-.63l1.102.302c.067.019.177.011.3-.071.214-.143.437-.272.668-.386.133-.066.194-.158.211-.224l.29-1.106C6.009.645 6.556.095 7.299.03 7.53.01 7.764 0 8 0Zm-.571 1.525c-.036.003-.108.036-.137.146l-.289 1.105c-.147.561-.549.967-.998 1.189-.173.086-.34.183-.5.29-.417.278-.97.423-1.529.27l-1.103-.303c-.109-.03-.175.016-.195.045-.22.312-.412.644-.573.99-.014.031-.021.11.059.19l.815.806c.411.406.562.957.53 1.456a4.709 4.709 0 0 0 0 .582c.032.499-.119 1.05-.53 1.456l-.815.806c-.081.08-.073.159-.059.19.162.346.353.677.573.989.02.03.085.076.195.046l1.102-.303c.56-.153 1.113-.008 1.53.27.161.107.328.204.501.29.447.222.85.629.997 1.189l.289 1.105c.029.109.101.143.137.146a6.6 6.6 0 0 0 1.142 0c.036-.003.108-.036.137-.146l.289-1.105c.147-.561.549-.967.998-1.189.173-.086.34-.183.5-.29.417-.278.97-.423 1.529-.27l1.103.303c.109.029.175-.016.195-.045.22-.313.411-.644.573-.99.014-.031.021-.11-.059-.19l-.815-.806c-.411-.406-.562-.957-.53-1.456a4.709 4.709 0 0 0 0-.582c-.032-.499.119-1.05.53-1.456l.815-.806c.081-.08.073-.159.059-.19a6.464 6.464 0 0 0-.573-.989c-.02-.03-.085-.076-.195-.046l-1.102.303c-.56.153-1.113.008-1.53-.27a4.44 4.44 0 0 0-.501-.29c-.447-.222-.85-.629-.997-1.189l-.289-1.105c-.029-.11-.101-.143-.137-.146a6.6 6.6 0 0 0-1.142 0ZM11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0ZM9.5 8a1.5 1.5 0 1 0-3.001.001A1.5 1.5 0 0 0 9.5 8Z"
>
</path>
</svg>
<span>Settings</span>
</a>
{#if deleteAllConfirm}
<button
class="btn btn-ghost w-full flex flex-row justify-between items-center p-2.5 text-left text-sm capitalize"
>
<div class="h-6 flex flex-row items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
width="18"
height="18"
fill="currentColor"
class="mr-3"
>
<path
d="M11 1.75V3h2.25a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1 0-1.5H5V1.75C5 .784 5.784 0 6.75 0h2.5C10.216 0 11 .784 11 1.75ZM4.496 6.675l.66 6.6a.25.25 0 0 0 .249.225h5.19a.25.25 0 0 0 .249-.225l.66-6.6a.75.75 0 0 1 1.492.149l-.66 6.6A1.748 1.748 0 0 1 10.595 15h-5.19a1.75 1.75 0 0 1-1.741-1.575l-.66-6.6a.75.75 0 1 1 1.492-.15ZM6.5 1.75V3h3V1.75a.25.25 0 0 0-.25-.25h-2.5a.25.25 0 0 0-.25.25Z"
>
</path>
</svg>
<span>Clear Chats</span>
</div>
<div class="h-6 flex flex-row items-center">
<button
name="confirm-delete"
class="btn-ghost btn-sm btn"
on:click|preventDefault={() => deleteAllChat()}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
width="16"
height="16"
>
<path
class="fill-base-content"
d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm1.5 0a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm10.28-1.72-4.5 4.5a.75.75 0 0 1-1.06 0l-2-2a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018l1.47 1.47 3.97-3.97a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z"
/>
</svg>
</button>
<button
name="cancel-delete"
class="btn-ghost btn-sm btn"
on:click|preventDefault={toggleDeleteAllConfirm}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
width="16"
height="16"
>
<path
class="fill-base-content"
d="M2.344 2.343h-.001a8 8 0 0 1 11.314 11.314A8.002 8.002 0 0 1 .234 10.089a8 8 0 0 1 2.11-7.746Zm1.06 10.253a6.5 6.5 0 1 0 9.108-9.275 6.5 6.5 0 0 0-9.108 9.275ZM6.03 4.97 8 6.94l1.97-1.97a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l1.97 1.97a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-1.97 1.97a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734L6.94 8 4.97 6.03a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018Z"
/>
</svg>
</button>
</div>
</button>
{:else}
<button
on:click|preventDefault={toggleDeleteAllConfirm}
class="btn btn-ghost w-full flex justify-start items-center p-2.5 text-left text-sm capitalize"
>
<div class="h-6 flex flex-row items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
@ -463,14 +263,71 @@
</path>
</svg>
<span>Clear Chats</span>
</button>
{/if}
</div>
<div class="h-6 flex flex-row items-center">
<button
name="confirm-delete"
class="btn-ghost btn-sm btn"
on:click|preventDefault={() => deleteAllChat()}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
width="16"
height="16"
>
<path
class="fill-base-content"
d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm1.5 0a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm10.28-1.72-4.5 4.5a.75.75 0 0 1-1.06 0l-2-2a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018l1.47 1.47 3.97-3.97a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z"
/>
</svg>
</button>
<button
name="cancel-delete"
class="btn-ghost btn-sm btn"
on:click|preventDefault={toggleDeleteAllConfirm}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
width="16"
height="16"
>
<path
class="fill-base-content"
d="M2.344 2.343h-.001a8 8 0 0 1 11.314 11.314A8.002 8.002 0 0 1 .234 10.089a8 8 0 0 1 2.11-7.746Zm1.06 10.253a6.5 6.5 0 1 0 9.108-9.275 6.5 6.5 0 0 0-9.108 9.275ZM6.03 4.97 8 6.94l1.97-1.97a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l1.97 1.97a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-1.97 1.97a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734L6.94 8 4.97 6.03a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018Z"
/>
</svg>
</button>
</div>
</button>
{:else}
<button
on:click|preventDefault={toggleDeleteAllConfirm}
class="btn btn-ghost w-full flex justify-start items-center p-2.5 text-left text-sm capitalize"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
width="18"
height="18"
fill="currentColor"
class="mr-3"
>
<path
d="M11 1.75V3h2.25a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1 0-1.5H5V1.75C5 .784 5.784 0 6.75 0h2.5C10.216 0 11 .784 11 1.75ZM4.496 6.675l.66 6.6a.25.25 0 0 0 .249.225h5.19a.25.25 0 0 0 .249-.225l.66-6.6a.75.75 0 0 1 1.492.149l-.66 6.6A1.748 1.748 0 0 1 10.595 15h-5.19a1.75 1.75 0 0 1-1.741-1.575l-.66-6.6a.75.75 0 1 1 1.492-.15ZM6.5 1.75V3h3V1.75a.25.25 0 0 0-.25-.25h-2.5a.25.25 0 0 0-.25.25Z"
>
</path>
</svg>
<span>Clear Chats</span>
</button>
{/if}
<button
on:click={toggleTheme}
class="btn btn-ghost w-full flex justify-start items-center p-2.5 text-left text-sm capitalize"
>
<label class="swap swap-rotate" for="theme-toggle">
<label class="swap swap-rotate">
<input type="checkbox" />
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
@ -500,12 +357,29 @@
</label>
<span>{theme == "dark" ? "Light" : "Dark"} theme</span>
</button>
<a
href="/"
class="btn btn-ghost w-full flex justify-start items-center p-2.5 text-left text-sm capitalize"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
width="18"
height="18"
fill="currentColor"
class="mr-3"
>
<path
d="M8 0a8.2 8.2 0 0 1 .701.031C9.444.095 9.99.645 10.16 1.29l.288 1.107c.018.066.079.158.212.224.231.114.454.243.668.386.123.082.233.09.299.071l1.103-.303c.644-.176 1.392.021 1.82.63.27.385.506.792.704 1.218.315.675.111 1.422-.364 1.891l-.814.806c-.049.048-.098.147-.088.294.016.257.016.515 0 .772-.01.147.038.246.088.294l.814.806c.475.469.679 1.216.364 1.891a7.977 7.977 0 0 1-.704 1.217c-.428.61-1.176.807-1.82.63l-1.102-.302c-.067-.019-.177-.011-.3.071a5.909 5.909 0 0 1-.668.386c-.133.066-.194.158-.211.224l-.29 1.106c-.168.646-.715 1.196-1.458 1.26a8.006 8.006 0 0 1-1.402 0c-.743-.064-1.289-.614-1.458-1.26l-.289-1.106c-.018-.066-.079-.158-.212-.224a5.738 5.738 0 0 1-.668-.386c-.123-.082-.233-.09-.299-.071l-1.103.303c-.644.176-1.392-.021-1.82-.63a8.12 8.12 0 0 1-.704-1.218c-.315-.675-.111-1.422.363-1.891l.815-.806c.05-.048.098-.147.088-.294a6.214 6.214 0 0 1 0-.772c.01-.147-.038-.246-.088-.294l-.815-.806C.635 6.045.431 5.298.746 4.623a7.92 7.92 0 0 1 .704-1.217c.428-.61 1.176-.807 1.82-.63l1.102.302c.067.019.177.011.3-.071.214-.143.437-.272.668-.386.133-.066.194-.158.211-.224l.29-1.106C6.009.645 6.556.095 7.299.03 7.53.01 7.764 0 8 0Zm-.571 1.525c-.036.003-.108.036-.137.146l-.289 1.105c-.147.561-.549.967-.998 1.189-.173.086-.34.183-.5.29-.417.278-.97.423-1.529.27l-1.103-.303c-.109-.03-.175.016-.195.045-.22.312-.412.644-.573.99-.014.031-.021.11.059.19l.815.806c.411.406.562.957.53 1.456a4.709 4.709 0 0 0 0 .582c.032.499-.119 1.05-.53 1.456l-.815.806c-.081.08-.073.159-.059.19.162.346.353.677.573.989.02.03.085.076.195.046l1.102-.303c.56-.153 1.113-.008 1.53.27.161.107.328.204.501.29.447.222.85.629.997 1.189l.289 1.105c.029.109.101.143.137.146a6.6 6.6 0 0 0 1.142 0c.036-.003.108-.036.137-.146l.289-1.105c.147-.561.549-.967.998-1.189.173-.086.34-.183.5-.29.417-.278.97-.423 1.529-.27l1.103.303c.109.029.175-.016.195-.045.22-.313.411-.644.573-.99.014-.031.021-.11-.059-.19l-.815-.806c-.411-.406-.562-.957-.53-1.456a4.709 4.709 0 0 0 0-.582c-.032-.499.119-1.05.53-1.456l.815-.806c.081-.08.073-.159.059-.19a6.464 6.464 0 0 0-.573-.989c-.02-.03-.085-.076-.195-.046l-1.102.303c-.56.153-1.113.008-1.53-.27a4.44 4.44 0 0 0-.501-.29c-.447-.222-.85-.629-.997-1.189l-.289-1.105c-.029-.11-.101-.143-.137-.146a6.6 6.6 0 0 0-1.142 0ZM11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0ZM9.5 8a1.5 1.5 0 1 0-3.001.001A1.5 1.5 0 0 0 9.5 8Z"
>
</path>
</svg>
<span>Settings</span>
</a>
</div>
</div>
</aside>
<button on:click={hideSidebar} type="button"></button>
<div id="main_content" class="h-full w-full">
<div class={"relative h-full transition-all md:ml-80"}>
<slot />
</div>

View File

@ -7,8 +7,6 @@ interface ChatMetadata {
subtitle: string;
}
export const ssr = false; // off for now because ssr with auth is broken
export interface ModelStatus {
name: string;
size: number;
@ -16,32 +14,14 @@ export interface ModelStatus {
progress?: number;
}
export interface User {
id: string;
username: string;
email: string;
pref_theme: "light" | "dark";
full_name: string;
default_prompt: string;
}
export const load: LayoutLoad = async ({ fetch }) => {
let userData: User | null = null;
const api_chat = await fetch("/api/chat/");
const chats = (await api_chat.json()) as ChatMetadata[];
const model_api = await fetch("/api/model/all");
const models = (await model_api.json()) as ModelStatus[];
const userData_api = await fetch("/api/user/");
if (userData_api.ok) {
userData = (await userData_api.json()) as User;
}
return {
chats,
models,
userData,
};
};

View File

@ -17,7 +17,6 @@
let repeat_penalty = 1.3;
let init_prompt =
data.userData?.default_prompt ??
"Below is an instruction that describes a task. Write a response that appropriately completes the request.";
let n_threads = 4;
@ -54,12 +53,7 @@
An easy way to chat with LLaMA based models.
</h1>
<form
on:submit|preventDefault={onCreateChat}
id="form-create-chat"
class="p-5"
aria-label="Model Settings"
>
<form on:submit|preventDefault={onCreateChat} id="form-create-chat" class="p-5">
<div class="w-full pb-20">
<div class="mx-auto w-fit pt-5 flex flex-col lg:flex-row justify-center">
<button
@ -74,181 +68,175 @@
>
</div>
</div>
<div class="flex justify-center">
<div class="grid grid-cols-3 gap-4 p-3 bg-base-200" id="model_settings">
<div class="col-span-3 text-xl font-medium">Model settings</div>
<div
class="tooltip tooltip-bottom col-span-2"
data-tip="Controls how random the generated text is. Higher temperatures lead to more random and creative text, while lower temperatures lead to more predictable and conservative text."
>
<label for="temperature" class="label-text"
>Temperature - [{temp}]</label
<div tabindex="-1" class="collapse-arrow rounded-box collapse bg-base-200">
<input type="checkbox" />
<div class="collapse-title text-xl font-medium">Model settings</div>
<div class="collapse-content">
<div class="grid grid-cols-3 gap-4 p-3">
<div
class="tooltip tooltip-bottom col-span-2"
data-tip="Controls how random the generated text is. Higher temperatures lead to more random and creative text, while lower temperatures lead to more predictable and conservative text."
>
<input
id="temperature"
name="temperature"
type="range"
bind:value={temp}
min="0.05"
max="2"
step="0.05"
class="range range-sm mt-auto"
/>
</div>
<div
class="tooltip tooltip-bottom flex flex-col"
data-tip="Controls the number of tokens that are considered when generating the next token. Higher values of top_k lead to more predictable text, while lower values of top_k lead to more creative text."
>
<label for="top_k" class="label-text pb-1">top_k</label>
<input
id="top_k"
class="input-bordered input w-full"
name="top_k"
type="number"
bind:value={top_k}
min="0"
max="100"
/>
</div>
<div
class="tooltip tooltip-bottom col-span-2"
data-tip="The maximum number of tokens that the model will generate. This parameter can be used to control the length of the generated text."
>
<label for="max_length" class="label-text"
>Maximum generated tokens - [{max_length}]</label
<label for="temperature" class="label-text"
>Temperature - [{temp}]</label
>
<input
name="temperature"
type="range"
bind:value={temp}
min="0.05"
max="2"
step="0.05"
class="range range-sm mt-auto"
/>
</div>
<div
class="tooltip tooltip-bottom flex flex-col"
data-tip="Controls the number of tokens that are considered when generating the next token. Higher values of top_k lead to more predictable text, while lower values of top_k lead to more creative text."
>
<input
id="max_length"
name="max_length"
type="range"
bind:value={max_length}
min="32"
max="32768"
step="16"
class="range range-sm mt-auto"
/>
</div>
<div
class="tooltip flex flex-col"
data-tip="Controls the diversity of the generated text. Higher values of top_p lead to more diverse text, while lower values of top_p lead to less diverse text."
>
<label for="top_p" class="label-text pb-1">top_p</label>
<input
class="input-bordered input w-full"
id="top_p"
name="top_p"
type="number"
bind:value={top_p}
min="0"
max="1"
step="0.025"
/>
</div>
<div
class="tooltip col-span-2"
data-tip="The number of previous tokens that are considered when generating the next token. A longer context length can help the model to generate more coherent and informative text."
>
<label for="context_window" class="label-text"
>Context Length - [{context_window}]</label
<label for="top_k" class="label-text pb-1">top_k</label>
<input
class="input-bordered input w-full max-w-xs"
name="top_k"
type="number"
bind:value={top_k}
min="0"
max="100"
/>
</div>
<div
class="tooltip tooltip-bottom col-span-2"
data-tip="The maximum number of tokens that the model will generate. This parameter can be used to control the length of the generated text."
>
<input
id="context_window"
name="context_window"
type="range"
bind:value={context_window}
min="16"
max="2048"
step="16"
class="range range-sm mt-auto"
/>
</div>
<div
class="tooltip col-span-2"
data-tip="Number of layers to put on the GPU. The rest will be on the CPU."
>
<label for="gpu_layers" class="label-text"
>GPU Layers - [{gpu_layers}]</label
<label for="max_length" class="label-text"
>Maximum generated tokens - [{max_length}]</label
>
<input
name="max_length"
type="range"
bind:value={max_length}
min="32"
max="32768"
step="16"
class="range range-sm mt-auto"
/>
</div>
<div
class="tooltip flex flex-col"
data-tip="Controls the diversity of the generated text. Higher values of top_p lead to more diverse text, while lower values of top_p lead to less diverse text."
>
<input
id="gpu_layers"
name="gpu_layers"
type="range"
bind:value={gpu_layers}
min="0"
max="100"
step="1"
class="range range-sm mt-auto"
/>
</div>
<div
class="tooltip flex flex-col"
data-tip="Defines the penalty associated with repeating the last 'n' tokens in a generated text sequence."
>
<label for="repeat_last_n" class="label-text pb-1">repeat_last_n</label>
<input
id="repeat_last_n"
class="input-bordered input w-full"
name="repeat_last_n"
type="number"
bind:value={repeat_last_n}
min="0"
max="100"
/>
</div>
<div class="flex flex-col">
<label for="model" class="label-text pb-1"> Model choice</label>
<select
name="model"
id="models"
class="select-bordered select w-full"
aria-haspopup="menu"
<label for="top_p" class="label-text pb-1">top_p</label>
<input
class="input-bordered input w-full max-w-xs"
name="top_p"
type="number"
bind:value={top_p}
min="0"
max="1"
step="0.025"
/>
</div>
<div
class="tooltip col-span-2"
data-tip="The number of previous tokens that are considered when generating the next token. A longer context length can help the model to generate more coherent and informative text."
>
{#each modelsLabels as model}
<option id={model} value={model}>{model}</option>
{/each}
</select>
</div>
<div
class="tooltip flex flex-col"
data-tip="Number of threads to run LLaMA on."
>
<label for="n_threads" class="label-text pb-1">n_threads</label>
<input
id="n_threads"
class="input-bordered input w-full"
name="n_threads"
type="number"
bind:value={n_threads}
min="0"
max="64"
/>
</div>
<div
class="tooltip flex flex-col"
data-tip="Defines the penalty assigned to the model when it repeats certain tokens or patterns in the generated text."
>
<label for="repeat_penalty" class="label-text pb-1">
repeat_penalty
</label>
<input
id="repeat_penalty"
class="input-bordered input w-full"
name="repeat_penalty"
type="number"
bind:value={repeat_penalty}
min="0"
max="2"
step="0.05"
/>
</div>
<div class="col-span-3 flex flex-col">
<label for="init_prompt" class="label-text pb-1">Prompt Template</label>
<textarea
class="textarea-bordered textarea h-24 w-full"
name="init_prompt"
bind:value={init_prompt}
placeholder="Enter your prompt here"
/>
<label for="context_window" class="label-text"
>Context Length - [{context_window}]</label
>
<input
name="context_window"
type="range"
bind:value={context_window}
min="16"
max="2048"
step="16"
class="range range-sm mt-auto"
/>
</div>
<div
class="tooltip col-span-2"
data-tip="Number of layers to put on the GPU. The rest will be on the CPU."
>
<label for="gpu_layers" class="label-text"
>GPU Layers - [{gpu_layers}]</label
>
<input
name="gpu_layers"
type="range"
bind:value={gpu_layers}
min="0"
max="100"
step="1"
class="range range-sm mt-auto"
/>
</div>
<div
class="tooltip flex flex-col"
data-tip="Defines the penalty associated with repeating the last 'n' tokens in a generated text sequence."
>
<label for="repeat_last_n" class="label-text pb-1"
>repeat_last_n</label
>
<input
class="input-bordered input w-full max-w-xs"
name="repeat_last_n"
type="number"
bind:value={repeat_last_n}
min="0"
max="100"
/>
</div>
<div class="flex flex-col">
<label for="model" class="label-text pb-1"> Model choice</label>
<select name="model" class="select-bordered select w-full max-w-xs">
{#each modelsLabels as model}
<option value={model}>{model}</option>
{/each}
</select>
</div>
<div
class="tooltip flex flex-col"
data-tip="Number of threads to run LLaMA on."
>
<label for="n_threads" class="label-text pb-1">n_threads</label>
<input
class="input-bordered input w-full max-w-xs"
name="n_threads"
type="number"
bind:value={n_threads}
min="0"
max="64"
/>
</div>
<div
class="tooltip flex flex-col"
data-tip="Defines the penalty assigned to the model when it repeats certain tokens or patterns in the generated text."
>
<label for="repeat_penalty" class="label-text pb-1">
repeat_penalty
</label>
<input
class="input-bordered input w-full max-w-xs"
name="repeat_penalty"
type="number"
bind:value={repeat_penalty}
min="0"
max="2"
step="0.05"
/>
</div>
<div class="col-span-3 flex flex-col">
<label for="init_prompt" class="label-text pb-1"
>Prompt Template</label
>
<textarea
class="textarea-bordered textarea h-24 w-full"
name="init_prompt"
bind:value={init_prompt}
placeholder="Enter your prompt here"
/>
</div>
</div>
</div>
</div>

View File

@ -1,106 +0,0 @@
<script context="module" lang="ts">
export { load } from "./+page";
</script>
<script lang="ts">
import { writable } from "svelte/store";
import { goto } from "$app/navigation";
export let data: {
user: {
id: string;
username: string;
email: string;
full_name: string;
pref_theme: "light" | "dark";
default_prompt: string;
} | null;
};
let user = data.user;
let id: string = user?.id ?? "";
let username: string = user?.username ?? "";
let email: string = user?.email ?? "";
let full_name: string = user?.full_name ?? "";
let pref_theme: "light" | "dark" = user?.pref_theme ?? "light";
let default_prompt: string = user?.default_prompt ?? "";
let status = writable<string | null>(null);
async function handleSubmit(event: Event) {
event.preventDefault();
// Implement the update logic here, e.g., sending a PUT request to update user preferences
try {
await fetch("/api/user/", {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
id,
username,
email,
full_name,
pref_theme,
default_prompt,
}),
});
status.set("Preferences updated successfully");
goto("/", { invalidateAll: true });
} catch (error) {
if (error instanceof Error) {
status.set(error.message);
} else {
status.set("Failed to update preferences");
}
}
}
</script>
<main>
<div class="card-group">
<div class="card">
<div class="card-title p-3 text-3xl justify-center font-bold">
User Preferences
</div>
<div class="card-body">
{#if user}
<form on:submit={handleSubmit}>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">Username</span>
</div>
<input type="text" bind:value={username} disabled />
</div>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">Full Name</span>
</div>
<input id="full_name" type="text" bind:value={full_name} />
</div>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">Email</span>
</div>
<input id="email" type="email" bind:value={email} />
</div>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">Default Prompt</span>
</div>
<textarea
id="default_prompt"
bind:value={default_prompt}
style="resize:both; width:100%;"
/>
</div>
{#if $status}
<p>{$status}</p>
{/if}
<button class="btn" type="submit">Save Preferences</button>
</form>
{:else}
<p>Loading...</p>
{/if}
</div>
</div>
</div>
</main>

View File

@ -1,27 +0,0 @@
import type { Load } from "@sveltejs/kit";
interface User {
id: string;
username: string;
email: string;
pref_theme: "light" | "dark";
full_name: string;
default_prompt: string;
}
export const load: Load = async () => {
const user = await fetch("/api/user/", {
method: "GET",
})
.then((response) => {
if (response.status == 401) {
window.location.href = "/";
}
return response.json();
})
.catch((error) => {
console.log(error);
window.location.href = "/";
});
return { user };
};

View File

@ -127,19 +127,7 @@
accept: "application/json",
},
},
)
.then((response) => {
if (response.status == 401) {
console.log("Not authorized");
window.location.href = "/";
} else {
return response.json();
}
})
.catch((error) => {
console.log(error);
window.location.href = "/";
});
).then((response) => response.json());
await invalidate("/api/chat/");
await goto("/chat/" + newData);
}
@ -154,8 +142,6 @@
await invalidate("/api/chat/" + $page.params.id);
} else if (response.status === 202) {
showToast("Chat in progress!");
} else if (response.status === 401) {
window.location.href = "/";
} else {
showToast("An error occurred: " + response.statusText);
}
@ -290,12 +276,12 @@
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="relative h-full max-h-screen overflow-hidden"
class="relative mx-auto h-full max-h-screen w-full overflow-hidden"
on:keydown={handleKeyDown}
>
<div class="mx-20">
<div class="h-8 justify-content border-b border-base-content/[.2]">
<div class="h-full relative flex items-center justify-center">
<div class="w-full border-b border-base-content/[.2]">
<div class="h-8 px-2 md:container md:mx-auto md:px-0">
<div class="w-full h-full relative flex items-center justify-center">
<div
class="flex flex-row items-center justify-center color-base-300"
title="Model"
@ -444,46 +430,49 @@
<div class="h-max pb-4">
{#each history as question, i}
{#if question.type === "human"}
<div class="w-10/12 mx-auto sm:w-10/12 chat chat-end py-4">
<div class="chat-image self-start pl-1 pt-1">
<div
class="mask mask-squircle online flex aspect-square w-8 items-center justify-center overflow-hidden bg-gradient-to-b from-primary to-primary-focus"
>
<span class="text-xs text-neutral-content">I</span>
</div>
</div>
<div
class="chat-bubble whitespace-normal break-words bg-base-300 text-base font-light text-base-content"
>
<!-- {question.data.content} -->
<div class="w-full overflow-hidden break-words">
{@html renderMarkdown(question.data.content)}
</div>
</div>
{#if i === history.length - 1 && !isLoading}
<div style="width: 100%; text-align: right;">
<button
disabled={isLoading}
class="btn-ghost btn-sm btn"
on:click|preventDefault={() => deletePrompt(data.chat.id, i)}
<div class="w-full border-y border-base-content/[.2] bg-base-300">
<div class="w-11/12 mx-auto sm:w-10/12 chat chat-start py-4">
<div class="chat-image self-start pl-1 pt-1">
<div
class="mask mask-squircle online flex aspect-square w-8 items-center justify-center overflow-hidden bg-gradient-to-b from-primary to-primary-focus"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
width="16"
height="16"
>
<path
class="fill-base-content"
d="M11 1.75V3h2.25a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1 0-1.5H5V1.75C5 .784 5.784 0 6.75 0h2.5C10.216 0 11 .784 11 1.75ZM4.496 6.675l.66 6.6a.25.25 0 0 0 .249.225h5.19a.25.25 0 0 0 .249-.225l.66-6.6a.75.75 0 0 1 1.492.149l-.66 6.6A1.748 1.748 0 0 1 10.595 15h-5.19a1.75 1.75 0 0 1-1.741-1.575l-.66-6.6a.75.75 0 1 1 1.492-.15ZM6.5 1.75V3h3V1.75a.25.25 0 0 0-.25-.25h-2.5a.25.25 0 0 0-.25.25Z"
/>
</svg>
</button>
<span class="text-xs text-neutral-content">I</span>
</div>
</div>
{/if}
<div
class="chat-bubble whitespace-normal break-words bg-base-300 text-base font-light text-base-content"
>
<!-- {question.data.content} -->
<div class="w-full overflow-hidden break-words">
{@html renderMarkdown(question.data.content)}
</div>
</div>
{#if i === history.length - 1 && !isLoading}
<div style="width: 100%; text-align: right;">
<button
disabled={isLoading}
class="btn-ghost btn-sm btn"
on:click|preventDefault={() =>
deletePrompt(data.chat.id, i)}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
width="16"
height="16"
>
<path
class="fill-base-content"
d="M11 1.75V3h2.25a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1 0-1.5H5V1.75C5 .784 5.784 0 6.75 0h2.5C10.216 0 11 .784 11 1.75ZM4.496 6.675l.66 6.6a.25.25 0 0 0 .249.225h5.19a.25.25 0 0 0 .249-.225l.66-6.6a.75.75 0 0 1 1.492.149l-.66 6.6A1.748 1.748 0 0 1 10.595 15h-5.19a1.75 1.75 0 0 1-1.741-1.575l-.66-6.6a.75.75 0 1 1 1.492-.15ZM6.5 1.75V3h3V1.75a.25.25 0 0 0-.25-.25h-2.5a.25.25 0 0 0-.25.25Z"
/>
</svg>
</button>
</div>
{/if}
</div>
</div>
{:else if question.type === "ai"}
<div class="w-10/12 mx-auto sm:w-10/12 chat chat-start py-4">
<div class="w-11/12 mx-auto sm:w-10/12 chat chat-start py-4">
<div class="chat-image self-start pl-1 pt-1">
<div
class="mask mask-squircle online flex aspect-square w-8 items-center justify-center overflow-hidden bg-gradient-to-b from-primary to-primary-focus"
@ -524,7 +513,6 @@
d="M11 1.75V3h2.25a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1 0-1.5H5V1.75C5 .784 5.784 0 6.75 0h2.5C10.216 0 11 .784 11 1.75ZM4.496 6.675l.66 6.6a.25.25 0 0 0 .249.225h5.19a.25.25 0 0 0 .249-.225l.66-6.6a.75.75 0 0 1 1.492.149l-.66 6.6A1.748 1.748 0 0 1 10.595 15h-5.19a1.75 1.75 0 0 1-1.741-1.575l-.66-6.6a.75.75 0 1 1 1.492-.15ZM6.5 1.75V3h3V1.75a.25.25 0 0 0-.25-.25h-2.5a.25.25 0 0 0-.25.25Z"
/>
</svg>
<span class="sr-only">Delete</span>
</button>
</div>
{/if}
@ -564,7 +552,7 @@
class="btn btn-ghost h-10 w-14 rounded-l-none rounded-r-lg border-0 text-lg"
class:loading={isLoading}
on:click|preventDefault={askQuestion}
><span class="sr-only">Send</span>
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"

View File

@ -28,22 +28,12 @@ interface Response {
id: string;
created: string;
params: Params;
owner: string;
history: Message[];
}
export const load: PageLoad = async ({ fetch, params }) => {
const data = await fetch("/api/chat/" + params.id)
.then((response) => {
if (response.status == 401) {
window.location.href = "/";
}
return response.json();
})
.catch((error) => {
console.log(error);
window.location.href = "/";
});
const r = await fetch("/api/chat/" + params.id);
const data = (await r.json()) as Response;
return {
chat: data,

View File

@ -1,69 +0,0 @@
<script lang="ts">
import { goto } from "$app/navigation";
import { writable } from "svelte/store";
let username = "";
let password = "";
let error = writable<string | null>(null);
async function handleSubmit(event: Event) {
event.preventDefault();
try {
const response = await fetch("/api/auth/token", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
username,
password,
}),
});
if (response.ok) {
const data = await response.json();
localStorage.setItem("token", data.access_token);
goto("/", { invalidateAll: true });
} else {
const errorData = await response.json();
error.set(errorData.detail || "Login failed");
}
} catch (err) {
error.set("An error occurred");
}
}
</script>
<main>
<div class="card-group">
<div class="card">
<div class="card-title p-3 text-3xl justify-center font-bold">
Sign In
</div>
<div class="card-body">
<form on:submit={handleSubmit}>
<div class="form-control">
<input
type="text"
placeholder="Username"
bind:value={username}
required
/>
</div>
<div class="form-control">
<input
type="password"
placeholder="Password"
bind:value={password}
required
/>
</div>
{#if $error}
<p style="color: red;">{$error}</p>
{/if}
<button class="btn" type="submit">Authenticate</button>
</form>
</div>
</div>
</div>
</main>

View File

@ -264,7 +264,7 @@
}
</script>
<div class="ml-12 pt-1">
<div class="top-section">
<div class="search-row">
<input
type="text"
@ -327,7 +327,6 @@
<div class="model-details">
{#if models.length > 1}
<select
class="select-bordered select w-full"
bind:value={selectedVariant[prefix]}
on:change={(event) => handleVariantChange(prefix, event)}
>

View File

@ -1,165 +0,0 @@
<script lang="ts">
import { onMount } from "svelte";
import { goto } from "$app/navigation";
let username = "";
let secret = "";
let full_name = "";
let email = "";
let auth_type = 1;
let error = "";
let success = "";
async function handleSubmit(event: Event) {
event.preventDefault();
error = "";
success = "";
const response = await fetch("/api/user/create", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
username,
secret,
full_name,
email,
auth_type,
}),
});
if (response.ok) {
success = "User created successfully!";
await authAfterCreate(event);
goto("/account");
} else {
const data = await response.json();
error = data.detail || "An error occurred";
}
}
async function authAfterCreate(event: Event) {
event.preventDefault();
try {
const response = await fetch("/api/auth/token", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
username: username,
password: secret,
}),
});
if (response.ok) {
goto("/", { invalidateAll: true });
} else {
const errorData = await response.json();
error = errorData.detail || "Login failed";
}
} catch (err) {
error = err instanceof Error ? err.message : "An unknown error occurred";
}
}
</script>
<main>
<div class="card-group">
<div class="card">
<div class="card-title p-3 text-3xl justify-center font-bold">
Register a new user
</div>
<div class="card-body">
<form on:submit={handleSubmit}>
<div class="form-control">
<input
type="text"
placeholder="Username"
bind:value={username}
required
/>
</div>
<div class="form-control">
<input
type="password"
placeholder="Password"
bind:value={secret}
required
/>
</div>
{#if error}
<p class="error-message">{error}</p>
{/if}
{#if success}
<p class="success-message">{success}</p>
{/if}
<button class="btn" type="submit">Submit</button>
</form>
</div>
</div>
<div class="card">
<div class="card-title p-3 text-3xl justify-center font-bold">
Or link an account (comming soon)
</div>
<div class="card-body">
<button name="google-btn" class="btn" disabled={true}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
fill="currentColor"
viewBox="0 0 16 16"
>
<path
d="M15.545 6.558a9.4 9.4 0 0 1 .139 1.626c0 2.434-.87 4.492-2.384 5.885h.002C11.978 15.292 10.158 16 8 16A8 8 0 1 1 8 0a7.7 7.7 0 0 1 5.352 2.082l-2.284 2.284A4.35 4.35 0 0 0 8 3.166c-2.087 0-3.86 1.408-4.492 3.304a4.8 4.8 0 0 0 0 3.063h.003c.635 1.893 2.405 3.301 4.492 3.301 1.078 0 2.004-.276 2.722-.764h-.003a3.7 3.7 0 0 0 1.599-2.431H8v-3.08z"
/>
</svg>
<span>Link Google Account</span>
</button>
<button name="reddit-btn" class="btn" disabled={true}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
fill="currentColor"
viewBox="0 0 16 16"
>
<path
d="M6.167 8a.83.83 0 0 0-.83.83c0 .459.372.84.83.831a.831.831 0 0 0 0-1.661m1.843 3.647c.315 0 1.403-.038 1.976-.611a.23.23 0 0 0 0-.306.213.213 0 0 0-.306 0c-.353.363-1.126.487-1.67.487-.545 0-1.308-.124-1.671-.487a.213.213 0 0 0-.306 0 .213.213 0 0 0 0 .306c.564.563 1.652.61 1.977.61zm.992-2.807c0 .458.373.83.831.83s.83-.381.83-.83a.831.831 0 0 0-1.66 0z"
/>
<path
d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0m-3.828-1.165c-.315 0-.602.124-.812.325-.801-.573-1.9-.945-3.121-.993l.534-2.501 1.738.372a.83.83 0 1 0 .83-.869.83.83 0 0 0-.744.468l-1.938-.41a.2.2 0 0 0-.153.028.2.2 0 0 0-.086.134l-.592 2.788c-1.24.038-2.358.41-3.17.992-.21-.2-.496-.324-.81-.324a1.163 1.163 0 0 0-.478 2.224q-.03.17-.029.353c0 1.795 2.091 3.256 4.669 3.256s4.668-1.451 4.668-3.256c0-.114-.01-.238-.029-.353.401-.181.688-.592.688-1.069 0-.65-.525-1.165-1.165-1.165"
/>
</svg>
<span>Link Reddit Account</span>
</button>
</div>
</div>
<div class="card">
<div class="card-title pt-3 text-3xl justify-center font-bold">
Already have an account?
</div>
<div class="card-body">
<button name="login-btn" class="btn" on:click={() => goto("/login")}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
fill="currentColor"
class="mr-3"
viewBox="0 0 16 16"
>
<path
d="M12.5 16a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7m1.679-4.493-1.335 2.226a.75.75 0 0 1-1.174.144l-.774-.773a.5.5 0 0 1 .708-.708l.547.548 1.17-1.951a.5.5 0 1 1 .858.514M11 5a3 3 0 1 1-6 0 3 3 0 0 1 6 0M8 7a2 2 0 1 0 0-4 2 2 0 0 0 0 4"
/>
<path
d="M8.256 14a4.5 4.5 0 0 1-.229-1.004H3c.001-.246.154-.986.832-1.664C4.484 10.68 5.711 10 8 10q.39 0 .74.025c.226-.341.496-.65.804-.918Q8.844 9.002 8 9c-5 0-6 3-6 4s1 1 1 1z"
/>
</svg>
<span>Login Instead</span>
</button>
</div>
</div>
</div>
</main>