mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-15 18:25:18 +02:00
Merge pull request #1240 from AnishSarkar22/feat/resume-builder
Some checks failed
Build and Push Docker Images / tag_release (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_web, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-24.04-arm, linux/arm64, arm64) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_web, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-latest, linux/amd64, amd64) (push) Has been cancelled
Build and Push Docker Images / create_manifest (backend, surfsense-backend) (push) Has been cancelled
Build and Push Docker Images / create_manifest (web, surfsense-web) (push) Has been cancelled
Some checks failed
Build and Push Docker Images / tag_release (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_web, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-24.04-arm, linux/arm64, arm64) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_web, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-latest, linux/amd64, amd64) (push) Has been cancelled
Build and Push Docker Images / create_manifest (backend, surfsense-backend) (push) Has been cancelled
Build and Push Docker Images / create_manifest (web, surfsense-web) (push) Has been cancelled
feat: resume builder
This commit is contained in:
commit
2b2453e015
24 changed files with 1917 additions and 60 deletions
|
|
@ -231,6 +231,57 @@ def _replace_audio_paths_with_public_urls(
|
|||
return result
|
||||
|
||||
|
||||
@router.get("/{share_token}/reports/{report_id}/preview")
|
||||
async def preview_public_report_pdf(
|
||||
share_token: str,
|
||||
report_id: int,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""
|
||||
Return a compiled PDF preview for a Typst-based report in a public snapshot.
|
||||
|
||||
No authentication required - the share_token provides access.
|
||||
"""
|
||||
import asyncio
|
||||
import io
|
||||
import re
|
||||
|
||||
import typst as typst_compiler
|
||||
|
||||
report_info = await get_snapshot_report(session, share_token, report_id)
|
||||
|
||||
if not report_info:
|
||||
raise HTTPException(status_code=404, detail="Report not found")
|
||||
|
||||
content = report_info.get("content")
|
||||
content_type = report_info.get("content_type", "markdown")
|
||||
|
||||
if not content:
|
||||
raise HTTPException(status_code=400, detail="Report has no content to preview")
|
||||
|
||||
if content_type != "typst":
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Preview is only available for Typst-based reports",
|
||||
)
|
||||
|
||||
def _compile() -> bytes:
|
||||
return typst_compiler.compile(content.encode("utf-8"))
|
||||
|
||||
pdf_bytes = await asyncio.to_thread(_compile)
|
||||
|
||||
safe_title = re.sub(r"[^\w\s-]", "", report_info.get("title") or "Resume").strip()
|
||||
filename = f"{safe_title}.pdf"
|
||||
|
||||
return StreamingResponse(
|
||||
io.BytesIO(pdf_bytes),
|
||||
media_type="application/pdf",
|
||||
headers={
|
||||
"Content-Disposition": f'inline; filename="{filename}"',
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{share_token}/reports/{report_id}/content")
|
||||
async def get_public_report_content(
|
||||
share_token: str,
|
||||
|
|
@ -259,6 +310,7 @@ async def get_public_report_content(
|
|||
"id": report_info.get("original_id"),
|
||||
"title": report_info.get("title"),
|
||||
"content": report_info.get("content"),
|
||||
"content_type": report_info.get("content_type", "markdown"),
|
||||
"report_metadata": report_info.get("report_metadata"),
|
||||
"report_group_id": report_info.get("report_group_id"),
|
||||
"versions": versions,
|
||||
|
|
|
|||
|
|
@ -279,6 +279,7 @@ async def read_report_content(
|
|||
id=report.id,
|
||||
title=report.title,
|
||||
content=report.content,
|
||||
content_type=report.content_type,
|
||||
report_metadata=report.report_metadata,
|
||||
report_group_id=report.report_group_id,
|
||||
versions=versions,
|
||||
|
|
@ -319,6 +320,7 @@ async def update_report_content(
|
|||
id=report.id,
|
||||
title=report.title,
|
||||
content=report.content,
|
||||
content_type=report.content_type,
|
||||
report_metadata=report.report_metadata,
|
||||
report_group_id=report.report_group_id,
|
||||
versions=versions,
|
||||
|
|
@ -333,6 +335,57 @@ async def update_report_content(
|
|||
) from None
|
||||
|
||||
|
||||
@router.get("/reports/{report_id}/preview")
|
||||
async def preview_report_pdf(
|
||||
report_id: int,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
user: User = Depends(current_active_user),
|
||||
):
|
||||
"""
|
||||
Return a compiled PDF preview for Typst-based reports (resumes).
|
||||
|
||||
Reads the Typst source from the database and compiles it to PDF bytes
|
||||
on-the-fly. Only works for reports with content_type='typst'.
|
||||
"""
|
||||
try:
|
||||
report = await _get_report_with_access(report_id, session, user)
|
||||
|
||||
if not report.content:
|
||||
raise HTTPException(
|
||||
status_code=400, detail="Report has no content to preview"
|
||||
)
|
||||
|
||||
if report.content_type != "typst":
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Preview is only available for Typst-based reports",
|
||||
)
|
||||
|
||||
def _compile() -> bytes:
|
||||
return typst.compile(report.content.encode("utf-8"))
|
||||
|
||||
pdf_bytes = await asyncio.to_thread(_compile)
|
||||
|
||||
safe_title = re.sub(r"[^\w\s-]", "", report.title or "Resume").strip()
|
||||
filename = f"{safe_title}.pdf"
|
||||
|
||||
return StreamingResponse(
|
||||
io.BytesIO(pdf_bytes),
|
||||
media_type="application/pdf",
|
||||
headers={
|
||||
"Content-Disposition": f'inline; filename="{filename}"',
|
||||
},
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception:
|
||||
logger.exception("Failed to compile Typst preview for report %d", report_id)
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail="Failed to compile resume preview",
|
||||
) from None
|
||||
|
||||
|
||||
@router.get("/reports/{report_id}/export")
|
||||
async def export_report(
|
||||
report_id: int,
|
||||
|
|
@ -354,6 +407,27 @@ async def export_report(
|
|||
status_code=400, detail="Report has no content to export"
|
||||
)
|
||||
|
||||
# Typst-based reports (resumes): compile directly without Pandoc
|
||||
if report.content_type == "typst":
|
||||
if format != ExportFormat.PDF:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Typst-based reports currently only support PDF export",
|
||||
)
|
||||
|
||||
def _compile_typst() -> bytes:
|
||||
return typst.compile(report.content.encode("utf-8"))
|
||||
|
||||
pdf_bytes = await asyncio.to_thread(_compile_typst)
|
||||
safe_title = re.sub(r"[^\w\s-]", "", report.title or "Resume").strip()
|
||||
return StreamingResponse(
|
||||
io.BytesIO(pdf_bytes),
|
||||
media_type="application/pdf",
|
||||
headers={
|
||||
"Content-Disposition": f'attachment; filename="{safe_title}.pdf"',
|
||||
},
|
||||
)
|
||||
|
||||
# Strip wrapping code fences that LLMs sometimes add around Markdown.
|
||||
# Without this, pandoc treats the entire content as a code block.
|
||||
markdown_content = _strip_wrapping_code_fences(report.content)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue