This commit is contained in:
Timothy Jaeryang Baek 2026-09-06 17:27:30 -04:00
parent 98fcb844e1
commit 508de20779
19 changed files with 23 additions and 560 deletions

View file

@ -251,8 +251,6 @@ if FROM_INIT_PY:
STATIC_DIR = Path(os.getenv('STATIC_DIR', OPEN_WEBUI_DIR / 'static'))
FONTS_DIR = Path(os.getenv('FONTS_DIR', OPEN_WEBUI_DIR / 'static' / 'fonts'))
FRONTEND_BUILD_DIR = Path(os.getenv('FRONTEND_BUILD_DIR', BASE_DIR / 'build')).resolve()
if FROM_INIT_PY:

View file

@ -267,11 +267,6 @@ class ChatsImportForm(BaseModel):
chats: list[ChatImportForm]
class ChatTitleMessagesForm(BaseModel):
title: str
messages: list[dict]
class ChatTitleForm(BaseModel):
title: str

View file

@ -3,15 +3,13 @@ from __future__ import annotations
import logging
import black
from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
from fastapi import APIRouter, Depends, HTTPException, Request, status
from open_webui.config import DATA_DIR, ENABLE_ADMIN_EXPORT
from open_webui.constants import ERROR_MESSAGES
from open_webui.models.chats import ChatTitleMessagesForm
from open_webui.models.config import Config
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.code_interpreter import execute_code_jupyter
from open_webui.utils.misc import get_gravatar_url
from open_webui.utils.pdf_generator import PDFGenerator
from pydantic import BaseModel
from starlette.responses import FileResponse
@ -73,26 +71,6 @@ async def execute_code(request: Request, form_data: CodeForm, user=Depends(get_v
)
class ChatForm(BaseModel):
title: str
messages: list[dict]
@router.post('/pdf')
async def download_chat_as_pdf(form_data: ChatTitleMessagesForm, user=Depends(get_verified_user)):
try:
pdf_bytes = PDFGenerator(form_data).generate_chat_pdf()
return Response(
content=pdf_bytes,
media_type='application/pdf',
headers={'Content-Disposition': 'attachment;filename=chat.pdf'},
)
except Exception as e:
log.exception(f'Error generating PDF: {e}')
raise HTTPException(status_code=400, detail=str(e))
@router.get('/db/download')
async def download_db(user=Depends(get_admin_user)):
"""Download the raw SQLite database file (admin-only, SQLite deployments only)."""

View file

@ -1,295 +0,0 @@
/* HTML and Body */
@font-face {
font-family: 'NotoSansSC-Regular';
src: url('fonts/NotoSansSC-Regular.ttf');
}
html {
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', 'NotoSans', 'NotoSansJP', 'NotoSansKR',
'NotoSansSC', 'Twemoji', 'STSong-Light', 'MSung-Light', 'HeiseiMin-W3', 'HYSMyeongJo-Medium',
Roboto, 'Helvetica Neue', Arial, sans-serif;
font-size: 14px; /* Default font size */
line-height: 1.5;
}
*,
*::before,
*::after {
box-sizing: inherit;
}
body {
margin: 0;
padding: 0;
background-color: #fff;
width: auto;
}
/* Typography */
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: 500;
margin: 0;
}
h1 {
font-size: 2.5rem;
}
h2 {
font-size: 2rem;
}
h3 {
font-size: 1.75rem;
}
h4 {
font-size: 1.5rem;
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
/* Grid System */
.container {
width: 100%;
padding-right: 15px;
padding-left: 15px;
margin-right: auto;
margin-left: auto;
}
/* Utilities */
.text-center {
text-align: center;
}
/* Additional Text Utilities */
.text-muted {
color: #6c757d; /* Muted text color */
}
/* Small Text */
small {
font-size: 80%; /* Smaller font size relative to the base */
color: #6c757d; /* Lighter text color for secondary information */
margin-bottom: 0;
margin-top: 0;
}
/* Strong Element Styles */
strong {
font-weight: bolder; /* Ensures the text is bold */
color: inherit; /* Inherits the color from its parent element */
}
/* link */
a {
color: #007bff;
text-decoration: none;
background-color: transparent;
}
a:hover {
color: #0056b3;
text-decoration: underline;
}
/* General styles for lists */
ol,
ul,
li {
padding-left: 40px; /* Increase padding to move bullet points to the right */
margin-left: 20px; /* Indent lists from the left */
}
/* Ordered list styles */
ol {
list-style-type: decimal; /* Use numbers for ordered lists */
margin-bottom: 10px; /* Space after each list */
}
ol li {
margin-bottom: 0.5rem; /* Space between ordered list items */
}
/* Unordered list styles */
ul {
list-style-type: disc; /* Use bullets for unordered lists */
margin-bottom: 10px; /* Space after each list */
}
ul li {
margin-bottom: 0.5rem; /* Space between unordered list items */
}
/* List item styles */
li {
margin-bottom: 5px; /* Space between list items */
line-height: 1.5; /* Line height for better readability */
}
/* Nested lists */
ol ol,
ol ul,
ul ol,
ul ul {
padding-left: 20px;
margin-left: 30px; /* Further indent nested lists */
margin-bottom: 0; /* Remove extra margin at the bottom of nested lists */
}
/* Code blocks */
pre {
background-color: #f4f4f4;
padding: 10px;
overflow-x: auto;
max-width: 100%; /* Ensure it doesn't overflow the page */
width: 80%; /* Set a specific width for a container-like appearance */
margin: 0 1em; /* Center the pre block */
box-sizing: border-box; /* Include padding in the width */
border: 1px solid #ccc; /* Optional: Add a border for better definition */
border-radius: 4px; /* Optional: Add rounded corners */
}
code {
font-family: 'Courier New', Courier, monospace;
background-color: #f4f4f4;
padding: 2px 4px;
border-radius: 4px;
box-sizing: border-box; /* Include padding in the width */
}
.message {
margin-top: 8px;
margin-bottom: 8px;
max-width: 100%;
overflow-wrap: break-word;
}
/* Table Styles */
table {
width: 100%;
margin-bottom: 1rem;
color: #212529;
border-collapse: collapse; /* Removes the space between borders */
}
th,
td {
margin: 0;
padding: 0.75rem;
vertical-align: top;
border-top: 1px solid #dee2e6;
}
thead th {
vertical-align: bottom;
border-bottom: 2px solid #dee2e6;
}
tbody + tbody {
border-top: 2px solid #dee2e6;
}
/* markdown-section styles */
.markdown-section blockquote,
.markdown-section h1,
.markdown-section h2,
.markdown-section h3,
.markdown-section h4,
.markdown-section h5,
.markdown-section h6,
.markdown-section p,
.markdown-section pre,
.markdown-section table,
.markdown-section ul {
/* Give most block elements margin top and bottom */
margin-top: 1rem;
}
/* Remove top margin if it's the first child */
.markdown-section blockquote:first-child,
.markdown-section h1:first-child,
.markdown-section h2:first-child,
.markdown-section h3:first-child,
.markdown-section h4:first-child,
.markdown-section h5:first-child,
.markdown-section h6:first-child,
.markdown-section p:first-child,
.markdown-section pre:first-child,
.markdown-section table:first-child,
.markdown-section ul:first-child {
margin-top: 0;
}
/* Remove top margin of <ul> following a <p> */
.markdown-section p + ul {
margin-top: 0;
}
/* Remove bottom margin of <p> if it is followed by a <ul> */
/* Note: :has is not supported in CSS, so you would need JavaScript for this behavior */
.markdown-section p {
margin-bottom: 0;
}
/* List item styles */
.markdown-section li {
padding: 2px;
}
.markdown-section li p {
margin-bottom: 0;
padding: 0;
}
/* Avoid margins for nested lists */
.markdown-section li > ul {
margin-top: 0;
margin-bottom: 0;
}
/* Table styles */
.markdown-section table {
width: 100%;
border-collapse: collapse;
margin: 1rem 0;
}
.markdown-section th,
.markdown-section td {
border: 1px solid #ddd;
padding: 0.5rem;
text-align: left;
}
.markdown-section th {
background-color: #f2f2f2;
}
.markdown-section pre {
padding: 10px;
margin: 10px;
}
.markdown-section pre code {
position: relative;
color: rgb(172, 0, 95);
}

View file

@ -1,140 +0,0 @@
import site
from datetime import datetime
from html import escape
from io import BytesIO
from pathlib import Path
from typing import Any, Dict, List
from fpdf import FPDF
from markdown import markdown
from open_webui.env import FONTS_DIR, STATIC_DIR
from open_webui.models.chats import ChatTitleMessagesForm
class PDFGenerator:
"""
Description:
The `PDFGenerator` class is designed to create PDF documents from chat messages.
The process involves transforming markdown content into HTML and then into a PDF format
Attributes:
- `form_data`: An instance of `ChatTitleMessagesForm` containing title and messages.
"""
def __init__(self, form_data: ChatTitleMessagesForm):
self.html_body = None
self.messages_html = None
self.form_data = form_data
self.css = Path(STATIC_DIR / 'assets' / 'pdf-style.css').read_text()
def format_timestamp(self, timestamp: float) -> str:
"""Convert a UNIX timestamp to a formatted date string."""
try:
date_time = datetime.fromtimestamp(timestamp)
return date_time.strftime('%Y-%m-%d, %H:%M:%S')
except (ValueError, TypeError) as e:
# Log the error if necessary
return ''
def _build_html_message(self, message: Dict[str, Any]) -> str:
"""Build HTML for a single message."""
role = escape(message.get('role', 'user'))
content = escape(message.get('content', ''))
timestamp = message.get('timestamp')
model = escape(message.get('model') if role == 'assistant' else '')
date_str = escape(self.format_timestamp(timestamp) if timestamp else '')
# extends pymdownx extension to convert markdown to html.
# - https://facelessuser.github.io/pymdown-extensions/usage_notes/
# html_content = markdown(content, extensions=["pymdownx.extra"])
content = content.replace('\n', '<br/>')
html_message = f"""
<div>
<div>
<h4>
<strong>{role.title()}</strong>
<span style="font-size: 12px;">{model}</span>
</h4>
<div> {date_str} </div>
</div>
<br/>
<br/>
<div>
{content}
</div>
</div>
<br/>
"""
return html_message
def _generate_html_body(self) -> str:
"""Generate the full HTML body for the PDF."""
escaped_title = escape(self.form_data.title)
return f"""
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<div>
<div>
<h2>{escaped_title}</h2>
{self.messages_html}
</div>
</div>
</body>
</html>
"""
def generate_chat_pdf(self) -> bytes:
"""
Generate a PDF from chat messages.
"""
try:
global FONTS_DIR
pdf = FPDF()
pdf.add_page()
# 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'
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.add_font('NotoSansSC', '', f'{FONTS_DIR}/NotoSansSC-Regular.ttf')
pdf.add_font('Twemoji', '', f'{FONTS_DIR}/Twemoji.ttf')
pdf.set_font('NotoSans', size=12)
pdf.set_fallback_fonts(['NotoSansKR', 'NotoSansJP', 'NotoSansSC', 'Twemoji'])
pdf.set_auto_page_break(auto=True, margin=15)
# Build HTML messages
messages_html_list: List[str] = [self._build_html_message(msg) for msg in self.form_data.messages]
self.messages_html = '<div>' + ''.join(messages_html_list) + '</div>'
# Generate full HTML body
self.html_body = self._generate_html_body()
pdf.write_html(self.html_body)
# Save the pdf with name .pdf
pdf_bytes = pdf.output()
return bytes(pdf_bytes)
except Exception as e:
raise e

View file

@ -60,7 +60,6 @@ fake-useragent==2.2.0
ftfy==6.3.1
chardet==7.4.3
fpdf2==2.8.7
Markdown==3.10.2
beautifulsoup4==4.14.3

View file

@ -66,7 +66,6 @@ einops==0.8.2
ftfy==6.3.1
chardet==7.4.3
pypdf==6.7.5
fpdf2==2.8.7
docx2txt==0.9
python-docx==1.2.0
python-pptx==1.0.2

View file

@ -75,7 +75,6 @@ dependencies = [
"ftfy==6.3.1",
"chardet==7.4.3",
"pypdf==6.7.5",
"fpdf2==2.8.7",
"docx2txt==0.9",
"python-docx==1.2.0",
"python-pptx==1.0.2",

View file

@ -95,33 +95,6 @@ export const formatPythonCode = async (token: string, code: string) => {
return res;
};
export const downloadChatAsPDF = async (token: string, title: string, messages: object[]) => {
let error = null;
const blob = await fetch(`${WEBUI_API_BASE_URL}/utils/pdf`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
},
body: JSON.stringify({
title: title,
messages: messages
})
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.blob();
})
.catch((err) => {
console.error(err);
error = err;
return null;
});
return blob;
};
export const downloadDatabase = async (token: string) => {
let error = null;

View file

@ -5,7 +5,6 @@
import fileSaver from 'file-saver';
const { saveAs } = fileSaver;
import { downloadChatAsPDF } from '$lib/apis/utils';
import { copyToClipboard, createMessagesList } from '$lib/utils';
import { getOutputText } from '$lib/components/chat/Messages/structuredOutput';

View file

@ -17,7 +17,6 @@
import { chats, folders, settings, theme, user } from '$lib/stores';
import { createMessagesList } from '$lib/utils';
import { getOutputText } from '$lib/components/chat/Messages/structuredOutput';
import { downloadChatAsPDF } from '$lib/apis/utils';
import ArchiveBoxIcon from '$lib/components/icons/ArchiveBox.svelte';
import CopyIcon from './icons/Copy.svelte';
import DownloadIcon from './icons/Download.svelte';

85
uv.lock generated
View file

@ -3,10 +3,10 @@ revision = 3
requires-python = ">=3.11, <3.13.0"
resolution-markers = [
"python_full_version >= '3.12' and sys_platform == 'win32'",
"python_full_version < '3.12' and sys_platform == 'win32'",
"python_full_version >= '3.12' and sys_platform == 'emscripten'",
"python_full_version < '3.12' and sys_platform == 'emscripten'",
"python_full_version >= '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'",
"python_full_version < '3.12' and sys_platform == 'win32'",
"python_full_version < '3.12' and sys_platform == 'emscripten'",
"python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'",
]
@ -936,7 +936,7 @@ name = "cuda-bindings"
version = "13.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cuda-pathfinder", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
{ name = "cuda-pathfinder" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/51/6b/457ca12dad3ee9bfcc9a545cfd6b64b359ba49de40f776f6e028e678f262/cuda_bindings-13.3.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5879712accf6e14bb01aa5e67440eb84998b8d104b509cc7a6dc0b8f656a474", size = 6053539, upload-time = "2026-05-29T23:11:43.19Z" },
@ -963,34 +963,34 @@ wheels = [
[package.optional-dependencies]
cudart = [
{ name = "nvidia-cuda-runtime", marker = "sys_platform == 'linux'" },
{ name = "nvidia-cuda-runtime" },
]
cufft = [
{ name = "nvidia-cufft", marker = "sys_platform == 'linux'" },
{ name = "nvidia-cufft" },
]
cufile = [
{ name = "nvidia-cufile", marker = "sys_platform == 'linux'" },
{ name = "nvidia-cufile" },
]
cupti = [
{ name = "nvidia-cuda-cupti", marker = "sys_platform == 'linux'" },
{ name = "nvidia-cuda-cupti" },
]
curand = [
{ name = "nvidia-curand", marker = "sys_platform == 'linux'" },
{ name = "nvidia-curand" },
]
cusolver = [
{ name = "nvidia-cusolver", marker = "sys_platform == 'linux'" },
{ name = "nvidia-cusolver" },
]
cusparse = [
{ name = "nvidia-cusparse", marker = "sys_platform == 'linux'" },
{ name = "nvidia-cusparse" },
]
nvjitlink = [
{ name = "nvidia-nvjitlink", marker = "sys_platform == 'linux'" },
{ name = "nvidia-nvjitlink" },
]
nvrtc = [
{ name = "nvidia-cuda-nvrtc", marker = "sys_platform == 'linux'" },
{ name = "nvidia-cuda-nvrtc" },
]
nvtx = [
{ name = "nvidia-nvtx", marker = "sys_platform == 'linux'" },
{ name = "nvidia-nvtx" },
]
[[package]]
@ -1272,45 +1272,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e8/2d/d2a548598be01649e2d46231d151a6c56d10b964d94043a335ae56ea2d92/flatbuffers-25.12.19-py2.py3-none-any.whl", hash = "sha256:7634f50c427838bb021c2d66a3d1168e9d199b0607e6329399f04846d42e20b4", size = 26661, upload-time = "2025-12-19T23:16:13.622Z" },
]
[[package]]
name = "fonttools"
version = "4.63.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/84/69/c97f2c18e0db87d2c7b15da1974dace76ae938f1cfa22e2727a648b7ed43/fonttools-4.63.0.tar.gz", hash = "sha256:caeb583deeb5168e694b65cda8b4ee62abedfa66cf88488734466f2366b9c4e0", size = 3597189, upload-time = "2026-05-14T12:04:30.958Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/75/2b/a7f1545bdf5da69c4bda0cea2a5781f0ad2a6623e0277267672db43c5fe6/fonttools-4.63.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b8ae05d9eacf6081414d759c0a352769ac28ce31280d6bb8e77b03f9e3c449f", size = 2881793, upload-time = "2026-05-14T12:02:56.645Z" },
{ url = "https://files.pythonhosted.org/packages/49/50/965308c703f085f225db2886813b27e015b8b3438c350b22dd65b52c2a2c/fonttools-4.63.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79cdc9f567aec74a72918fd060283911406750cbc9fd28c1316023deb6ce31a9", size = 2428130, upload-time = "2026-05-14T12:02:58.891Z" },
{ url = "https://files.pythonhosted.org/packages/d8/38/6937fbd7f2dc3a6b48725851bc2c15ec949b9af14d9bbcb5fe83cdf9bdf9/fonttools-4.63.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c14b4fd138c4bafcca294765c547914e1aa431ae1ca94ab99d8db08c958bd3b", size = 5111952, upload-time = "2026-05-14T12:03:01.263Z" },
{ url = "https://files.pythonhosted.org/packages/0b/43/a81f20050a3115b57d62c8e781446949512eac36690dc384ccea65ff4cc1/fonttools-4.63.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76ac49f929aecaf82d83250b8347e099d7aecba0f4726c1d9b6df3b8bb5fe18", size = 5082308, upload-time = "2026-05-14T12:03:03.211Z" },
{ url = "https://files.pythonhosted.org/packages/67/00/cdd9d4944ca6ae280d01e69cc37bde3bf663630b837a6fc6d2cd65d80e0e/fonttools-4.63.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dcf076a4474fe0d7367e5bbf5b052c7284fa1feca729c04176ce513521afd8a0", size = 5087932, upload-time = "2026-05-14T12:03:05.147Z" },
{ url = "https://files.pythonhosted.org/packages/f5/f1/0aa0dbea778c75adbef223c42019fd47d22262b905974d62d829545d485f/fonttools-4.63.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7dd683fef0663e9f0f45cf541d788d24caa3ec9db50796b588e1757d8b3bc007", size = 5213271, upload-time = "2026-05-14T12:03:07.238Z" },
{ url = "https://files.pythonhosted.org/packages/a8/99/253e4056e1f0e67b9390125a154b73b5eb73ad521bece95c004858fdeec2/fonttools-4.63.0-cp311-cp311-win32.whl", hash = "sha256:afefc1ed0a59785a7fb06ea7e1678e849c193e1e387db783579bc7b3056fcfcb", size = 2304473, upload-time = "2026-05-14T12:03:09.271Z" },
{ url = "https://files.pythonhosted.org/packages/08/60/defa5e69641db890a63be281f41345f4c33b157824eaf0b9fad3e08b0dcb/fonttools-4.63.0-cp311-cp311-win_amd64.whl", hash = "sha256:063e08bd17bd5a90127a14123de0d6a952dbc847695fd98b63c043d58057f90c", size = 2356389, upload-time = "2026-05-14T12:03:11.53Z" },
{ url = "https://files.pythonhosted.org/packages/08/ef/b3c6b9b5be2f82416d73fe2ed2e96e2793cd80e7510bd6a17ca79cdd88ec/fonttools-4.63.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:37dd23e621e3b0aef1baa70a303b80aaf38449632cfc8fd2a55fb285bbccfc02", size = 2881131, upload-time = "2026-05-14T12:03:13.386Z" },
{ url = "https://files.pythonhosted.org/packages/44/a0/c815bea63117fa63e4e1c01f8a1110d2112fa003f838e6467094ec2432ce/fonttools-4.63.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a9faff9e0c1f76f9fd55899d2ce785832efebab37eb8ae13995853aef178bef0", size = 2426704, upload-time = "2026-05-14T12:03:15.801Z" },
{ url = "https://files.pythonhosted.org/packages/44/04/0b91d8e916e92ad1fac9e4624760baf0fd5ff2ead614c2f68fb21373f03f/fonttools-4.63.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef3048ef05dbb552b89817713d9cac912e00d0fde4a3105c00d29e52e10c89af", size = 5044298, upload-time = "2026-05-14T12:03:18.085Z" },
{ url = "https://files.pythonhosted.org/packages/77/c7/2342da9830e3e9d4870305ca5d2091d2a83284f2953079b7bdd3b5e029d8/fonttools-4.63.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58dc6bb86a78d782f00f9190ca02c119cf5bbe2807536e361e18d42019f877d8", size = 4999800, upload-time = "2026-05-14T12:03:20.161Z" },
{ url = "https://files.pythonhosted.org/packages/e6/6d/67fe16c48d7ce050979b33f47e0d28a318f02da030602e944c34f7a16ef3/fonttools-4.63.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee08ebfa58f6e1aeff5697ab9582105bb620008c1caafb681e4c557e7483027b", size = 4982666, upload-time = "2026-05-14T12:03:22.87Z" },
{ url = "https://files.pythonhosted.org/packages/f2/00/3bbab338c07c71fa56269953845e92c951a61457bbbb0f1022551ea266d9/fonttools-4.63.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:27fdc65af8da6f88b9c6121c47a464cbe359fcfff7ff6fc2d37a1f395d755b78", size = 5133598, upload-time = "2026-05-14T12:03:25.168Z" },
{ url = "https://files.pythonhosted.org/packages/62/f2/aa27c7f98db5b064883dadcc5283947e81e034de42e22a33675878d98b54/fonttools-4.63.0-cp312-cp312-win32.whl", hash = "sha256:af2fd1664d00a397d75f806985ddb36282091c2131a73a6485c23b4a34722263", size = 2292575, upload-time = "2026-05-14T12:03:27.496Z" },
{ url = "https://files.pythonhosted.org/packages/87/36/cccb9bc2a6ab63d1b2980374f0dca72ce95ae267c9b4cfe77455bb70d0d4/fonttools-4.63.0-cp312-cp312-win_amd64.whl", hash = "sha256:59ac449f8cca9b4ffa08d2e7bbadad87ce710d69d1eda5c3c1ce579baa987272", size = 2343211, upload-time = "2026-05-14T12:03:30.057Z" },
{ url = "https://files.pythonhosted.org/packages/2c/47/c99d5268f354002ce80f8d029cd9d7d872969da1de8b93d32de4dc56d6f4/fonttools-4.63.0-py3-none-any.whl", hash = "sha256:445af2eab030a16b9171ea8bdda7ebf7d96bda2df88ee182a464252f6e05e20d", size = 1164562, upload-time = "2026-05-14T12:04:29.092Z" },
]
[[package]]
name = "fpdf2"
version = "2.8.7"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "defusedxml" },
{ name = "fonttools" },
{ name = "pillow" },
]
sdist = { url = "https://files.pythonhosted.org/packages/27/f2/72feae0b2827ed38013e4307b14f95bf0b3d124adfef4d38a7d57533f7be/fpdf2-2.8.7.tar.gz", hash = "sha256:7060ccee5a9c7ab0a271fb765a36a23639f83ef8996c34e3d46af0a17ede57f9", size = 362351, upload-time = "2026-02-28T05:39:16.456Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/66/0a/cf50ecffa1e3747ed9380a3adfc829259f1f86b3fdbd9e505af789003141/fpdf2-2.8.7-py3-none-any.whl", hash = "sha256:d391fc508a3ce02fc43a577c830cda4fe6f37646f2d143d489839940932fbc19", size = 327056, upload-time = "2026-02-28T05:39:14.619Z" },
]
[[package]]
name = "frozenlist"
version = "1.8.0"
@ -2607,7 +2568,7 @@ name = "nvidia-cublas"
version = "13.1.1.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-cuda-nvrtc", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
{ name = "nvidia-cuda-nvrtc" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/a7/a1/0bd24ee8c8d03adac032fd2909426a00c88f8c57961b1277ded97f91119f/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b7a210458267ac818974c53038fbec2e969d5c99f305ab15c72522fa9f001dd5", size = 542848918, upload-time = "2026-04-08T18:46:22.985Z" },
@ -2646,7 +2607,7 @@ name = "nvidia-cudnn-cu13"
version = "9.20.0.48"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-cublas", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
{ name = "nvidia-cublas" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/56/c5/83384d846b2fd17c44bd499b36c75a45ed4f095fbbb2252294e89cea5c5c/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:e31454ae00094b0c55319d9d15b6fa2fc50a9e1c0f5c8c80fb75258234e731e1", size = 444574296, upload-time = "2026-03-09T19:28:27.751Z" },
@ -2658,7 +2619,7 @@ name = "nvidia-cufft"
version = "12.0.0.61"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-nvjitlink", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
{ name = "nvidia-nvjitlink" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" },
@ -2688,9 +2649,9 @@ name = "nvidia-cusolver"
version = "12.0.4.66"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-cublas", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
{ name = "nvidia-cusparse", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
{ name = "nvidia-nvjitlink", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
{ name = "nvidia-cublas" },
{ name = "nvidia-cusparse" },
{ name = "nvidia-nvjitlink" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" },
@ -2702,7 +2663,7 @@ name = "nvidia-cusparse"
version = "12.6.3.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-nvjitlink", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
{ name = "nvidia-nvjitlink" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" },
@ -2841,7 +2802,6 @@ dependencies = [
{ name = "fake-useragent" },
{ name = "fastapi" },
{ name = "faster-whisper" },
{ name = "fpdf2" },
{ name = "ftfy" },
{ name = "google-cloud-storage" },
{ name = "google-genai" },
@ -2969,7 +2929,6 @@ requires-dist = [
{ name = "fake-useragent", specifier = "==2.2.0" },
{ name = "fastapi", specifier = "==0.136.3" },
{ name = "faster-whisper", specifier = "==1.2.1" },
{ name = "fpdf2", specifier = "==2.8.7" },
{ name = "ftfy", specifier = "==6.3.1" },
{ name = "google-cloud-storage", specifier = "==3.9.0" },
{ name = "google-genai", specifier = "==1.66.0" },
@ -4708,7 +4667,7 @@ resolution-markers = [
"python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'",
]
dependencies = [
{ name = "numpy", marker = "python_full_version < '3.12'" },
{ name = "numpy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" }
wheels = [
@ -4744,7 +4703,7 @@ resolution-markers = [
"python_full_version >= '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'",
]
dependencies = [
{ name = "numpy", marker = "python_full_version >= '3.12'" },
{ name = "numpy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a7/25/c2700dfaf6442b4effaa91af24ebce5dc9d31bb4a69706313aae70d72cd0/scipy-1.18.0.tar.gz", hash = "sha256:67b2ad2ad54c72ca6d04975a9b2df8c3638c34ddd5b28738e94fc2b57929d378", size = 30774447, upload-time = "2026-06-19T15:01:43.456Z" }
wheels = [