feat: fase 3 — export PDF con reportlab + /export command
Build & Deploy ResearchOwl / build-and-push (push) Successful in 1m2s
Build & Deploy ResearchOwl / build-and-push (push) Successful in 1m2s
This commit is contained in:
@@ -445,3 +445,109 @@ Iterations: {session.get('iterations', 0)}
|
||||
Total words researched: {session.get('total_words', 0):,}
|
||||
---
|
||||
"""
|
||||
|
||||
|
||||
def generate_pdf(content: str, title: str = "ResearchOwl Output") -> bytes:
|
||||
try:
|
||||
from reportlab.lib.pagesizes import A4
|
||||
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
||||
from reportlab.lib.units import cm
|
||||
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, HRFlowable
|
||||
from reportlab.lib.enums import TA_LEFT
|
||||
from reportlab.lib import colors
|
||||
import io
|
||||
import re
|
||||
except ImportError:
|
||||
raise ImportError("reportlab is required for PDF export — pip install reportlab")
|
||||
|
||||
buf = io.BytesIO()
|
||||
doc = SimpleDocTemplate(
|
||||
buf,
|
||||
pagesize=A4,
|
||||
rightMargin=2 * cm,
|
||||
leftMargin=2 * cm,
|
||||
topMargin=2.5 * cm,
|
||||
bottomMargin=2 * cm,
|
||||
title=title,
|
||||
)
|
||||
|
||||
base = getSampleStyleSheet()
|
||||
normal = ParagraphStyle("RO_Normal", parent=base["Normal"],
|
||||
fontSize=10, leading=14, spaceAfter=4)
|
||||
h1 = ParagraphStyle("RO_H1", parent=base["Heading1"],
|
||||
fontSize=18, spaceBefore=12, spaceAfter=6,
|
||||
textColor=colors.HexColor("#1a1a2e"))
|
||||
h2 = ParagraphStyle("RO_H2", parent=base["Heading2"],
|
||||
fontSize=14, spaceBefore=10, spaceAfter=4,
|
||||
textColor=colors.HexColor("#16213e"))
|
||||
h3 = ParagraphStyle("RO_H3", parent=base["Heading3"],
|
||||
fontSize=12, spaceBefore=8, spaceAfter=4)
|
||||
code_style = ParagraphStyle("RO_Code", parent=base["Code"],
|
||||
fontSize=9, leading=12, fontName="Courier",
|
||||
backColor=colors.HexColor("#f4f4f4"), spaceAfter=4)
|
||||
bullet_style = ParagraphStyle("RO_Bullet", parent=normal,
|
||||
leftIndent=20, bulletIndent=10, spaceAfter=2)
|
||||
|
||||
def md_to_para(text: str) -> str:
|
||||
text = text.replace("&", "&").replace("<", "<").replace(">", ">")
|
||||
text = re.sub(r'\*\*(.+?)\*\*', r'<b>\1</b>', text)
|
||||
text = re.sub(r'__(.+?)__', r'<b>\1</b>', text)
|
||||
text = re.sub(r'\*(.+?)\*', r'<i>\1</i>', text)
|
||||
text = re.sub(r'_(.+?)_', r'<i>\1</i>', text)
|
||||
text = re.sub(r'`(.+?)`', r'<font name="Courier">\1</font>', text)
|
||||
return text
|
||||
|
||||
story = []
|
||||
lines = content.split("\n")
|
||||
in_code = False
|
||||
code_buf = []
|
||||
|
||||
for line in lines:
|
||||
if line.startswith("```"):
|
||||
if not in_code:
|
||||
in_code = True
|
||||
code_buf = []
|
||||
else:
|
||||
in_code = False
|
||||
try:
|
||||
story.append(Paragraph(
|
||||
"<br/>".join(l.replace("&", "&").replace("<", "<").replace(">", ">")
|
||||
for l in code_buf),
|
||||
code_style
|
||||
))
|
||||
except Exception:
|
||||
pass
|
||||
continue
|
||||
|
||||
if in_code:
|
||||
code_buf.append(line)
|
||||
continue
|
||||
|
||||
if re.match(r'^[-*_]{3,}$', line.strip()):
|
||||
story.append(HRFlowable(width="100%", thickness=0.5,
|
||||
color=colors.grey, spaceAfter=6))
|
||||
continue
|
||||
|
||||
try:
|
||||
if line.startswith("### "):
|
||||
story.append(Paragraph(md_to_para(line[4:]), h3))
|
||||
elif line.startswith("## "):
|
||||
story.append(Paragraph(md_to_para(line[3:]), h2))
|
||||
elif line.startswith("# "):
|
||||
story.append(Paragraph(md_to_para(line[2:]), h1))
|
||||
elif re.match(r'^[-*+] ', line):
|
||||
story.append(Paragraph("• " + md_to_para(line[2:]), bullet_style))
|
||||
elif re.match(r'^\d+\. ', line):
|
||||
story.append(Paragraph(md_to_para(line), bullet_style))
|
||||
elif line.strip() == "":
|
||||
story.append(Spacer(1, 6))
|
||||
else:
|
||||
story.append(Paragraph(md_to_para(line), normal))
|
||||
except Exception:
|
||||
try:
|
||||
story.append(Paragraph(line[:300], normal))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
doc.build(story)
|
||||
return buf.getvalue()
|
||||
|
||||
Reference in New Issue
Block a user