@ -5,6 +5,7 @@ import logging
import mimetypes
import os
import re
import shutil
import subprocess
import time
import uuid
@ -36,6 +37,10 @@ def _normalize_base_path(value: str) -> str:
BASE_PATH = _normalize_base_path ( os . getenv ( " BASE_PATH " , " " ) )
CONVERT_DAILY_LIMIT = int ( os . getenv ( " CONVERT_DAILY_LIMIT " , " 5 " ) )
EDIT_DAILY_LIMIT = int ( os . getenv ( " EDIT_DAILY_LIMIT " , " 10 " ) )
PDFLATEX_TIMEOUT_SECONDS = int ( os . getenv ( " PDFLATEX_TIMEOUT_SECONDS " , " 10 " ) )
def _parse_model_list ( env_value : str | None , default : list [ str ] ) - > list [ str ] :
if not env_value :
@ -53,6 +58,8 @@ DEFAULT_MODELS = _parse_model_list(
" gemini/gemini-flash-latest " ,
] ,
)
EDIT_MODELS = _parse_model_list (
os . getenv ( " EDIT_MODELS " ) ,
[
@ -61,9 +68,6 @@ EDIT_MODELS = _parse_model_list(
] ,
)
CONVERT_DAILY_LIMIT = 5
EDIT_DAILY_LIMIT = 10
class RateLimitEntry ( TypedDict , total = False ) :
day : str
@ -417,20 +421,29 @@ def _run_cmd(
def _compile_pdflatex (
run_dir : Path , tex_filename : str , run_logger : RunLogger , section_title : str
) - > Optional [ Path ] :
cmd = [
" pdflatex " ,
" -interaction=nonstopmode " ,
" -halt-on-error " ,
" -file-line-error " ,
" -output-directory " ,
str ( run_dir ) ,
str ( run_dir / tex_filename ) ,
]
code , out = _run_cmd ( cmd , cwd = run_dir , timeout_s = 180 , py_logger = run_logger . py )
run_logger . section ( section_title , out )
code , out = _run_cmd (
[
" pdflatex " ,
" -interaction=nonstopmode " ,
" -halt-on-error " ,
" -file-line-error " ,
" -output-directory " ,
str ( run_dir ) ,
str ( run_dir / tex_filename ) ,
] ,
cwd = run_dir ,
timeout_s = PDFLATEX_TIMEOUT_SECONDS ,
py_logger = run_logger . py ,
)
if code == 0 :
run_logger . section ( section_title , " Compilation succeeded. " )
else :
run_logger . section ( section_title , out )
pdf_path = run_dir / Path ( tex_filename ) . with_suffix ( " .pdf " ) . name
if code == 0 and pdf_path . exists ( ) :
return pdf_path
return None
@ -453,7 +466,7 @@ def _render_png_with_magick(
" off " ,
str ( png_path ) ,
]
code , out = _run_cmd ( cmd , cwd = run_dir , timeout_s = 180 , py_logger = run_logger . py )
code , out = _run_cmd ( cmd , cwd = run_dir , timeout_s = 5 , py_logger = run_logger . py )
run_logger . section ( section_title , out )
if code == 0 and png_path . exists ( ) :
return png_path
@ -505,7 +518,7 @@ def _render_svg_with_pdf2svg(
) - > Optional [ Path ] :
svg_path = run_dir / svg_name
cmd = [ " pdf2svg " , str ( pdf_path ) , str ( svg_path ) , " 1 " ]
code , out = _run_cmd ( cmd , cwd = run_dir , timeout_s = 180 , py_logger = run_logger . py )
code , out = _run_cmd ( cmd , cwd = run_dir , timeout_s = 5 , py_logger = run_logger . py )
run_logger . section ( section_title , out )
if code == 0 and svg_path . exists ( ) :
return svg_path
@ -1188,6 +1201,26 @@ async def get_history(request: Request, run_id: str):
return JSONResponse ( { " entries " : safe_entries } )
@app.post ( " /d/ {run_id} /delete " , response_class = HTMLResponse )
async def delete_run ( request : Request , run_id : str ) :
run_dir = _run_dir_for_id ( run_id )
if run_dir is None :
return RedirectResponse ( url = f " { BASE_PATH } /?error=Invalid%20run%20id " , status_code = 303 )
try :
resolved_runs_dir = RUNS_DIR . resolve ( )
resolved_run_dir = run_dir . resolve ( )
if resolved_runs_dir not in resolved_run_dir . parents :
logger . warning ( " delete.invalid_path run_id= %s path= %s " , run_id , str ( run_dir ) )
return HTMLResponse ( " Invalid run directory " , status_code = 400 )
shutil . rmtree ( resolved_run_dir )
logger . info ( " delete.ok run_id= %s " , run_id )
except Exception :
logger . exception ( " delete.error run_id= %s " , run_id )
return RedirectResponse ( url = f " { BASE_PATH } /d/ { run_id } ?error=Delete%20failed " , status_code = 303 )
return RedirectResponse ( url = f " { BASE_PATH } / " , status_code = 303 )
@app.get ( " /runs/ {run_id} / {filename} " )
async def get_run_file ( run_id : str , filename : str ) :
path = _safe_join_run_file ( run_id , filename )
@ -1195,7 +1228,14 @@ async def get_run_file(run_id: str, filename: str):
logger . info ( " download.not_found run_id= %s filename= %s " , run_id , filename )
return HTMLResponse ( " Not found " , status_code = 404 )
logger . info ( " download.ok run_id= %s filename= %s " , run_id , filename )
return FileResponse ( path )
return FileResponse (
path ,
headers = {
" Cache-Control " : " no-store, no-cache, must-revalidate, max-age=0 " ,
" Pragma " : " no-cache " ,
" Expires " : " 0 " ,
} ,
)
if __name__ == " __main__ " :