From 19e96ef59b095fc7d446618f9f30f06140af3f23 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Jan 2026 18:29:22 +0000 Subject: [PATCH] feat: Add LaTeX template-based form filling for G2210-11 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace unreliable visual overlay mode with precise LaTeX templates: - Add LaTeX template for G2210-11 (Ärztlicher Befundbericht der WAG) - Create Python Flask backend for LaTeX compilation (latex_service.py, server.py) - Add frontend latexService.ts for API communication - Update ReviewPanel with LaTeX mode toggle and preview - Enhance Gemini prompts with G2210-11 specific field extraction - Add Dockerfile with TeX Live for Railway deployment - Update railway.toml to use Docker builder The LaTeX approach ensures accurate field placement and proper formatting for German medical/insurance forms. https://claude.ai/code/session_016pQhdznHZ74Fpkvwr3cLBq --- Dockerfile | 67 +++++++ components/ReviewPanel.tsx | 194 +++++++++++++++++--- latex_service.py | 249 ++++++++++++++++++++++++++ package.json | 4 +- railway.toml | 6 +- requirements.txt | 2 + server.py | 337 +++++++++++++++++++++++++++++++++++ services/geminiService.ts | 126 ++++++++++++- services/latexService.ts | 251 ++++++++++++++++++++++++++ templates/G2210-11.tex | 353 +++++++++++++++++++++++++++++++++++++ 10 files changed, 1557 insertions(+), 32 deletions(-) create mode 100644 Dockerfile create mode 100644 latex_service.py create mode 100644 server.py create mode 100644 services/latexService.ts create mode 100644 templates/G2210-11.tex diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f901e01 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,67 @@ +# Multi-stage Dockerfile for AutoForm AI with LaTeX support + +# Stage 1: Build Frontend +FROM node:20-alpine AS frontend-builder + +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install dependencies +RUN npm ci + +# Copy source files +COPY . . + +# Build the frontend +RUN npm run build + +# Stage 2: Production image with Python and LaTeX +FROM python:3.11-slim + +# Install TeX Live (minimal installation for form generation) +RUN apt-get update && apt-get install -y --no-install-recommends \ + texlive-latex-base \ + texlive-latex-recommended \ + texlive-latex-extra \ + texlive-fonts-recommended \ + texlive-lang-german \ + texlive-plain-generic \ + nodejs \ + npm \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# Copy Python requirements and install +COPY requirements.txt ./ +RUN pip install --no-cache-dir -r requirements.txt + +# Install serve for static file serving +RUN npm install -g serve + +# Copy the built frontend +COPY --from=frontend-builder /app/dist ./dist + +# Copy Python backend and LaTeX templates +COPY latex_service.py ./ +COPY server.py ./ +COPY templates ./templates + +# Create startup script +RUN echo '#!/bin/bash\n\ +python server.py &\n\ +serve dist -l ${PORT:-3000}\n\ +' > /app/start.sh && chmod +x /app/start.sh + +# Expose ports +EXPOSE 3000 5000 + +# Environment variables +ENV PORT=3000 +ENV FLASK_DEBUG=false +ENV VITE_LATEX_API_URL=http://localhost:5000 + +# Start both services +CMD ["/app/start.sh"] diff --git a/components/ReviewPanel.tsx b/components/ReviewPanel.tsx index 56b280f..60aae8d 100644 --- a/components/ReviewPanel.tsx +++ b/components/ReviewPanel.tsx @@ -1,7 +1,8 @@ -import React, { useState, useEffect, useMemo } from 'react'; +import React, { useState, useEffect, useMemo, useCallback } from 'react'; import { ExtractedField, FileData } from '../types'; -import { Check, Edit2, Download, RefreshCw, FileText, AlertTriangle, XCircle, ArrowRight, PenTool, CheckCircle2, Circle } from 'lucide-react'; +import { Check, Edit2, Download, RefreshCw, FileText, AlertTriangle, XCircle, ArrowRight, PenTool, CheckCircle2, Circle, FileCode, Loader2 } from 'lucide-react'; import { createFilledPdf } from '../services/pdfService'; +import { generateLatexPdf, isLatexServiceAvailable, detectTemplate, base64ToBlob } from '../services/latexService'; import { jsPDF } from "jspdf"; interface ReviewPanelProps { @@ -13,18 +14,40 @@ interface ReviewPanelProps { onReset: () => void; } -export const ReviewPanel: React.FC = ({ - fields: initialFields, - formFile, +type PdfMode = 'overlay' | 'fillable' | 'latex'; + +export const ReviewPanel: React.FC = ({ + fields: initialFields, + formFile, sourceFile, summary, isFillablePdf, - onReset + onReset }) => { const [fields, setFields] = useState(initialFields); const [activeField, setActiveField] = useState(null); const [previewUrl, setPreviewUrl] = useState(null); const [filterMode, setFilterMode] = useState<'ALL' | 'ATTENTION'>('ALL'); + const [latexAvailable, setLatexAvailable] = useState(null); + const [pdfMode, setPdfMode] = useState(isFillablePdf ? 'fillable' : 'overlay'); + const [isGenerating, setIsGenerating] = useState(false); + const [latexPdfBase64, setLatexPdfBase64] = useState(null); + + // Detect template from form file name + const detectedTemplate = useMemo(() => detectTemplate(formFile.file.name), [formFile.file.name]); + + // Check LaTeX service availability on mount + useEffect(() => { + const checkLatex = async () => { + const available = await isLatexServiceAvailable(); + setLatexAvailable(available); + // Auto-switch to LaTeX mode if available and template detected + if (available && detectedTemplate && !isFillablePdf) { + setPdfMode('latex'); + } + }; + checkLatex(); + }, [detectedTemplate, isFillablePdf]); // Derived state for progress const verifiedCount = fields.filter(f => f.isVerified).length; @@ -35,12 +58,46 @@ export const ReviewPanel: React.FC = ({ fields.filter(f => f.validation?.status !== 'VALID'), [fields]); + // Generate LaTeX PDF + const generateLatexPreview = useCallback(async () => { + if (!detectedTemplate || pdfMode !== 'latex') return; + + setIsGenerating(true); + try { + const result = await generateLatexPdf(detectedTemplate, fields); + if (result.success && result.pdf) { + setLatexPdfBase64(result.pdf); + const blob = base64ToBlob(result.pdf); + const url = URL.createObjectURL(blob); + setPreviewUrl(url); + } else { + console.error('LaTeX generation failed:', result.error); + // Fallback to overlay mode + setPdfMode('overlay'); + } + } catch (e) { + console.error('LaTeX generation error:', e); + setPdfMode('overlay'); + } finally { + setIsGenerating(false); + } + }, [detectedTemplate, fields, pdfMode]); + // Generate preview useEffect(() => { const updatePreview = async () => { + // LaTeX mode - handled separately with button click + if (pdfMode === 'latex') { + // Only auto-generate if we don't have a preview yet + if (!latexPdfBase64) { + generateLatexPreview(); + } + return; + } + if (formFile.type === 'application/pdf') { try { - const filledPdfBytes = await createFilledPdf(formFile.base64, fields, isFillablePdf); + const filledPdfBytes = await createFilledPdf(formFile.base64, fields, pdfMode === 'fillable'); const blob = new Blob([filledPdfBytes], { type: 'application/pdf' }); const url = URL.createObjectURL(blob); setPreviewUrl(url); @@ -52,11 +109,11 @@ export const ReviewPanel: React.FC = ({ setPreviewUrl(formFile.previewUrl); } }; - + // Debounce slightly to avoid rapid updates on typing const timer = setTimeout(updatePreview, 600); return () => clearTimeout(timer); - }, [fields, isFillablePdf, formFile.base64, formFile.type]); + }, [fields, pdfMode, formFile.base64, formFile.type, latexPdfBase64, generateLatexPreview]); const handleUpdate = (index: number, newValue: string) => { const newFields = [...fields]; @@ -87,6 +144,30 @@ export const ReviewPanel: React.FC = ({ }; const handleDownload = async () => { + // LaTeX mode - regenerate fresh PDF for download + if (pdfMode === 'latex' && detectedTemplate) { + setIsGenerating(true); + try { + const result = await generateLatexPdf(detectedTemplate, fields); + if (result.success && result.pdf) { + const blob = base64ToBlob(result.pdf); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `${detectedTemplate}_filled.pdf`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + } else { + alert(`PDF generation failed: ${result.error}`); + } + } finally { + setIsGenerating(false); + } + return; + } + if (formFile.type === 'application/pdf' && previewUrl) { const a = document.createElement('a'); a.href = previewUrl; @@ -106,6 +187,12 @@ export const ReviewPanel: React.FC = ({ } }; + // Regenerate LaTeX preview + const handleRegenerateLatex = () => { + setLatexPdfBase64(null); + generateLatexPreview(); + }; + // Sort: Unverified/Issues first, then verified const displayedFields = fields.map((f, i) => ({ ...f, originalIndex: i })) .sort((a, b) => { @@ -154,11 +241,16 @@ export const ReviewPanel: React.FC = ({ Start Over - @@ -170,29 +262,73 @@ export const ReviewPanel: React.FC = ({
Preview - {!isFillablePdf && ( + {pdfMode === 'latex' && ( + + + LaTeX Template Mode + + )} + {pdfMode === 'overlay' && ( Visual Overlay Mode )} + {pdfMode === 'fillable' && ( + + + Fillable PDF Mode + + )} +
+
+ {/* Mode Switcher */} + {latexAvailable && detectedTemplate && ( + + )} + {pdfMode === 'latex' && ( + + )} + {formFile.file.name}
- {formFile.file.name}
- {previewUrl ? ( - formFile.type === 'application/pdf' ? ( -