utils.py 3.73 KB
Newer Older
Timothy J. Baek's avatar
Timothy J. Baek committed
1
from fastapi import APIRouter, UploadFile, File, Response
Timothy J. Baek's avatar
Timothy J. Baek committed
2
from fastapi import Depends, HTTPException, status
3
from peewee import SqliteDatabase
4
from starlette.responses import StreamingResponse, FileResponse
Timothy J. Baek's avatar
Timothy J. Baek committed
5
6
from pydantic import BaseModel

Timothy J. Baek's avatar
Timothy J. Baek committed
7

Timothy J. Baek's avatar
Timothy J. Baek committed
8
from fpdf import FPDF
Timothy J. Baek's avatar
Timothy J. Baek committed
9
import markdown
Timothy J. Baek's avatar
Timothy J. Baek committed
10
11
import black

12

13
from apps.webui.internal.db import DB
14
from utils.utils import get_admin_user
15
from utils.misc import calculate_sha256, get_gravatar_url
16

Timothy J. Baek's avatar
Timothy J. Baek committed
17
from config import OLLAMA_BASE_URLS, DATA_DIR, UPLOAD_DIR, ENABLE_ADMIN_EXPORT
Timothy J. Baek's avatar
Timothy J. Baek committed
18
from constants import ERROR_MESSAGES
Timothy J. Baek's avatar
Timothy J. Baek committed
19
from typing import List
Timothy J. Baek's avatar
Timothy J. Baek committed
20
21
22
23

router = APIRouter()


24
25
26
27
28
@router.get("/gravatar")
async def get_gravatar(
    email: str,
):
    return get_gravatar_url(email)
29
30


Timothy J. Baek's avatar
Timothy J. Baek committed
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class CodeFormatRequest(BaseModel):
    code: str


@router.post("/code/format")
async def format_code(request: CodeFormatRequest):
    try:
        formatted_code = black.format_str(request.code, mode=black.Mode())
        return {"code": formatted_code}
    except black.NothingChanged:
        return {"code": request.code}
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))


Timothy J. Baek's avatar
Timothy J. Baek committed
46
47
48
49
50
51
52
53
54
55
56
class MarkdownForm(BaseModel):
    md: str


@router.post("/markdown")
async def get_html_from_markdown(
    form_data: MarkdownForm,
):
    return {"html": markdown.markdown(form_data.md)}


Timothy J. Baek's avatar
Timothy J. Baek committed
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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"]
Timothy J. Baek's avatar
Timothy J. Baek committed
92
        pdf.set_font("NotoSans", "B", size=14)  # Bold for the role
Timothy J. Baek's avatar
Timothy J. Baek committed
93
94
95
96
        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
Timothy J. Baek's avatar
Timothy J. Baek committed
97
98
        pdf.multi_cell(effective_page_width, 6, content, 0, "L")
        pdf.ln(1.5)  # Extra space between messages
Timothy J. Baek's avatar
Timothy J. Baek committed
99
100
101
102
103
104
105
106
107
108
109

    # 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"},
    )


110
111
@router.get("/db/download")
async def download_db(user=Depends(get_admin_user)):
112
    if not ENABLE_ADMIN_EXPORT:
113
114
115
116
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
        )
117
118
119
120
121
    if not isinstance(DB, SqliteDatabase):
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=ERROR_MESSAGES.DB_NOT_SQLITE,
        )
122
    return FileResponse(
123
        DB.database,
124
125
126
        media_type="application/octet-stream",
        filename="webui.db",
    )
127
128
129
130
131
132
133
134
135


@router.get("/litellm/config")
async def download_litellm_config_yaml(user=Depends(get_admin_user)):
    return FileResponse(
        f"{DATA_DIR}/litellm/config.yaml",
        media_type="application/octet-stream",
        filename="config.yaml",
    )