support mark inner api
parent
f36c378390
commit
71bc05235c
@ -0,0 +1,7 @@
|
||||
from flask import Blueprint
|
||||
from libs.external_api import ExternalApi
|
||||
|
||||
bp = Blueprint("inner_tools", __name__, url_prefix="/inner_tools")
|
||||
api = ExternalApi(bp)
|
||||
|
||||
from . import markdown_to_pdf
|
||||
@ -0,0 +1,151 @@
|
||||
import io
|
||||
import urllib.parse
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import markdown
|
||||
from controllers.inner_tools import api
|
||||
from core.tools.tool_file_manager import ToolFileManager
|
||||
from flask import Response, jsonify, request
|
||||
from flask_restful import Resource # type: ignore
|
||||
from models.account import Tenant
|
||||
from reportlab.lib.pagesizes import letter
|
||||
from reportlab.pdfgen import canvas
|
||||
|
||||
|
||||
class MarkdownToPDFApi(Resource):
|
||||
def post(self):
|
||||
"""Convert markdown to PDF
|
||||
|
||||
This endpoint takes markdown text and converts it to a PDF document.
|
||||
Returns URL of uploaded PDF file.
|
||||
No authentication required.
|
||||
"""
|
||||
# Parse request arguments
|
||||
if not request.is_json:
|
||||
return {"error": "Request must be JSON"}, 400
|
||||
|
||||
data = request.get_json()
|
||||
markdown_text = data.get('markdown_text')
|
||||
title = data.get('title', 'Document')
|
||||
|
||||
if not markdown_text:
|
||||
return {"error": "markdown_text is required"}, 400
|
||||
|
||||
# get first tenant from database
|
||||
tenant = Tenant.query.first()
|
||||
if not tenant:
|
||||
return {"error": "no tenant found"}, 400
|
||||
|
||||
tenant_id = tenant.id
|
||||
|
||||
# Generate PDF
|
||||
pdf_binary, filename = self._generate_pdf_binary(markdown_text, title)
|
||||
|
||||
# Save the file using ToolFileManager
|
||||
tool_file = ToolFileManager.create_file_by_raw(
|
||||
user_id=None,
|
||||
tenant_id=tenant_id,
|
||||
conversation_id=None,
|
||||
file_binary=pdf_binary,
|
||||
mimetype='application/pdf',
|
||||
)
|
||||
|
||||
# Return the file info with URL
|
||||
file_url = ToolFileManager.sign_file(tool_file.id, '.pdf')
|
||||
return jsonify({'url': file_url, 'file_id': tool_file.id, 'file_name': filename, 'file_size': tool_file.size})
|
||||
|
||||
def _generate_pdf_binary(self, markdown_text: str, title: str) -> Tuple[bytes, str]:
|
||||
"""Generate PDF from markdown text and return the binary data and filename"""
|
||||
# Convert markdown to HTML (for potential future enhancement)
|
||||
html_content = markdown.markdown(markdown_text, extensions=['extra'])
|
||||
|
||||
# Create PDF using reportlab
|
||||
buffer = io.BytesIO()
|
||||
pdf = canvas.Canvas(buffer, pagesize=letter)
|
||||
|
||||
# Add title
|
||||
pdf.setFont("Helvetica-Bold", 16)
|
||||
pdf.drawString(72, 760, title)
|
||||
|
||||
# Add markdown content
|
||||
pdf.setFont("Helvetica", 12)
|
||||
y_position = 730
|
||||
|
||||
# Simple text rendering - each line as a separate line in the PDF
|
||||
for line in markdown_text.split('\n'):
|
||||
if line.startswith('# '): # H1 heading
|
||||
pdf.setFont("Helvetica-Bold", 14)
|
||||
pdf.drawString(72, y_position, line[2:])
|
||||
y_position -= 20
|
||||
pdf.setFont("Helvetica", 12)
|
||||
elif line.startswith('## '): # H2 heading
|
||||
pdf.setFont("Helvetica-Bold", 13)
|
||||
pdf.drawString(72, y_position, line[3:])
|
||||
y_position -= 18
|
||||
pdf.setFont("Helvetica", 12)
|
||||
elif line.startswith('### '): # H3 heading
|
||||
pdf.setFont("Helvetica-Bold", 12)
|
||||
pdf.drawString(72, y_position, line[4:])
|
||||
y_position -= 16
|
||||
pdf.setFont("Helvetica", 12)
|
||||
elif line.strip() == '': # Empty line
|
||||
y_position -= 12
|
||||
else: # Regular text
|
||||
pdf.drawString(72, y_position, line)
|
||||
y_position -= 14
|
||||
|
||||
# Check if we need a new page
|
||||
if y_position < 72:
|
||||
pdf.showPage()
|
||||
y_position = 760
|
||||
pdf.setFont("Helvetica", 12)
|
||||
|
||||
# Save the PDF
|
||||
pdf.save()
|
||||
buffer.seek(0)
|
||||
|
||||
# Generate a filename based on the title
|
||||
filename = f"{title.replace(' ', '_')}_{uuid.uuid4().hex[:8]}.pdf"
|
||||
|
||||
return buffer.getvalue(), filename
|
||||
|
||||
|
||||
# Add API endpoint for getting PDF from stored file
|
||||
class MarkdownToPDFFileApi(Resource):
|
||||
def get(self, file_id):
|
||||
"""Get a PDF file by its tool file ID
|
||||
|
||||
This endpoint retrieves a PDF file stored in the system using its tool file ID.
|
||||
No authentication required.
|
||||
"""
|
||||
try:
|
||||
# Get the file binary from ToolFileManager
|
||||
result = ToolFileManager.get_file_binary(file_id)
|
||||
|
||||
if result is None:
|
||||
return {"error": "File not found"}, 404
|
||||
|
||||
# Safely unpack result only after confirming it's not None
|
||||
file_binary, mimetype = result
|
||||
|
||||
if mimetype != 'application/pdf':
|
||||
return {"error": "File is not a PDF"}, 400
|
||||
|
||||
# Return the PDF
|
||||
response = Response(
|
||||
file_binary,
|
||||
mimetype='application/pdf',
|
||||
headers={
|
||||
'Content-Disposition': f'attachment; filename="{file_id}.pdf"',
|
||||
'Content-Type': 'application/pdf',
|
||||
},
|
||||
)
|
||||
return response
|
||||
except Exception as e:
|
||||
return {"error": str(e)}, 500
|
||||
|
||||
|
||||
api.add_resource(MarkdownToPDFApi, '/markdown-to-pdf')
|
||||
api.add_resource(MarkdownToPDFFileApi, '/markdown-to-pdf/<string:file_id>')
|
||||
@ -0,0 +1,37 @@
|
||||
"""make tool file user id optional
|
||||
|
||||
Revision ID: e927d80409a1
|
||||
Revises: a91b476a53de
|
||||
Create Date: 2025-04-05 21:00:53.060052
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import models as models
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'e927d80409a1'
|
||||
down_revision = 'a91b476a53de'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('tool_files', schema=None) as batch_op:
|
||||
batch_op.alter_column('user_id',
|
||||
existing_type=sa.UUID(),
|
||||
nullable=True)
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('tool_files', schema=None) as batch_op:
|
||||
batch_op.alter_column('user_id',
|
||||
existing_type=sa.UUID(),
|
||||
nullable=False)
|
||||
|
||||
# ### end Alembic commands ###
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue