Feature 1 — PPTX from Template (Code-Gen): - backend/services/template_codegen_service.py: analyze PPTX, strip slides, Gemini code-gen + subprocess exec (60s timeout, auto-retry on error) - backend/api/v1/ppt/endpoints/template_codegen.py: POST /template-codegen/generate (multipart: presentation_id + template_file + custom_prompt, rate-limited 3/min) - frontend/components/TemplateCodegenExport.tsx: drag-drop modal - Header.tsx: "Export from Template" option in export dropdown Feature 2 — Diagrams in Slides: - backend/models/diagram_data.py: DiagramData / FlowStep / BarChartItem models - generate_slide_content.py: optional __diagram__ + __mermaid__ fields in LLM schema - DiagramRenderer.tsx: pure React flowchart / bar chart / pie chart (no deps) - SlideRenderer.tsx: chart elements render DiagramRenderer/MermaidRenderer; floating overlay fallback when no chart element exists in JSON layout - V1ContentRender.tsx: diagram/mermaid overlay on built-in template slides - generate-pptx/route.ts: addDiagramToSlide() — bar/pie via pptxgenjs addChart(), flowchart via addShape()+addText(), mermaid via /api/mermaid-to-image Feature 3 — Mermaid Diagrams: - MermaidRenderer.tsx: dynamic import mermaid@11, useEffect render, error fallback - frontend/app/api/mermaid-to-image/route.ts: Puppeteer renders Mermaid to PNG → base64 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
70 lines
2.4 KiB
Python
70 lines
2.4 KiB
Python
import os
|
|
import tempfile
|
|
import uuid
|
|
from typing import Optional
|
|
|
|
from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile
|
|
from fastapi.responses import FileResponse
|
|
|
|
from api.middlewares.rate_limit_middleware import limiter
|
|
from models.sql.user import UserModel
|
|
from services.template_codegen_service import generate_pptx_from_template
|
|
from utils.auth_dependencies import get_current_user
|
|
from fastapi import Request
|
|
|
|
TEMPLATE_CODEGEN_ROUTER = APIRouter(prefix="/template-codegen", tags=["Template CodeGen"])
|
|
|
|
|
|
@TEMPLATE_CODEGEN_ROUTER.post("/generate")
|
|
@limiter.limit("3/minute")
|
|
async def generate_from_template(
|
|
request: Request,
|
|
presentation_id: str = Form(...),
|
|
template_file: UploadFile = File(...),
|
|
custom_prompt: Optional[str] = Form(None),
|
|
_current_user: UserModel = Depends(get_current_user),
|
|
):
|
|
"""
|
|
Generate a populated PPTX from a branded template file and existing presentation content.
|
|
Accepts: multipart/form-data with presentation_id, template_file (.pptx), custom_prompt (optional)
|
|
Returns: PPTX file download
|
|
"""
|
|
# Validate file type
|
|
filename = template_file.filename or ""
|
|
if not filename.lower().endswith(".pptx"):
|
|
raise HTTPException(status_code=400, detail="Template file must be a .pptx file")
|
|
|
|
# Save uploaded template to temp
|
|
tmp_dir = os.environ.get("TEMP_DIRECTORY", tempfile.gettempdir())
|
|
template_path = os.path.join(tmp_dir, f"template_{uuid.uuid4().hex}.pptx")
|
|
output_path = os.path.join(tmp_dir, f"output_{uuid.uuid4().hex}.pptx")
|
|
|
|
try:
|
|
content = await template_file.read()
|
|
with open(template_path, "wb") as f:
|
|
f.write(content)
|
|
|
|
result = await generate_pptx_from_template(
|
|
template_path=template_path,
|
|
presentation_id=presentation_id,
|
|
custom_prompt=custom_prompt or "",
|
|
output_path=output_path,
|
|
)
|
|
|
|
return FileResponse(
|
|
path=result["output_path"],
|
|
media_type="application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
filename="presentation-from-template.pptx",
|
|
background=None,
|
|
)
|
|
|
|
except ValueError as e:
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
except RuntimeError as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
finally:
|
|
# Clean up template file
|
|
try:
|
|
os.unlink(template_path)
|
|
except Exception:
|
|
pass
|