Unverified Commit 3b3d0cce authored by Jannik S's avatar Jannik S Committed by GitHub
Browse files

Merge branch 'dev' into dockerfile-optimisation

parents 073f06d4 72110c29
......@@ -10,3 +10,7 @@ OPENAI_API_KEY=''
# DO NOT TRACK
SCARF_NO_ANALYTICS=true
DO_NOT_TRACK=true
# Use locally bundled version of the LiteLLM cost map json
# to avoid repetitive startup connections
LITELLM_LOCAL_MODEL_COST_MAP="True"
......@@ -57,3 +57,14 @@ jobs:
path: .
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Trigger Docker build workflow
uses: actions/github-script@v7
with:
script: |
github.rest.actions.createWorkflowDispatch({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'docker-build.yaml',
ref: 'v${{ steps.get_version.outputs.version }}',
})
......@@ -2,6 +2,7 @@ name: Create and publish Docker images with specific build args
# Configures this workflow to run every time a change is pushed to the branch called `release`.
on:
workflow_dispatch:
push:
branches:
- main
......
......@@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.1.117] - 2024-04-03
### Added
- 🗨️ **Local Chat Sharing**: Share chat links seamlessly between users.
- 🔑 **API Key Generation Support**: Generate secret keys to leverage Open WebUI with OpenAI libraries.
- 📄 **Chat Download as PDF**: Easily download chats in PDF format.
- 📝 **Improved Logging**: Enhancements to logging functionality.
- 📧 **Trusted Email Authentication**: Authenticate using a trusted email header.
### Fixed
- 🌷 **Enhanced Dutch Translation**: Improved translation for Dutch users.
-**White Theme Styling**: Resolved styling issue with the white theme.
- 📜 **LaTeX Chat Screen Overflow**: Fixed screen overflow issue with LaTeX rendering.
- 🔒 **Security Patches**: Applied necessary security patches.
## [0.1.116] - 2024-03-31
### Added
......
......@@ -215,7 +215,8 @@ async def get_ollama_versions(url_idx: Optional[int] = None):
if len(responses) > 0:
lowest_version = min(
responses, key=lambda x: tuple(map(int, x["version"].split(".")))
responses,
key=lambda x: tuple(map(int, x["version"].split("-")[0].split("."))),
)
return {"version": lowest_version["version"]}
......
......@@ -8,7 +8,7 @@ from fastapi import (
Form,
)
from fastapi.middleware.cors import CORSMiddleware
import os, shutil, logging
import os, shutil, logging, re
from pathlib import Path
from typing import List
......@@ -438,25 +438,11 @@ def store_doc(
log.info(f"file.content_type: {file.content_type}")
try:
is_valid_filename = True
unsanitized_filename = file.filename
if not unsanitized_filename.isascii():
is_valid_filename = False
filename = os.path.basename(unsanitized_filename)
unvalidated_file_path = f"{UPLOAD_DIR}/{unsanitized_filename}"
dereferenced_file_path = str(Path(unvalidated_file_path).resolve(strict=False))
if not dereferenced_file_path.startswith(UPLOAD_DIR):
is_valid_filename = False
file_path = f"{UPLOAD_DIR}/{filename}"
if is_valid_filename:
file_path = dereferenced_file_path
else:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.DEFAULT(),
)
filename = file.filename
contents = file.file.read()
with open(file_path, "wb") as f:
f.write(contents)
......@@ -467,7 +453,7 @@ def store_doc(
collection_name = calculate_sha256(f)[:63]
f.close()
loader, known_type = get_loader(file.filename, file.content_type, file_path)
loader, known_type = get_loader(filename, file.content_type, file_path)
data = loader.load()
try:
......
......@@ -86,6 +86,7 @@ class SignupForm(BaseModel):
name: str
email: str
password: str
profile_image_url: Optional[str] = "/user.png"
class AuthsTable:
......@@ -94,7 +95,12 @@ class AuthsTable:
self.db.create_tables([Auth])
def insert_new_auth(
self, email: str, password: str, name: str, role: str = "pending"
self,
email: str,
password: str,
name: str,
profile_image_url: str = "/user.png",
role: str = "pending",
) -> Optional[UserModel]:
log.info("insert_new_auth")
......@@ -105,7 +111,7 @@ class AuthsTable:
)
result = Auth.create(**auth.model_dump())
user = Users.insert_new_user(id, name, email, role)
user = Users.insert_new_user(id, name, email, profile_image_url, role)
if result and user:
return user
......
......@@ -206,6 +206,18 @@ class ChatTable:
except:
return None
def get_chat_by_share_id(self, id: str) -> Optional[ChatModel]:
try:
chat = Chat.get(Chat.share_id == id)
if chat:
chat = Chat.get(Chat.id == id)
return ChatModel(**model_to_dict(chat))
else:
return None
except:
return None
def get_chat_by_id_and_user_id(self, id: str, user_id: str) -> Optional[ChatModel]:
try:
chat = Chat.get(Chat.id == id, Chat.user_id == user_id)
......
......@@ -31,7 +31,7 @@ class UserModel(BaseModel):
name: str
email: str
role: str = "pending"
profile_image_url: str = "/user.png"
profile_image_url: str
timestamp: int # timestamp in epoch
api_key: Optional[str] = None
......@@ -59,7 +59,12 @@ class UsersTable:
self.db.create_tables([User])
def insert_new_user(
self, id: str, name: str, email: str, role: str = "pending"
self,
id: str,
name: str,
email: str,
profile_image_url: str = "/user.png",
role: str = "pending",
) -> Optional[UserModel]:
user = UserModel(
**{
......@@ -67,7 +72,7 @@ class UsersTable:
"name": name,
"email": email,
"role": role,
"profile_image_url": "/user.png",
"profile_image_url": profile_image_url,
"timestamp": int(time.time()),
}
)
......
......@@ -163,7 +163,11 @@ async def signup(request: Request, form_data: SignupForm):
)
hashed = get_password_hash(form_data.password)
user = Auths.insert_new_auth(
form_data.email.lower(), hashed, form_data.name, role
form_data.email.lower(),
hashed,
form_data.name,
form_data.profile_image_url,
role,
)
if user:
......
......@@ -251,6 +251,14 @@ async def delete_shared_chat_by_id(id: str, user=Depends(get_current_user)):
@router.get("/share/{share_id}", response_model=Optional[ChatResponse])
async def get_shared_chat_by_id(share_id: str, user=Depends(get_current_user)):
if user.role == "pending":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND
)
if user.role == "user":
chat = Chats.get_chat_by_share_id(share_id)
elif user.role == "admin":
chat = Chats.get_chat_by_id(share_id)
if chat:
......
from fastapi import APIRouter, UploadFile, File, BackgroundTasks
from fastapi import APIRouter, UploadFile, File, Response
from fastapi import Depends, HTTPException, status
from starlette.responses import StreamingResponse, FileResponse
from pydantic import BaseModel
import requests
import os
import aiohttp
import json
from fpdf import FPDF
import markdown
from utils.utils import get_admin_user
......@@ -16,7 +13,7 @@ from utils.misc import calculate_sha256, get_gravatar_url
from config import OLLAMA_BASE_URLS, DATA_DIR, UPLOAD_DIR
from constants import ERROR_MESSAGES
from typing import List
router = APIRouter()
......@@ -28,6 +25,70 @@ async def get_gravatar(
return get_gravatar_url(email)
class MarkdownForm(BaseModel):
md: str
@router.post("/markdown")
async def get_html_from_markdown(
form_data: MarkdownForm,
):
return {"html": markdown.markdown(form_data.md)}
class ChatForm(BaseModel):
title: str
messages: List[dict]
@router.post("/pdf")
async def download_chat_as_pdf(
form_data: ChatForm,
):
pdf = FPDF()
pdf.add_page()
STATIC_DIR = "./static"
FONTS_DIR = f"{STATIC_DIR}/fonts"
pdf.add_font("NotoSans", "", f"{FONTS_DIR}/NotoSans-Regular.ttf")
pdf.add_font("NotoSans", "b", f"{FONTS_DIR}/NotoSans-Bold.ttf")
pdf.add_font("NotoSans", "i", f"{FONTS_DIR}/NotoSans-Italic.ttf")
pdf.add_font("NotoSansKR", "", f"{FONTS_DIR}/NotoSansKR-Regular.ttf")
pdf.add_font("NotoSansJP", "", f"{FONTS_DIR}/NotoSansJP-Regular.ttf")
pdf.set_font("NotoSans", size=12)
pdf.set_fallback_fonts(["NotoSansKR", "NotoSansJP"])
pdf.set_auto_page_break(auto=True, margin=15)
# Adjust the effective page width for multi_cell
effective_page_width = (
pdf.w - 2 * pdf.l_margin - 10
) # Subtracted an additional 10 for extra padding
# Add chat messages
for message in form_data.messages:
role = message["role"]
content = message["content"]
pdf.set_font("NotoSans", "B", size=14) # Bold for the role
pdf.multi_cell(effective_page_width, 10, f"{role.upper()}", 0, "L")
pdf.ln(1) # Extra space between messages
pdf.set_font("NotoSans", size=10) # Regular for content
pdf.multi_cell(effective_page_width, 6, content, 0, "L")
pdf.ln(1.5) # Extra space between messages
# Save the pdf with name .pdf
pdf_bytes = pdf.output()
return Response(
content=bytes(pdf_bytes),
media_type="application/pdf",
headers={"Content-Disposition": f"attachment;filename=chat.pdf"},
)
@router.get("/db/download")
async def download_db(user=Depends(get_admin_user)):
......
......@@ -25,8 +25,9 @@ try:
except ImportError:
log.warning("dotenv not installed, skipping...")
WEBUI_NAME = "Open WebUI"
WEBUI_NAME = os.environ.get("WEBUI_NAME", "Open WebUI")
WEBUI_FAVICON_URL = "https://openwebui.com/favicon.png"
shutil.copyfile("../build/favicon.png", "./static/favicon.png")
####################################
......@@ -149,6 +150,7 @@ log.setLevel(SRC_LOG_LEVELS["CONFIG"])
####################################
CUSTOM_NAME = os.environ.get("CUSTOM_NAME", "")
if CUSTOM_NAME:
try:
r = requests.get(f"https://api.openwebui.com/api/v1/custom/{CUSTOM_NAME}")
......@@ -171,7 +173,9 @@ if CUSTOM_NAME:
except Exception as e:
log.exception(e)
pass
else:
if WEBUI_NAME != "Open WebUI":
WEBUI_NAME += " (Open WebUI)"
####################################
# DATA/FRONTEND BUILD DIR
......
......@@ -84,7 +84,6 @@ app.state.MODEL_FILTER_LIST = MODEL_FILTER_LIST
app.state.WEBHOOK_URL = WEBHOOK_URL
origins = ["*"]
......@@ -284,6 +283,20 @@ async def get_app_latest_release_version():
)
@app.get("/manifest.json")
async def get_manifest_json():
return {
"name": WEBUI_NAME,
"short_name": WEBUI_NAME,
"start_url": "/",
"display": "standalone",
"background_color": "#343541",
"theme_color": "#343541",
"orientation": "portrait-primary",
"icons": [{"src": "/favicon.png", "type": "image/png", "sizes": "844x884"}],
}
app.mount("/static", StaticFiles(directory="static"), name="static")
app.mount("/cache", StaticFiles(directory="data/cache"), name="cache")
......
......@@ -18,6 +18,8 @@ peewee-migrate
bcrypt
litellm==1.30.7
boto3
argon2-cffi
apscheduler
google-generativeai
......@@ -40,6 +42,8 @@ xlrd
opencv-python-headless
rapidocr-onnxruntime
fpdf2
faster-whisper
PyJWT
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment