diff --git a/backend/open_webui/apps/webui/routers/utils.py b/backend/open_webui/apps/webui/routers/utils.py index bab30aff6..0ab0f6b15 100644 --- a/backend/open_webui/apps/webui/routers/utils.py +++ b/backend/open_webui/apps/webui/routers/utils.py @@ -56,8 +56,17 @@ class ChatForm(BaseModel): async def download_chat_as_pdf( form_data: ChatTitleMessagesForm, ): - response = PDFGenerator(form_data).generate_chat_pdf() - return response + 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: + print(e) + raise HTTPException(status_code=400, detail=str(e)) @router.get("/db/download") diff --git a/backend/open_webui/env.py b/backend/open_webui/env.py index 0f2ecada0..a291bb188 100644 --- a/backend/open_webui/env.py +++ b/backend/open_webui/env.py @@ -230,6 +230,8 @@ if FROM_INIT_PY: DATA_DIR = Path(os.getenv("DATA_DIR", OPEN_WEBUI_DIR / "data")) +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() diff --git a/backend/open_webui/static/assets/pdf-style.css b/backend/open_webui/static/assets/pdf-style.css index bc83c3c64..db9ac83dd 100644 --- a/backend/open_webui/static/assets/pdf-style.css +++ b/backend/open_webui/static/assets/pdf-style.css @@ -1,190 +1,230 @@ /* HTML and Body */ -html { - box-sizing: border-box; - font-size: 14px; /* Default font size */ - line-height: 1.5; +@font-face { + font-family: 'NotoSans'; + src: url('fonts/NotoSans-Variable.ttf'); } -*, *::before, *::after { - box-sizing: inherit; +@font-face { + font-family: 'NotoSansJP'; + src: url('fonts/NotoSansJP-Variable.ttf'); +} + +@font-face { + font-family: 'NotoSansKR'; + src: url('fonts/NotoSansKR-Variable.ttf'); +} + +@font-face { + font-family: 'NotoSansSC'; + src: url('fonts/NotoSansSC-Variable.ttf'); +} + +@font-face { + font-family: 'NotoSansSC-Regular'; + src: url('fonts/NotoSansSC-Regular.ttf'); +} + +html { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'NotoSans', 'NotoSansJP', 'NotoSansKR', + 'NotoSansSC', '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; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; - color: #212529; - background-color: #fff; - width: auto; + margin: 0; + color: #212529; + background-color: #fff; + width: auto; } /* Typography */ -h1, h2, h3, h4, h5, h6 { - font-weight: 500; - margin: 0; +h1, +h2, +h3, +h4, +h5, +h6 { + font-weight: 500; + margin: 0; } h1 { - font-size: 2.5rem; + font-size: 2.5rem; } h2 { - font-size: 2rem; + font-size: 2rem; } h3 { - font-size: 1.75rem; + font-size: 1.75rem; } h4 { - font-size: 1.5rem; + font-size: 1.5rem; } h5 { - font-size: 1.25rem; + font-size: 1.25rem; } h6 { - font-size: 1rem; + font-size: 1rem; } p { - margin-top: 0; - margin-bottom: 1rem; + margin-top: 0; + margin-bottom: 1rem; } /* Grid System */ .container { - width: 100%; - padding-right: 15px; - padding-left: 15px; - margin-right: auto; - margin-left: auto; + width: 100%; + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; } /* Utilities */ .text-center { - text-align: center; + text-align: center; } /* Additional Text Utilities */ .text-muted { - color: #6c757d; /* Muted text color */ + 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; + 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 */ + 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; + color: #007bff; + text-decoration: none; + background-color: transparent; } a:hover { - color: #0056b3; - text-decoration: underline; + 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 */ +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 */ + 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 */ + 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 */ + 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 */ + 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 */ + 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 */ +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 */ + 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 */ + 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; + 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 */ + 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; +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; + vertical-align: bottom; + border-bottom: 2px solid #dee2e6; } tbody + tbody { - border-top: 2px solid #dee2e6; + border-top: 2px solid #dee2e6; } /* markdown-section styles */ @@ -199,8 +239,8 @@ tbody + tbody { .markdown-section pre, .markdown-section table, .markdown-section ul { - /* Give most block elements margin top and bottom */ - margin-top: 1rem; + /* Give most block elements margin top and bottom */ + margin-top: 1rem; } /* Remove top margin if it's the first child */ @@ -215,69 +255,65 @@ tbody + tbody { .markdown-section pre:first-child, .markdown-section table:first-child, .markdown-section ul:first-child { - margin-top: 0; + margin-top: 0; } - /* Remove top margin of
*/ .markdown-section p + ul { - margin-top: 0; + margin-top: 0; } /* Remove bottom margin of
if it is followed by a
not followed by