Files
linear-coding-agent/generations/library_rag/utils/pdf_exporter.py
David Blanc Brioir b835cd13ea Ajout des fonctionnalités d'export Word et PDF pour le chat RAG
- Ajout de python-docx et reportlab aux dépendances
- Création du module utils/word_exporter.py pour l'export Word
- Création du module utils/pdf_exporter.py pour l'export PDF
- Ajout des routes /chat/export-word et /chat/export-pdf dans flask_app.py
- Ajout des boutons d'export (Word et PDF) dans chat.html
- Les boutons apparaissent après chaque réponse de l'assistant
- Support des questions reformulées avec question originale

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-30 14:02:11 +01:00

205 lines
6.0 KiB
Python

"""Generate PDF documents from chat exchanges.
This module provides functionality to export chat conversations between users
and the RAG assistant into formatted PDF documents.
Example:
Export a simple chat exchange::
from pathlib import Path
from utils.pdf_exporter import create_chat_export_pdf
filepath = create_chat_export_pdf(
user_question="What is phenomenology?",
assistant_response="Phenomenology is a philosophical movement...",
output_dir=Path("output")
)
Export with reformulated question::
filepath = create_chat_export_pdf(
user_question="What does Husserl mean by phenomenology?",
assistant_response="Husserl defines phenomenology as...",
is_reformulated=True,
original_question="What is phenomenology?",
output_dir=Path("output")
)
"""
from pathlib import Path
from typing import Optional
from datetime import datetime
try:
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm
from reportlab.lib.colors import HexColor
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
from reportlab.lib.enums import TA_CENTER, TA_JUSTIFY
except ImportError:
raise ImportError(
"reportlab is required for PDF export. "
"Install with: pip install reportlab"
)
def create_chat_export_pdf(
user_question: str,
assistant_response: str,
is_reformulated: bool = False,
original_question: Optional[str] = None,
output_dir: Path = Path("output"),
) -> Path:
"""Create a PDF document from a chat exchange.
Args:
user_question: The user's question (or reformulated version).
assistant_response: The assistant's complete response text.
is_reformulated: Whether the question was reformulated by the system.
If True, both original and reformulated questions are included.
original_question: The original user question before reformulation.
Only used when is_reformulated is True.
output_dir: Directory where the PDF file will be saved.
Created if it doesn't exist.
Returns:
Path to the generated PDF file.
Raises:
OSError: If the output directory cannot be created or file cannot be saved.
Example:
>>> from pathlib import Path
>>> filepath = create_chat_export_pdf(
... user_question="Qu'est-ce que la phénoménologie ?",
... assistant_response="La phénoménologie est...",
... output_dir=Path("output/test_exports")
... )
>>> print(filepath)
output/test_exports/chat_export_20250101_123045.pdf
"""
# Generate filename with timestamp
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"chat_export_{timestamp}.pdf"
filepath = output_dir / filename
# Create directory if needed
output_dir.mkdir(parents=True, exist_ok=True)
# Create PDF document
doc = SimpleDocTemplate(
str(filepath),
pagesize=A4,
rightMargin=2 * cm,
leftMargin=2 * cm,
topMargin=2.5 * cm,
bottomMargin=2.5 * cm,
)
# Get default styles
styles = getSampleStyleSheet()
# Custom styles
title_style = ParagraphStyle(
"CustomTitle",
parent=styles["Heading1"],
fontSize=18,
textColor=HexColor("#2B2B2B"),
spaceAfter=6,
alignment=TA_CENTER,
fontName="Helvetica-Bold",
)
date_style = ParagraphStyle(
"DateStyle",
parent=styles["Normal"],
fontSize=9,
textColor=HexColor("#808080"),
alignment=TA_CENTER,
spaceAfter=20,
)
heading_style = ParagraphStyle(
"CustomHeading",
parent=styles["Heading2"],
fontSize=12,
textColor=HexColor("#7D6E58"),
spaceAfter=10,
spaceBefore=15,
fontName="Helvetica-Bold",
)
question_style = ParagraphStyle(
"QuestionStyle",
parent=styles["Normal"],
fontSize=11,
textColor=HexColor("#2B2B2B"),
spaceAfter=10,
leftIndent=1 * cm,
rightIndent=1 * cm,
backColor=HexColor("#F8F4EE"),
borderPadding=10,
)
body_style = ParagraphStyle(
"BodyStyle",
parent=styles["Normal"],
fontSize=11,
textColor=HexColor("#2B2B2B"),
spaceAfter=10,
alignment=TA_JUSTIFY,
leading=16,
)
footer_style = ParagraphStyle(
"FooterStyle",
parent=styles["Normal"],
fontSize=8,
textColor=HexColor("#969696"),
alignment=TA_CENTER,
fontName="Helvetica-Oblique",
)
# Build document content
story = []
# Title
story.append(Paragraph("Conversation RAG - Export", title_style))
# Date
date_text = f"Exporté le {datetime.now().strftime('%d/%m/%Y à %H:%M')}"
story.append(Paragraph(date_text, date_style))
# Original question (if reformulated)
if is_reformulated and original_question:
story.append(Paragraph("Question Originale", heading_style))
story.append(Paragraph(original_question, question_style))
story.append(Spacer(1, 0.5 * cm))
# User question
question_label = "Question Reformulée" if is_reformulated else "Question"
story.append(Paragraph(question_label, heading_style))
story.append(Paragraph(user_question, question_style))
story.append(Spacer(1, 0.5 * cm))
# Assistant response
story.append(Paragraph("Réponse de l'Assistant", heading_style))
# Split response into paragraphs
response_paragraphs = assistant_response.split("\n\n")
for para in response_paragraphs:
if para.strip():
story.append(Paragraph(para.strip(), body_style))
# Footer
story.append(Spacer(1, 1 * cm))
story.append(
Paragraph("Généré par Library RAG - Recherche Philosophique", footer_style)
)
# Build PDF
doc.build(story)
return filepath