from datetime import datetime from io import BytesIO from pathlib import Path from typing import Dict, Any, List from markdown import markdown from starlette.responses import Response from xhtml2pdf import pisa from open_webui.apps.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, which can be easily returned as a response to the routes. It depends on xhtml2pdf for converting HTML to PDF (more details at https://github.com/xhtml2pdf/xhtml2pdf). I found xhtml2pdf issues when rendering list html tag, see https://github.com/xhtml2pdf/xhtml2pdf/issues/550 and https://github.com/xhtml2pdf/xhtml2pdf/issues/756. 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_style_file = Path("./backend/open_webui/static/assets/pdf-style.css") def build_html_message(self, message: Dict[str, Any]) -> str: """Build HTML for a single message.""" role = message.get("role", "user") content = message.get("content", "") timestamp = message.get('timestamp') model = message.get('model') if role == 'assistant' else '' date_str = 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']) html_message = f"""
""" return html_message def create_pdf_from_html(self) -> bytes: """Convert HTML content to PDF and return the bytes.""" pdf_buffer = BytesIO() pisa_status = pisa.CreatePDF(src=self.html_body, dest=pdf_buffer) if pisa_status.err: raise RuntimeError("Error generating PDF") return pdf_buffer.getvalue() 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 generate_chat_pdf(self) -> Response: """ Generate a PDF from chat messages. Returns: A FastAPI Response with the generated PDF or an error message. """ try: # Build HTML messages messages_html_list: List[str] = [self.build_html_message(msg) for msg in self.form_data.messages] self.messages_html = '