invictatill-ai / export_tools.py
CI Bot
deploy: clean deploy with LFS binary tracking
550cb8d
Raw
History Blame Contribute Delete
3.25 kB
"""Conversation export helpers kept separate from Flask request handling."""
import os
import re
from typing import Iterable, Mapping
def _unicode_font(base_dir: str) -> str:
candidates = [
os.path.join(base_dir, "fonts", "NotoSans-Regular.ttf"),
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
r"C:\Windows\Fonts\arial.ttf",
]
return next((candidate for candidate in candidates if os.path.exists(candidate)), "")
def _core_font_text(value) -> str:
text = str(value or "")
replacements = {
"→": " -> ", "⇒": " => ", "—": " - ", "–": " - ",
"‘": "'", "’": "'", "“": '"', "”": '"', "…": "...",
"•": "-", "✓": "[done]", "✗": "[failed]",
}
for source, replacement in replacements.items():
text = text.replace(source, replacement)
return text.encode("latin-1", "replace").decode("latin-1")
def _readable_export_text(value) -> str:
text = str(value or "")
text = re.sub(r"^#{1,6}\s+", "", text, flags=re.MULTILINE)
text = text.replace("**", "").replace("__", "")
text = re.sub(r"^- \[ \]\s+", "[ ] ", text, flags=re.MULTILINE)
text = re.sub(r"^- \[[xX]\]\s+", "[done] ", text, flags=re.MULTILINE)
return text
def create_conversation_pdf(messages: Iterable[Mapping[str, str]], output_path: str, base_dir: str) -> str:
"""Create a paginated export without silently truncating normal conversation turns."""
from fpdf import FPDF
class ConversationPDF(FPDF):
def header(self):
if self.page_no() <= 1:
return
self.set_font("Helvetica", "", 8)
self.set_text_color(90, 100, 115)
self.cell(0, 6, "Invicta AI Conversation Export", align="R")
self.ln(10)
self.set_text_color(0, 0, 0)
def footer(self):
self.set_y(-12)
self.set_font("Helvetica", "", 8)
self.set_text_color(110, 120, 132)
self.cell(0, 6, f"Page {self.page_no()}/{{nb}}", align="C")
pdf = ConversationPDF()
pdf.alias_nb_pages()
pdf.set_title("Invicta AI Conversation Export")
pdf.set_author("InvictaTill AI")
pdf.set_auto_page_break(auto=True, margin=15)
pdf.add_page()
font_path = _unicode_font(base_dir)
if font_path:
pdf.add_font("InvictaUnicode", "", font_path)
pdf.set_font("InvictaUnicode", "", 16)
else:
pdf.set_font("Helvetica", "B", 16)
pdf.cell(0, 10, "Invicta AI Conversation Export", new_x="LMARGIN", new_y="NEXT", align="C")
pdf.ln(5)
for message in messages:
role = str(message.get("role", "user")).upper()
content = _readable_export_text(str(message.get("content", ""))[:20_000])
if not font_path:
role = _core_font_text(role)
content = _core_font_text(content)
pdf.set_font("InvictaUnicode" if font_path else "Helvetica", "" if font_path else "B", 11)
pdf.multi_cell(0, 6, f"[{role}]", new_x="LMARGIN", new_y="NEXT")
pdf.set_font("InvictaUnicode" if font_path else "Helvetica", "", 10)
pdf.multi_cell(0, 5.5, content, new_x="LMARGIN", new_y="NEXT")
pdf.ln(2)
pdf.output(output_path)
return output_path