utils.py 4.25 KB
Newer Older
1
2
3
from pathlib import Path
import site

Timothy J. Baek's avatar
Timothy J. Baek committed
4
from fastapi import APIRouter, UploadFile, File, Response
Timothy J. Baek's avatar
Timothy J. Baek committed
5
from fastapi import Depends, HTTPException, status
6
from starlette.responses import StreamingResponse, FileResponse
Timothy J. Baek's avatar
Timothy J. Baek committed
7
8
from pydantic import BaseModel

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

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

14

15
from utils.utils import get_admin_user
16
from utils.misc import calculate_sha256, get_gravatar_url
17

Timothy J. Baek's avatar
Timothy J. Baek committed
18
from config import OLLAMA_BASE_URLS, DATA_DIR, UPLOAD_DIR, ENABLE_ADMIN_EXPORT
Timothy J. Baek's avatar
Timothy J. Baek committed
19
from constants import ERROR_MESSAGES
Michael Poluektov's avatar
Michael Poluektov committed
20

Timothy J. Baek's avatar
Timothy J. Baek committed
21
22
23
24

router = APIRouter()


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


Timothy J. Baek's avatar
Timothy J. Baek committed
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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
47
48
49
50
51
52
53
54
55
56
57
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
58
59
class ChatForm(BaseModel):
    title: str
Michael Poluektov's avatar
Michael Poluektov committed
60
    messages: list[dict]
Timothy J. Baek's avatar
Timothy J. Baek committed
61
62
63
64
65
66
67
68
69


@router.post("/pdf")
async def download_chat_as_pdf(
    form_data: ChatForm,
):
    pdf = FPDF()
    pdf.add_page()

70
71
72
73
74
75
76
77
78
79
80
81
    # When running in docker, workdir is /app/backend, so fonts is in /app/backend/static/fonts
    FONTS_DIR = Path("./static/fonts")

    # Non Docker Installation

    # When running using `pip install` the static directory is in the site packages.
    if not FONTS_DIR.exists():
        FONTS_DIR = Path(site.getsitepackages()[0]) / "static/fonts"
    # When running using `pip install -e .` the static directory is in the site packages.
    # This path only works if `open-webui serve` is run from the root of this project.
    if not FONTS_DIR.exists():
        FONTS_DIR = Path("./backend/static/fonts")
Timothy J. Baek's avatar
Timothy J. Baek committed
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102

    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
103
        pdf.set_font("NotoSans", "B", size=14)  # Bold for the role
Timothy J. Baek's avatar
Timothy J. Baek committed
104
105
106
107
        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
108
109
        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
110
111
112
113
114
115
116
117
118
119
120

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


121
122
@router.get("/db/download")
async def download_db(user=Depends(get_admin_user)):
123
    if not ENABLE_ADMIN_EXPORT:
124
125
126
127
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
        )
128
129
130
    from apps.webui.internal.db import engine

    if engine.name != "sqlite":
131
132
133
134
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=ERROR_MESSAGES.DB_NOT_SQLITE,
        )
135
    return FileResponse(
136
        engine.url.database,
137
138
139
        media_type="application/octet-stream",
        filename="webui.db",
    )
140
141
142
143
144
145
146
147
148


@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",
    )