Files
Stirling-PDF/engine/src/editing/routes.py
T

105 lines
3.7 KiB
Python

import logging
import os
import subprocess
import uuid
from flask import Blueprint, jsonify, request
from werkzeug.security import safe_join
from config import OUTPUT_DIR
from file_processing_agent import ToolCatalogService
from llm_utils import AIProviderOverloadedError
from models import EditMessageRequest, PdfEditorUploadResponse
from pdf_text_editor import convert_pdf_to_text_editor_document
from .service import EditService
from .session_store import EditSessionStore
logger = logging.getLogger(__name__)
edit_blueprint = Blueprint("edit", __name__)
_edit_service = EditService(EditSessionStore(), ToolCatalogService())
def register_edit_routes(app) -> None:
app.register_blueprint(edit_blueprint)
def _json_body(model):
return model.model_validate(request.get_json(silent=True) or {})
@edit_blueprint.route("/api/edit/sessions", methods=["POST"])
def create_edit_session():
files = request.files.getlist("file")
return _edit_service.create_session(files)
@edit_blueprint.route("/api/edit/sessions/<session_id>/messages", methods=["POST"])
def edit_session_message(session_id: str):
payload = _json_body(EditMessageRequest)
try:
return _edit_service.handle_message(session_id, payload)
except AIProviderOverloadedError as exc:
logger.warning("[EDIT] AI provider overloaded session_id=%s (exc=%s)", session_id, exc)
response = {
"assistantMessage": "The AI service is temporarily unavailable. Please try again later.",
"needsMoreInfo": True,
}
return jsonify(response), 503
@edit_blueprint.route("/api/edit/sessions/<session_id>/attachments", methods=["POST"])
def edit_session_attachment(session_id: str):
name = request.form.get("name")
file = request.files.get("file")
return _edit_service.add_attachment(session_id, name, file)
@edit_blueprint.route("/api/pdf-editor/document", methods=["GET"])
def pdf_editor_document():
"""Expose a JSON snapshot of the PDF for rich text editing."""
pdf_url = request.args.get("pdfUrl")
if not pdf_url:
return jsonify({"error": "Missing pdfUrl"}), 400
filename = os.path.basename(pdf_url.split("?")[0])
if not filename:
return jsonify({"error": "Invalid pdf file"}), 400
if not filename.lower().endswith(".pdf"):
return jsonify({"error": "Invalid pdf file"}), 400
pdf_path = safe_join(OUTPUT_DIR, filename)
if pdf_path is None or not os.path.exists(pdf_path):
return jsonify({"error": "PDF not found"}), 404
try:
document = convert_pdf_to_text_editor_document(pdf_path)
return jsonify(document.model_dump(by_alias=True, exclude_none=True))
except FileNotFoundError:
return jsonify({"error": "Conversion failed"}), 500
except subprocess.CalledProcessError as exc:
logger.error("[PDF-EDITOR] Conversion failed: %s", exc)
return jsonify({"error": "Conversion failed"}), 500
except Exception as exc:
logger.error("[PDF-EDITOR] Unexpected conversion failure: %s", exc)
return jsonify({"error": "Conversion failed"}), 500
@edit_blueprint.route("/api/pdf-editor/upload", methods=["POST"])
def pdf_editor_upload():
"""Accept an edited PDF and save it so the preview can refresh."""
file = request.files.get("file")
if not file:
return jsonify({"error": "Missing file"}), 400
job_id = str(uuid.uuid4())
filename = f"{job_id}-edited.pdf"
output_path = os.path.join(OUTPUT_DIR, filename)
os.makedirs(OUTPUT_DIR, exist_ok=True)
file.save(output_path)
logger.info("[PDF-EDITOR] uploaded edited PDF job_id=%s -> %s", job_id, filename)
response = PdfEditorUploadResponse(pdf_url=f"/output/{filename}")
return jsonify(response.model_dump(by_alias=True, exclude_none=True))