| 
									
										
										
										
											2024-10-13 00:12:31 +08:00
										 |  |  | from datetime import datetime | 
					
						
							|  |  |  | from io import BytesIO | 
					
						
							|  |  |  | from pathlib import Path | 
					
						
							|  |  |  | from typing import Dict, Any, List | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from markdown import markdown | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-13 15:05:28 +08:00
										 |  |  | import site | 
					
						
							|  |  |  | from fpdf import FPDF | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from open_webui.env import STATIC_DIR, FONTS_DIR | 
					
						
							| 
									
										
										
										
											2024-12-10 16:54:13 +08:00
										 |  |  | from open_webui.models.chats import ChatTitleMessagesForm | 
					
						
							| 
									
										
										
										
											2024-10-13 00:12:31 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class PDFGenerator: | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Description: | 
					
						
							|  |  |  |     The `PDFGenerator` class is designed to create PDF documents from chat messages. | 
					
						
							| 
									
										
										
										
											2024-10-13 15:21:06 +08:00
										 |  |  |     The process involves transforming markdown content into HTML and then into a PDF format | 
					
						
							| 
									
										
										
										
											2024-10-13 00:12:31 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     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 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-13 15:05:28 +08:00
										 |  |  |         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: | 
					
						
							| 
									
										
										
										
											2024-10-13 00:12:31 +08:00
										 |  |  |         """Build HTML for a single message.""" | 
					
						
							|  |  |  |         role = message.get("role", "user") | 
					
						
							|  |  |  |         content = message.get("content", "") | 
					
						
							| 
									
										
										
										
											2024-10-13 15:05:28 +08:00
										 |  |  |         timestamp = message.get("timestamp") | 
					
						
							| 
									
										
										
										
											2024-10-13 00:12:31 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-13 15:05:28 +08:00
										 |  |  |         model = message.get("model") if role == "assistant" else "" | 
					
						
							| 
									
										
										
										
											2024-10-13 00:12:31 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-13 15:05:28 +08:00
										 |  |  |         date_str = self.format_timestamp(timestamp) if timestamp else "" | 
					
						
							| 
									
										
										
										
											2024-10-13 00:12:31 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # extends pymdownx extension to convert markdown to html. | 
					
						
							|  |  |  |         # - https://facelessuser.github.io/pymdown-extensions/usage_notes/ | 
					
						
							| 
									
										
										
										
											2024-12-08 06:28:17 +08:00
										 |  |  |         # html_content = markdown(content, extensions=["pymdownx.extra"]) | 
					
						
							| 
									
										
										
										
											2024-10-13 00:12:31 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-08 23:49:53 +08:00
										 |  |  |         content = content.replace("\n", "<br/>") | 
					
						
							| 
									
										
										
										
											2024-10-13 00:12:31 +08:00
										 |  |  |         html_message = f"""
 | 
					
						
							| 
									
										
										
										
											2024-12-08 06:28:17 +08:00
										 |  |  |             <div> | 
					
						
							| 
									
										
										
										
											2024-10-28 20:47:53 +08:00
										 |  |  |                 <div> | 
					
						
							| 
									
										
										
										
											2024-12-08 06:28:17 +08:00
										 |  |  |                     <h4> | 
					
						
							| 
									
										
										
										
											2024-10-28 20:47:53 +08:00
										 |  |  |                         <strong>{role.title()}</strong> | 
					
						
							| 
									
										
										
										
											2024-12-08 06:28:17 +08:00
										 |  |  |                         <span style="font-size: 12px;">{model}</span> | 
					
						
							|  |  |  |                     </h4> | 
					
						
							|  |  |  |                     <div> {date_str} </div> | 
					
						
							| 
									
										
										
										
											2024-10-28 20:47:53 +08:00
										 |  |  |                 </div> | 
					
						
							| 
									
										
										
										
											2024-12-08 06:28:17 +08:00
										 |  |  |                 <br/> | 
					
						
							|  |  |  |                 <br/> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 <div> | 
					
						
							| 
									
										
										
										
											2024-10-28 20:47:53 +08:00
										 |  |  |                     {content} | 
					
						
							| 
									
										
										
										
											2024-12-08 06:28:17 +08:00
										 |  |  |                 </div> | 
					
						
							| 
									
										
										
										
											2024-10-28 20:47:53 +08:00
										 |  |  |             </div> | 
					
						
							| 
									
										
										
										
											2024-12-08 06:28:17 +08:00
										 |  |  |             <br/> | 
					
						
							| 
									
										
										
										
											2024-10-13 00:12:31 +08:00
										 |  |  |           """
 | 
					
						
							|  |  |  |         return html_message | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-13 15:05:28 +08:00
										 |  |  |     def _generate_html_body(self) -> str: | 
					
						
							| 
									
										
										
										
											2024-10-13 00:12:31 +08:00
										 |  |  |         """Generate the full HTML body for the PDF.""" | 
					
						
							|  |  |  |         return f"""
 | 
					
						
							|  |  |  |         <html> | 
					
						
							|  |  |  |             <head> | 
					
						
							| 
									
										
										
										
											2024-12-08 06:28:17 +08:00
										 |  |  |                 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | 
					
						
							| 
									
										
										
										
											2024-10-13 00:12:31 +08:00
										 |  |  |             </head> | 
					
						
							|  |  |  |             <body> | 
					
						
							| 
									
										
										
										
											2024-12-08 06:28:17 +08:00
										 |  |  |             <div> | 
					
						
							|  |  |  |                 <div> | 
					
						
							|  |  |  |                     <h2>{self.form_data.title}</h2> | 
					
						
							|  |  |  |                     {self.messages_html} | 
					
						
							| 
									
										
										
										
											2024-10-13 00:12:31 +08:00
										 |  |  |                 </div> | 
					
						
							| 
									
										
										
										
											2024-12-08 06:28:17 +08:00
										 |  |  |             </div> | 
					
						
							| 
									
										
										
										
											2024-10-13 00:12:31 +08:00
										 |  |  |             </body> | 
					
						
							|  |  |  |         </html> | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2024-10-13 15:05:28 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     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") | 
					
						
							| 
									
										
										
										
											2024-12-08 06:28:17 +08:00
										 |  |  |             pdf.add_font("Twemoji", "", f"{FONTS_DIR}/Twemoji.ttf") | 
					
						
							| 
									
										
										
										
											2024-10-13 15:05:28 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |             pdf.set_font("NotoSans", size=12) | 
					
						
							| 
									
										
										
										
											2024-12-08 06:28:17 +08:00
										 |  |  |             pdf.set_fallback_fonts( | 
					
						
							|  |  |  |                 ["NotoSansKR", "NotoSansJP", "NotoSansSC", "Twemoji"] | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2024-10-13 15:05:28 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |             pdf.set_auto_page_break(auto=True, margin=15) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-13 15:21:06 +08:00
										 |  |  |             # 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) | 
					
						
							| 
									
										
										
										
											2024-10-13 15:05:28 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |             # Save the pdf with name .pdf | 
					
						
							|  |  |  |             pdf_bytes = pdf.output() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return bytes(pdf_bytes) | 
					
						
							|  |  |  |         except Exception as e: | 
					
						
							|  |  |  |             raise e |