diff --git a/README.es.md b/README.es.md
index 06dc7abbc..a1f5b80d8 100644
--- a/README.es.md
+++ b/README.es.md
@@ -62,7 +62,7 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7

- - Generación de informes y exportaciones (PDF, DOCX por ahora)
+ - Generación de informes y exportaciones (PDF, DOCX, HTML, LaTeX, EPUB, ODT, texto plano)

diff --git a/README.hi.md b/README.hi.md
index 22acebb8c..7a4822e68 100644
--- a/README.hi.md
+++ b/README.hi.md
@@ -62,7 +62,7 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7

- - रिपोर्ट जनरेशन और एक्सपोर्ट (फ़िलहाल PDF, DOCX)
+ - रिपोर्ट जनरेशन और एक्सपोर्ट (PDF, DOCX, HTML, LaTeX, EPUB, ODT, सादा टेक्स्ट)

diff --git a/README.md b/README.md
index 0ca61c746..f37664dd7 100644
--- a/README.md
+++ b/README.md
@@ -62,7 +62,7 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7

- - Report Generations and Exports (PDF, DOCX for now)
+ - Report Generations and Exports (PDF, DOCX, HTML, LaTeX, EPUB, ODT, Plain Text)

diff --git a/README.pt-BR.md b/README.pt-BR.md
index e537cb37f..5461d8824 100644
--- a/README.pt-BR.md
+++ b/README.pt-BR.md
@@ -62,7 +62,7 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7

- - Geração de relatórios e exportações (PDF, DOCX por enquanto)
+ - Geração de relatórios e exportações (PDF, DOCX, HTML, LaTeX, EPUB, ODT, texto simples)

diff --git a/README.zh-CN.md b/README.zh-CN.md
index 128a9780a..9333348b6 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -62,7 +62,7 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7

- - 报告生成和导出(目前支持 PDF、DOCX)
+ - 报告生成和导出(PDF、DOCX、HTML、LaTeX、EPUB、ODT、纯文本)

diff --git a/surfsense_backend/app/agents/new_chat/system_prompt.py b/surfsense_backend/app/agents/new_chat/system_prompt.py
index 1ca036c40..e9a5af341 100644
--- a/surfsense_backend/app/agents/new_chat/system_prompt.py
+++ b/surfsense_backend/app/agents/new_chat/system_prompt.py
@@ -151,14 +151,14 @@ up-to-date, or domain-specific information that is more relevant than your gener
- parent_report_id: Set this to the report_id from a previous generate_report result when the user wants to MODIFY an existing report. Do NOT set it for new reports or questions about reports.
- Returns: A dictionary with status "ready" or "failed", report_id, title, and word_count.
- The report is generated immediately in Markdown and displayed inline in the chat.
- - Export/download formats (e.g., PDF/DOCX) are produced from the generated Markdown report.
+ - Export/download formats (PDF, DOCX, HTML, LaTeX, EPUB, ODT, plain text) are produced from the generated Markdown report.
- SOURCE STRATEGY DECISION (HIGH PRIORITY — follow this exactly):
* If the conversation already has substantive Q&A / discussion on the topic → use source_strategy="conversation" with a comprehensive summary as source_content. Do NOT call search_knowledge_base first.
* If the user wants a report on a topic not yet discussed → use source_strategy="kb_search" with targeted search_queries. Do NOT call search_knowledge_base first.
* If you have some content but might need more → use source_strategy="auto" with both source_content and search_queries.
* When revising an existing report (parent_report_id set) and the conversation has relevant context → use source_strategy="conversation". The revision will use the previous report content plus your source_content.
* NEVER call search_knowledge_base and then pass its results to generate_report. The tool handles KB search internally.
- - AFTER CALLING THIS TOOL: Do NOT repeat, summarize, or reproduce the report content in the chat. The report is already displayed as an interactive card that the user can open, read, copy, and export. Simply confirm that the report was generated (e.g., "I've generated your report on [topic]. You can view the Markdown report now, and export to PDF/DOCX from the card."). NEVER write out the report text in the chat.
+ - AFTER CALLING THIS TOOL: Do NOT repeat, summarize, or reproduce the report content in the chat. The report is already displayed as an interactive card that the user can open, read, copy, and export. Simply confirm that the report was generated (e.g., "I've generated your report on [topic]. You can view the Markdown report now, and export it in various formats from the card."). NEVER write out the report text in the chat.
4. link_preview: Fetch metadata for a URL to display a rich preview card.
- IMPORTANT: Use this tool WHENEVER the user shares or mentions a URL/link in their message.
diff --git a/surfsense_backend/app/routes/__init__.py b/surfsense_backend/app/routes/__init__.py
index 7568c0f25..d7df2182a 100644
--- a/surfsense_backend/app/routes/__init__.py
+++ b/surfsense_backend/app/routes/__init__.py
@@ -55,7 +55,7 @@ router.include_router(new_chat_router) # Chat with assistant-ui persistence
router.include_router(sandbox_router) # Sandbox file downloads (Daytona)
router.include_router(chat_comments_router)
router.include_router(podcasts_router) # Podcast task status and audio
-router.include_router(reports_router) # Report CRUD and export (PDF/DOCX)
+router.include_router(reports_router) # Report CRUD and multi-format export
router.include_router(image_generation_router) # Image generation via litellm
router.include_router(search_source_connectors_router)
router.include_router(google_calendar_add_connector_router)
diff --git a/surfsense_backend/app/routes/reports_routes.py b/surfsense_backend/app/routes/reports_routes.py
index db89d1c4b..56ac5ec2d 100644
--- a/surfsense_backend/app/routes/reports_routes.py
+++ b/surfsense_backend/app/routes/reports_routes.py
@@ -1,12 +1,12 @@
"""
-Report routes for read, update, export (PDF/DOCX), and delete operations.
+Report routes for read, update, export, and delete operations.
Reports are generated inline by the agent tool during chat and stored as
Markdown in the database. Users can edit report content via the Plate editor
and save changes through the PUT endpoint.
-Export is on-demand in multiple formats (PDF, DOCX, HTML, PPTX, LaTeX, EPUB,
-ODT, plain text). PDF uses pypandoc (Markdown->Typst) + typst-py; the rest
-use pypandoc directly with format-specific templates and options.
+Export is on-demand in multiple formats (PDF, DOCX, HTML, LaTeX, EPUB, ODT,
+plain text). PDF uses pypandoc (Markdown->Typst) + typst-py; the rest use
+pypandoc directly with format-specific templates and options.
Authorization: lightweight search-space membership checks (no granular RBAC)
since reports are chat-generated artifacts, not standalone managed resources.
@@ -338,7 +338,7 @@ async def export_report(
report_id: int,
format: ExportFormat = Query(
ExportFormat.PDF,
- description="Export format: pdf, docx, html, pptx, latex, epub, odt, or plain",
+ description="Export format: pdf, docx, html, latex, epub, odt, or plain",
),
session: AsyncSession = Depends(get_async_session),
user: User = Depends(current_active_user),
diff --git a/surfsense_backend/app/templates/export_helpers.py b/surfsense_backend/app/templates/export_helpers.py
index 4367ac5a6..ba2c29ea1 100644
--- a/surfsense_backend/app/templates/export_helpers.py
+++ b/surfsense_backend/app/templates/export_helpers.py
@@ -4,7 +4,6 @@ Helpers for report export templates.
* ``get_typst_template_path()`` - path to the custom Pandoc -> Typst template.
* ``get_html_css_path()`` - path to the CSS stylesheet for HTML exports.
* ``get_reference_docx_path()`` - path to a styled reference.docx for Pandoc.
-* ``get_reference_pptx_path()`` - path to a styled reference.pptx for Pandoc.
The reference DOCX is generated lazily on first call from Pandoc's built-in
default, then restyled with *python-docx* and cached on disk so subsequent
@@ -20,12 +19,10 @@ from pathlib import Path
_DIR = Path(__file__).resolve().parent
_GENERATED_DIR = _DIR / "_generated"
_REFERENCE_DOCX = _GENERATED_DIR / "reference.docx"
-_REFERENCE_PPTX = _GENERATED_DIR / "reference.pptx"
_TYPST_TEMPLATE = _DIR / "report_pdf.typst"
_HTML_CSS = _DIR / "report_html.css"
_docx_lock = threading.Lock()
-_pptx_lock = threading.Lock()
def get_typst_template_path() -> Path:
@@ -36,17 +33,6 @@ def get_html_css_path() -> Path:
return _HTML_CSS
-def get_reference_pptx_path() -> Path:
- """Return path to the styled reference.pptx, creating it if absent."""
- if _REFERENCE_PPTX.exists():
- return _REFERENCE_PPTX
- with _pptx_lock:
- if _REFERENCE_PPTX.exists():
- return _REFERENCE_PPTX
- _generate_reference_pptx()
- return _REFERENCE_PPTX
-
-
def get_reference_docx_path() -> Path:
"""Return path to the styled reference.docx, creating it if absent."""
if _REFERENCE_DOCX.exists():
@@ -199,59 +185,6 @@ def _generate_reference_docx() -> None:
doc.save(str(_REFERENCE_DOCX))
-# ---------------------------------------------------------------------------
-# Reference PPTX generation
-# ---------------------------------------------------------------------------
-
-
-def _generate_reference_pptx() -> None:
- """Build a reference.pptx with smaller fonts for report-to-slide conversion."""
- import pypandoc
- from lxml import etree
-
- _GENERATED_DIR.mkdir(parents=True, exist_ok=True)
-
- pandoc_bin = pypandoc.get_pandoc_path()
- result = subprocess.run(
- [pandoc_bin, "--print-default-data-file", "reference.pptx"],
- capture_output=True,
- check=True,
- )
- _REFERENCE_PPTX.write_bytes(result.stdout)
-
- from pptx import Presentation
-
- prs = Presentation(str(_REFERENCE_PPTX))
- master = prs.slide_masters[0]
-
- ns = {
- "a": "http://schemas.openxmlformats.org/drawingml/2006/main",
- "p": "http://schemas.openxmlformats.org/presentationml/2006/main",
- }
-
- # Shrink body text: 24pt -> 16pt base, scaling down per level
- body_sizes = [1600, 1400, 1300, 1200, 1100, 1100, 1100, 1100, 1100]
- body_style = master._element.find(".//p:txStyles/p:bodyStyle", ns)
- if body_style is not None:
- for lvl_el in body_style:
- tag = etree.QName(lvl_el).localname
- if tag.startswith("lvl") and tag.endswith("pPr"):
- idx = int(tag[3]) - 1
- def_rpr = lvl_el.find("a:defRPr", ns)
- if def_rpr is not None and idx < len(body_sizes):
- def_rpr.set("sz", str(body_sizes[idx]))
-
- # Shrink title: 33pt -> 26pt
- title_style = master._element.find(".//p:txStyles/p:titleStyle", ns)
- if title_style is not None:
- for lvl_el in title_style:
- def_rpr = lvl_el.find("a:defRPr", ns)
- if def_rpr is not None:
- def_rpr.set("sz", "2600")
-
- prs.save(str(_REFERENCE_PPTX))
-
-
# ---------------------------------------------------------------------------
# OOXML helpers
# ---------------------------------------------------------------------------