Samuel commited on
Commit
18e1c52
·
1 Parent(s): 5e088bb
Files changed (4) hide show
  1. Dockerfile +28 -0
  2. README.md +387 -4
  3. app.py +201 -0
  4. index.html +422 -0
Dockerfile ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM ubuntu:22.04
2
+
3
+ # Prevent interactive prompts during installation
4
+ ENV DEBIAN_FRONTEND=noninteractive
5
+
6
+ # Install system dependencies
7
+ RUN apt-get update && apt-get install -y \
8
+ python3 \
9
+ python3-pip \
10
+ texlive-full \
11
+ latexmk \
12
+ && apt-get clean \
13
+ && rm -rf /var/lib/apt/lists/*
14
+
15
+ # Install Python dependencies
16
+ RUN pip3 install --no-cache-dir flask flask-cors
17
+
18
+ # Set working directory
19
+ WORKDIR /app
20
+
21
+ # Copy application files
22
+ COPY . .
23
+
24
+ # Expose port
25
+ EXPOSE 7860
26
+
27
+ # Run the application
28
+ CMD ["python3", "app.py"]
README.md CHANGED
@@ -1,10 +1,393 @@
1
  ---
2
- title: Texcomp
3
- emoji: 💻
4
- colorFrom: indigo
5
  colorTo: indigo
6
  sdk: docker
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: LaTeX Compiler
3
+ emoji: 📄
4
+ colorFrom: purple
5
  colorTo: indigo
6
  sdk: docker
7
  pinned: false
8
+ license: mit
9
+ app_port: 7860
10
  ---
11
 
12
+ # LaTeX Compiler Web App
13
+
14
+ A full-featured LaTeX compiler web application that converts LaTeX documents to PDF using pdflatex. Built with Flask backend and vanilla JavaScript frontend, deployable on Hugging Face Spaces using Docker.
15
+
16
+ ## Features
17
+
18
+ - 🚀 **Web Interface**: Upload `.tex` files or paste LaTeX code directly
19
+ - 📄 **PDF Generation**: Compile LaTeX to PDF using pdflatex
20
+ - 🔌 **REST API**: JSON and file upload endpoints
21
+ - 🎨 **Modern UI**: Responsive design with Tailwind CSS
22
+ - 🐳 **Docker Ready**: Easy deployment with Docker
23
+ - 🤗 **Hugging Face Compatible**: Ready for Hugging Face Spaces
24
+
25
+ ## Project Structure
26
+
27
+ ```
28
+ latex-compiler/
29
+ ├── app.py # Flask backend server
30
+ ├── index.html # Frontend interface
31
+ ├── Dockerfile # Docker configuration
32
+ └── README.md # This file (with HF config)
33
+ ```
34
+
35
+ ## Hugging Face Spaces Deployment
36
+
37
+ ### Quick Deploy
38
+
39
+ 1. **Create a new Space:**
40
+ - Go to [Hugging Face Spaces](https://huggingface.co/spaces)
41
+ - Click "Create new Space"
42
+ - Choose **Docker** as the SDK
43
+ - Name your space (e.g., `latex-compiler`)
44
+
45
+ 2. **Upload files:**
46
+ Upload all four files to your Space:
47
+ - `README.md` (this file - **must include YAML frontmatter**)
48
+ - `Dockerfile`
49
+ - `app.py`
50
+ - `index.html`
51
+
52
+ 3. **Configuration:**
53
+ The YAML frontmatter at the top of this README configures your Space:
54
+ ```yaml
55
+ ---
56
+ title: LaTeX Compiler # Your app name
57
+ emoji: 📄 # Icon for your Space
58
+ colorFrom: purple # Gradient start color
59
+ colorTo: indigo # Gradient end color
60
+ sdk: docker # REQUIRED: Use Docker SDK
61
+ pinned: false # Pin to your profile
62
+ license: mit # License type
63
+ app_port: 7860 # REQUIRED: Port your app runs on
64
+ ---
65
+ ```
66
+
67
+ 4. **Wait for build:**
68
+ - Hugging Face will automatically build your Docker image
69
+ - This takes **10-15 minutes** due to texlive-full installation
70
+ - Watch the build logs in the "Logs" tab
71
+ - Once complete, your app will be live!
72
+
73
+ 5. **Access your Space:**
74
+ - Your app will be available at: `https://huggingface.co/spaces/YOUR_USERNAME/YOUR_SPACE_NAME`
75
+
76
+ ### Important Notes for Hugging Face
77
+
78
+ - ✅ **Port 7860 is REQUIRED** - Hugging Face Spaces expects this port
79
+ - ✅ **Docker SDK is REQUIRED** - Must be specified in YAML frontmatter
80
+ - ✅ **README.md must have YAML frontmatter** - Configuration at the top of this file
81
+ - ⚠️ Build time is long (~10-15 min) due to texlive-full package size
82
+ - ⚠️ First compile may be slower as LaTeX initializes fonts
83
+
84
+ ## Local Development
85
+
86
+ ### Prerequisites
87
+
88
+ - Docker installed on your system
89
+ - OR Python 3.8+ and texlive-full for non-Docker setup
90
+
91
+ ### Run with Docker (Recommended)
92
+
93
+ 1. **Build the Docker image:**
94
+ ```bash
95
+ docker build -t latex-compiler .
96
+ ```
97
+
98
+ 2. **Run the container:**
99
+ ```bash
100
+ docker run -p 7860:7860 latex-compiler
101
+ ```
102
+
103
+ 3. **Access the application:**
104
+ Open your browser and navigate to `http://localhost:7860`
105
+
106
+ ### Run without Docker (Local Development)
107
+
108
+ 1. **Install dependencies:**
109
+ ```bash
110
+ # Ubuntu/Debian
111
+ sudo apt-get update
112
+ sudo apt-get install python3 python3-pip texlive-full latexmk
113
+
114
+ # macOS
115
+ brew install python texlive
116
+
117
+ # Install Python packages
118
+ pip3 install flask flask-cors
119
+ ```
120
+
121
+ 2. **Run the application:**
122
+ ```bash
123
+ python3 app.py
124
+ ```
125
+
126
+ 3. **Access the application:**
127
+ Open your browser and navigate to `http://localhost:7860`
128
+
129
+ ## API Documentation
130
+
131
+ ### Endpoints
132
+
133
+ #### `POST /compile`
134
+
135
+ Compile LaTeX code to PDF.
136
+
137
+ **Request Method 1: JSON**
138
+ ```bash
139
+ curl -X POST http://localhost:7860/compile \
140
+ -H "Content-Type: application/json" \
141
+ -d '{"code": "\\documentclass{article}\\begin{document}Hello World\\end{document}"}' \
142
+ --output output.pdf
143
+ ```
144
+
145
+ **Request Method 2: File Upload**
146
+ ```bash
147
+ curl -X POST http://localhost:7860/compile \
148
+ -F "file=@document.tex" \
149
+ --output output.pdf
150
+ ```
151
+
152
+ **Success Response:**
153
+ - Status: `200 OK`
154
+ - Content-Type: `application/pdf`
155
+ - Body: PDF binary data
156
+
157
+ **Error Response:**
158
+ - Status: `400 Bad Request`
159
+ - Content-Type: `application/json`
160
+ ```json
161
+ {
162
+ "error": "Compilation failed",
163
+ "log": "LaTeX error log details..."
164
+ }
165
+ ```
166
+
167
+ #### `GET /`
168
+
169
+ Serves the web frontend interface.
170
+
171
+ #### `GET /docs`
172
+
173
+ Displays comprehensive API documentation.
174
+
175
+ ## Usage Examples
176
+
177
+ ### Python Example
178
+
179
+ ```python
180
+ import requests
181
+
182
+ latex_code = r"""
183
+ \documentclass{article}
184
+ \usepackage[utf8]{inputenc}
185
+
186
+ \title{My Document}
187
+ \author{John Doe}
188
+
189
+ \begin{document}
190
+ \maketitle
191
+
192
+ \section{Introduction}
193
+ This is a sample document.
194
+
195
+ \end{document}
196
+ """
197
+
198
+ response = requests.post(
199
+ 'http://localhost:7860/compile',
200
+ json={'code': latex_code}
201
+ )
202
+
203
+ if response.status_code == 200:
204
+ with open('output.pdf', 'wb') as f:
205
+ f.write(response.content)
206
+ print("PDF created successfully!")
207
+ else:
208
+ print("Error:", response.json())
209
+ ```
210
+
211
+ ### JavaScript Example
212
+
213
+ ```javascript
214
+ const latexCode = `
215
+ \\documentclass{article}
216
+ \\begin{document}
217
+ Hello from JavaScript!
218
+ \\end{document}
219
+ `;
220
+
221
+ fetch('http://localhost:7860/compile', {
222
+ method: 'POST',
223
+ headers: {
224
+ 'Content-Type': 'application/json',
225
+ },
226
+ body: JSON.stringify({ code: latexCode })
227
+ })
228
+ .then(response => response.blob())
229
+ .then(blob => {
230
+ const url = URL.createObjectURL(blob);
231
+ const a = document.createElement('a');
232
+ a.href = url;
233
+ a.download = 'output.pdf';
234
+ a.click();
235
+ });
236
+ ```
237
+
238
+ ### File Upload Example
239
+
240
+ ```python
241
+ import requests
242
+
243
+ with open('document.tex', 'rb') as f:
244
+ files = {'file': f}
245
+ response = requests.post(
246
+ 'http://localhost:7860/compile',
247
+ files=files
248
+ )
249
+
250
+ if response.status_code == 200:
251
+ with open('output.pdf', 'wb') as f:
252
+ f.write(response.content)
253
+ ```
254
+
255
+ ### Using on Hugging Face Spaces
256
+
257
+ ```python
258
+ import requests
259
+
260
+ # Replace with your actual Space URL
261
+ SPACE_URL = "https://YOUR_USERNAME-latex-compiler.hf.space"
262
+
263
+ latex_code = r"""
264
+ \documentclass{article}
265
+ \begin{document}
266
+ Hello from Hugging Face!
267
+ \end{document}
268
+ """
269
+
270
+ response = requests.post(
271
+ f'{SPACE_URL}/compile',
272
+ json={'code': latex_code}
273
+ )
274
+
275
+ if response.status_code == 200:
276
+ with open('output.pdf', 'wb') as f:
277
+ f.write(response.content)
278
+ ```
279
+
280
+ ## Features & Limitations
281
+
282
+ ### Supported
283
+
284
+ ✅ Standard LaTeX packages (texlive-full includes 99% of packages)
285
+ ✅ Mathematical equations and symbols
286
+ ✅ Tables, figures, and images (embedded)
287
+ ✅ Bibliography and citations
288
+ ✅ Custom document classes
289
+ ✅ Multiple compilation passes (handled automatically)
290
+
291
+ ### Limitations
292
+
293
+ ⚠️ Compilation timeout: 30 seconds
294
+ ⚠️ Recommended file size: < 10MB
295
+ ⚠️ Only pdflatex engine (XeLaTeX and LuaLaTeX not included)
296
+ ⚠️ No support for external file dependencies (images must be embedded or base64)
297
+
298
+ ## Troubleshooting
299
+
300
+ ### Common Errors
301
+
302
+ **"Compilation failed"**
303
+ - Check your LaTeX syntax
304
+ - View the error log in the web interface
305
+ - Ensure all required packages are declared
306
+
307
+ **"Compilation timeout"**
308
+ - Your document is too complex
309
+ - Try simplifying or splitting into smaller documents
310
+
311
+ **"No file selected"**
312
+ - Ensure you've selected a `.tex` file before clicking compile
313
+
314
+ ### Docker Build Issues
315
+
316
+ **Build fails due to texlive-full size:**
317
+ ```bash
318
+ # Increase Docker memory limit in Docker Desktop settings
319
+ # Recommended: 4GB+ RAM, 10GB+ disk space
320
+ ```
321
+
322
+ **Port 7860 already in use:**
323
+ ```bash
324
+ # Use a different port locally
325
+ docker run -p 8080:7860 latex-compiler
326
+ # Then access at http://localhost:8080
327
+ ```
328
+
329
+ ### Hugging Face Spaces Issues
330
+
331
+ **Space stuck in "Building" state:**
332
+ - Check the build logs for errors
333
+ - Ensure Dockerfile is correct
334
+ - Verify all files are uploaded
335
+ - texlive-full installation takes 10-15 minutes (this is normal)
336
+
337
+ **Space shows "Application Error":**
338
+ - Check that `app_port: 7860` is in README.md frontmatter
339
+ - Verify Flask app runs on host `0.0.0.0` and port `7860`
340
+ - Review Space logs for Python errors
341
+
342
+ **"Connection refused" errors:**
343
+ - Ensure `EXPOSE 7860` is in Dockerfile
344
+ - Verify `app.run(host='0.0.0.0', port=7860)` in app.py
345
+
346
+ ## Technical Stack
347
+
348
+ - **Backend**: Flask 3.x (Python)
349
+ - **Frontend**: Vanilla JavaScript + Tailwind CSS
350
+ - **LaTeX Engine**: pdflatex (TeX Live 2022)
351
+ - **Containerization**: Docker (Ubuntu 22.04 base)
352
+ - **Deployment**: Hugging Face Spaces (Docker SDK)
353
+
354
+ ## Environment Variables (Optional)
355
+
356
+ You can customize the app with environment variables:
357
+
358
+ ```bash
359
+ # In Hugging Face Spaces, add these in Settings > Variables
360
+ FLASK_ENV=production
361
+ MAX_CONTENT_LENGTH=10485760 # 10MB limit
362
+ ```
363
+
364
+ ## License
365
+
366
+ MIT License - feel free to use and modify!
367
+
368
+ ## Contributing
369
+
370
+ Contributions welcome! Areas for improvement:
371
+ - Add XeLaTeX/LuaLaTeX support
372
+ - Implement file caching for faster recompilation
373
+ - Add syntax highlighting in code editor
374
+ - Support for multi-file LaTeX projects
375
+ - Real-time preview with live updates
376
+ - Integration with Overleaf-style collaborative editing
377
+
378
+ ## Support
379
+
380
+ - **Hugging Face Discussions**: Use the Community tab on your Space
381
+ - **Issues**: Report bugs via GitHub or Space discussions
382
+ - **Documentation**: Visit `/docs` endpoint for API reference
383
+ - **Examples**: Check the Usage Examples section above
384
+
385
+ ## Acknowledgments
386
+
387
+ - Built for the LaTeX community
388
+ - Powered by TeX Live and Flask
389
+ - Hosted on Hugging Face Spaces
390
+
391
+ ---
392
+
393
+ **Need help?** Visit the `/docs` endpoint or check Hugging Face Spaces documentation at https://huggingface.co/docs/hub/spaces-overview
app.py ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify, send_file, render_template_string
2
+ from flask_cors import CORS
3
+ import os
4
+ import subprocess
5
+ import tempfile
6
+ import shutil
7
+ from pathlib import Path
8
+
9
+ app = Flask(__name__)
10
+ CORS(app)
11
+
12
+ def read_file(filepath):
13
+ """Read index.html content"""
14
+ try:
15
+ with open(filepath, 'r', encoding='utf-8') as f:
16
+ return f.read()
17
+ except FileNotFoundError:
18
+ return "<h1>Frontend not found</h1><p>Please ensure index.html is in the same directory as app.py</p>"
19
+
20
+ @app.route('/')
21
+ def index():
22
+ """Serve the frontend HTML"""
23
+ html_content = read_file('index.html')
24
+ return render_template_string(html_content)
25
+
26
+ @app.route('/docs')
27
+ def docs():
28
+ """API documentation"""
29
+ docs_html = """
30
+ <!DOCTYPE html>
31
+ <html lang="en">
32
+ <head>
33
+ <meta charset="UTF-8">
34
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
35
+ <title>LaTeX Compiler API Documentation</title>
36
+ <script src="https://cdn.tailwindcss.com"></script>
37
+ </head>
38
+ <body class="bg-gray-50 p-8">
39
+ <div class="max-w-4xl mx-auto bg-white rounded-lg shadow-lg p-8">
40
+ <h1 class="text-3xl font-bold mb-6 text-gray-800">LaTeX Compiler API Documentation</h1>
41
+
42
+ <div class="mb-8">
43
+ <h2 class="text-2xl font-semibold mb-4 text-gray-700">Endpoints</h2>
44
+
45
+ <div class="mb-6 p-4 bg-blue-50 rounded-lg">
46
+ <h3 class="text-xl font-semibold mb-2 text-blue-900">POST /compile</h3>
47
+ <p class="mb-3 text-gray-700">Compile LaTeX code to PDF</p>
48
+
49
+ <h4 class="font-semibold mb-2">Request Methods:</h4>
50
+ <ul class="list-disc ml-6 mb-3 space-y-2">
51
+ <li><strong>File Upload:</strong> multipart/form-data with 'file' field (.tex file)</li>
52
+ <li><strong>JSON:</strong> application/json with {"code": "LaTeX source code"}</li>
53
+ </ul>
54
+
55
+ <h4 class="font-semibold mb-2">Success Response:</h4>
56
+ <pre class="bg-gray-800 text-green-400 p-3 rounded overflow-x-auto">Content-Type: application/pdf
57
+ PDF file binary data</pre>
58
+
59
+ <h4 class="font-semibold mb-2 mt-3">Error Response:</h4>
60
+ <pre class="bg-gray-800 text-red-400 p-3 rounded overflow-x-auto">{
61
+ "error": "Compilation failed",
62
+ "log": "LaTeX error log details..."
63
+ }</pre>
64
+ </div>
65
+
66
+ <div class="mb-6 p-4 bg-green-50 rounded-lg">
67
+ <h3 class="text-xl font-semibold mb-2 text-green-900">GET /</h3>
68
+ <p class="text-gray-700">Serve the web frontend interface</p>
69
+ </div>
70
+
71
+ <div class="p-4 bg-purple-50 rounded-lg">
72
+ <h3 class="text-xl font-semibold mb-2 text-purple-900">GET /docs</h3>
73
+ <p class="text-gray-700">Display this API documentation</p>
74
+ </div>
75
+ </div>
76
+
77
+ <div class="mb-8">
78
+ <h2 class="text-2xl font-semibold mb-4 text-gray-700">Example Usage</h2>
79
+
80
+ <h3 class="text-lg font-semibold mb-2">cURL - JSON Request:</h3>
81
+ <pre class="bg-gray-800 text-white p-4 rounded overflow-x-auto mb-4">curl -X POST http://localhost:7860/compile \\
82
+ -H "Content-Type: application/json" \\
83
+ -d '{"code": "\\\\documentclass{article}\\\\begin{document}Hello World\\\\end{document}"}' \\
84
+ --output output.pdf</pre>
85
+
86
+ <h3 class="text-lg font-semibold mb-2">cURL - File Upload:</h3>
87
+ <pre class="bg-gray-800 text-white p-4 rounded overflow-x-auto mb-4">curl -X POST http://localhost:7860/compile \\
88
+ -F "file=@document.tex" \\
89
+ --output output.pdf</pre>
90
+
91
+ <h3 class="text-lg font-semibold mb-2">Python Example:</h3>
92
+ <pre class="bg-gray-800 text-white p-4 rounded overflow-x-auto">import requests
93
+
94
+ latex_code = r"""
95
+ \\documentclass{article}
96
+ \\begin{document}
97
+ Hello from Python!
98
+ \\end{document}
99
+ """
100
+
101
+ response = requests.post(
102
+ 'http://localhost:7860/compile',
103
+ json={'code': latex_code}
104
+ )
105
+
106
+ if response.status_code == 200:
107
+ with open('output.pdf', 'wb') as f:
108
+ f.write(response.content)
109
+ else:
110
+ print(response.json())</pre>
111
+ </div>
112
+
113
+ <div class="mt-8 p-4 bg-yellow-50 rounded-lg">
114
+ <h3 class="font-semibold text-yellow-900 mb-2">⚠️ Notes</h3>
115
+ <ul class="list-disc ml-6 text-gray-700 space-y-1">
116
+ <li>Maximum file size recommended: 10MB</li>
117
+ <li>Compilation timeout: 30 seconds</li>
118
+ <li>Temporary files are automatically cleaned up</li>
119
+ <li>Only pdflatex engine is supported</li>
120
+ </ul>
121
+ </div>
122
+
123
+ <div class="mt-6 text-center">
124
+ <a href="/" class="text-blue-600 hover:text-blue-800 underline">← Back to Compiler</a>
125
+ </div>
126
+ </div>
127
+ </body>
128
+ </html>
129
+ """
130
+ return docs_html
131
+
132
+ @app.route('/compile', methods=['POST'])
133
+ def compile_latex():
134
+ """Compile LaTeX code to PDF"""
135
+ temp_dir = None
136
+
137
+ try:
138
+ # Create temporary directory
139
+ temp_dir = tempfile.mkdtemp()
140
+ tex_file = os.path.join(temp_dir, 'document.tex')
141
+ pdf_file = os.path.join(temp_dir, 'document.pdf')
142
+
143
+ # Get LaTeX code from request
144
+ latex_code = None
145
+
146
+ if request.is_json:
147
+ data = request.get_json()
148
+ latex_code = data.get('code', '')
149
+ elif 'file' in request.files:
150
+ file = request.files['file']
151
+ if file.filename == '':
152
+ return jsonify({'error': 'No file selected'}), 400
153
+ latex_code = file.read().decode('utf-8')
154
+ else:
155
+ return jsonify({'error': 'No LaTeX code provided. Send JSON with "code" field or upload .tex file'}), 400
156
+
157
+ if not latex_code:
158
+ return jsonify({'error': 'Empty LaTeX code'}), 400
159
+
160
+ # Write LaTeX code to file
161
+ with open(tex_file, 'w', encoding='utf-8') as f:
162
+ f.write(latex_code)
163
+
164
+ # Compile with pdflatex
165
+ result = subprocess.run(
166
+ ['pdflatex', '-interaction=nonstopmode', '-output-directory', temp_dir, tex_file],
167
+ capture_output=True,
168
+ text=True,
169
+ timeout=30
170
+ )
171
+
172
+ # Check if PDF was created
173
+ if os.path.exists(pdf_file):
174
+ return send_file(pdf_file, mimetype='application/pdf', as_attachment=True, download_name='compiled.pdf')
175
+ else:
176
+ # Compilation failed, return error log
177
+ log_file = os.path.join(temp_dir, 'document.log')
178
+ error_log = ''
179
+ if os.path.exists(log_file):
180
+ with open(log_file, 'r', encoding='utf-8', errors='ignore') as f:
181
+ error_log = f.read()
182
+
183
+ return jsonify({
184
+ 'error': 'Compilation failed',
185
+ 'log': error_log or result.stderr or result.stdout
186
+ }), 400
187
+
188
+ except subprocess.TimeoutExpired:
189
+ return jsonify({'error': 'Compilation timeout (30s limit exceeded)'}), 408
190
+ except Exception as e:
191
+ return jsonify({'error': f'Server error: {str(e)}'}), 500
192
+ finally:
193
+ # Clean up temporary files
194
+ if temp_dir and os.path.exists(temp_dir):
195
+ try:
196
+ shutil.rmtree(temp_dir)
197
+ except Exception:
198
+ pass
199
+
200
+ if __name__ == '__main__':
201
+ app.run(host='0.0.0.0', port=7860, debug=False)
index.html ADDED
@@ -0,0 +1,422 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>LaTeX Compiler - Modern PDF Generator</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <style>
9
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
10
+
11
+ * {
12
+ font-family: 'Inter', sans-serif;
13
+ }
14
+
15
+ .glass-effect {
16
+ background: rgba(255, 255, 255, 0.95);
17
+ backdrop-filter: blur(10px);
18
+ border: 1px solid rgba(255, 255, 255, 0.3);
19
+ }
20
+
21
+ .gradient-bg {
22
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
23
+ }
24
+
25
+ .code-editor {
26
+ font-family: 'Courier New', monospace;
27
+ background: #1e1e1e;
28
+ color: #d4d4d4;
29
+ }
30
+
31
+ .animate-fade-in {
32
+ animation: fadeIn 0.3s ease-in;
33
+ }
34
+
35
+ @keyframes fadeIn {
36
+ from { opacity: 0; transform: translateY(-10px); }
37
+ to { opacity: 1; transform: translateY(0); }
38
+ }
39
+
40
+ .btn-primary {
41
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
42
+ transition: all 0.3s ease;
43
+ }
44
+
45
+ .btn-primary:hover {
46
+ transform: translateY(-2px);
47
+ box-shadow: 0 10px 25px rgba(102, 126, 234, 0.4);
48
+ }
49
+
50
+ .tab-active {
51
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
52
+ color: white;
53
+ }
54
+
55
+ .tab-inactive {
56
+ background: #f3f4f6;
57
+ color: #6b7280;
58
+ }
59
+ </style>
60
+ </head>
61
+ <body class="min-h-screen gradient-bg">
62
+ <!-- Animated Background Pattern -->
63
+ <div class="fixed inset-0 opacity-10">
64
+ <div class="absolute inset-0" style="background-image: radial-gradient(circle at 2px 2px, white 1px, transparent 0); background-size: 40px 40px;"></div>
65
+ </div>
66
+
67
+ <div class="relative z-10 container mx-auto px-4 py-8 max-w-6xl">
68
+ <!-- Header -->
69
+ <div class="text-center mb-10">
70
+ <div class="inline-block mb-4">
71
+ <div class="text-6xl mb-2">📄</div>
72
+ </div>
73
+ <h1 class="text-5xl font-bold text-white mb-3 tracking-tight">LaTeX Compiler</h1>
74
+ <p class="text-xl text-purple-100 mb-4">Transform your LaTeX code into beautiful PDFs instantly</p>
75
+ <a href="/docs" class="inline-flex items-center text-white hover:text-purple-200 transition font-medium">
76
+ <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
77
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
78
+ </svg>
79
+ API Documentation
80
+ </a>
81
+ </div>
82
+
83
+ <!-- Main Card -->
84
+ <div class="glass-effect rounded-2xl shadow-2xl overflow-hidden">
85
+ <!-- Tab Navigation -->
86
+ <div class="flex border-b border-gray-200 bg-gray-50">
87
+ <button onclick="showTextarea()" id="btnTextarea" class="flex-1 px-6 py-4 font-semibold transition tab-active">
88
+ <div class="flex items-center justify-center">
89
+ <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
90
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"></path>
91
+ </svg>
92
+ Write Code
93
+ </div>
94
+ </button>
95
+ <button onclick="showFileUpload()" id="btnFile" class="flex-1 px-6 py-4 font-semibold transition tab-inactive">
96
+ <div class="flex items-center justify-center">
97
+ <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
98
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"></path>
99
+ </svg>
100
+ Upload File
101
+ </div>
102
+ </button>
103
+ </div>
104
+
105
+ <div class="p-8">
106
+ <!-- Code Editor Section -->
107
+ <div id="textareaSection">
108
+ <div class="mb-4 flex justify-between items-center">
109
+ <label class="text-sm font-semibold text-gray-700">LaTeX Source Code</label>
110
+ <button onclick="clearCode()" class="text-sm text-gray-500 hover:text-gray-700 transition">Clear</button>
111
+ </div>
112
+ <textarea id="latexCode" class="code-editor w-full h-96 p-4 rounded-xl border-2 border-gray-200 focus:border-purple-500 focus:ring-4 focus:ring-purple-200 transition-all resize-none" placeholder="% Start typing your LaTeX code...">% Beautiful LaTeX Document
113
+ \documentclass[12pt]{article}
114
+ \usepackage[utf8]{inputenc}
115
+ \usepackage{amsmath}
116
+ \usepackage{graphicx}
117
+ \usepackage{xcolor}
118
+
119
+ \title{\textbf{Professional LaTeX Document}}
120
+ \author{LaTeX Compiler}
121
+ \date{\today}
122
+
123
+ \begin{document}
124
+
125
+ \maketitle
126
+
127
+ \begin{abstract}
128
+ This is a professionally formatted LaTeX document demonstrating advanced typesetting capabilities.
129
+ \end{abstract}
130
+
131
+ \section{Introduction}
132
+ Welcome to the world of \LaTeX! This powerful typesetting system enables you to create documents with exceptional quality.
133
+
134
+ \section{Mathematical Excellence}
135
+ Einstein's groundbreaking equation:
136
+ \[
137
+ E = mc^2
138
+ \]
139
+
140
+ \subsection{Advanced Mathematics}
141
+ The Gaussian integral:
142
+ \[
143
+ \int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}
144
+ \]
145
+
146
+ Euler's identity, often called the most beautiful equation:
147
+ \[
148
+ e^{i\pi} + 1 = 0
149
+ \]
150
+
151
+ \section{Structured Content}
152
+ \subsection{Key Features}
153
+ \begin{itemize}
154
+ \item \textbf{Professional Typography:} Beautiful fonts and spacing
155
+ \item \textbf{Mathematical Typesetting:} Perfect equations every time
156
+ \item \textbf{Cross-referencing:} Automatic numbering and references
157
+ \item \textbf{Bibliography Support:} Manage citations effortlessly
158
+ \end{itemize}
159
+
160
+ \subsection{Quality Metrics}
161
+ \begin{enumerate}
162
+ \item Document structure and organization
163
+ \item Visual consistency throughout
164
+ \item Professional appearance
165
+ \item Academic standards compliance
166
+ \end{enumerate}
167
+
168
+ \section{Conclusion}
169
+ \LaTeX\ remains the gold standard for technical and academic document preparation, delivering unmatched quality and precision.
170
+
171
+ \end{document}</textarea>
172
+ </div>
173
+
174
+ <!-- File Upload Section -->
175
+ <div id="fileSection" class="hidden">
176
+ <div class="border-3 border-dashed border-purple-300 rounded-2xl p-12 text-center hover:border-purple-500 hover:bg-purple-50 transition-all cursor-pointer" onclick="document.getElementById('fileInput').click()">
177
+ <input type="file" id="fileInput" accept=".tex" class="hidden" onchange="handleFileSelect(event)">
178
+ <div class="mb-4">
179
+ <svg class="mx-auto h-20 w-20 text-purple-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
180
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"></path>
181
+ </svg>
182
+ </div>
183
+ <p class="text-lg font-semibold text-gray-700 mb-2">Drop your .tex file here</p>
184
+ <p class="text-sm text-gray-500 mb-4">or click to browse</p>
185
+ <div class="inline-block px-6 py-2 bg-purple-100 text-purple-700 rounded-lg font-medium">
186
+ Choose File
187
+ </div>
188
+ <p id="fileName" class="mt-6 text-base text-gray-700 font-semibold"></p>
189
+ </div>
190
+ </div>
191
+
192
+ <!-- Compile Button -->
193
+ <div class="mt-8">
194
+ <button onclick="compileLatex()" id="compileBtn" class="w-full btn-primary text-white py-4 px-8 rounded-xl font-bold text-lg shadow-xl flex items-center justify-center">
195
+ <svg class="w-6 h-6 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
196
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
197
+ </svg>
198
+ Compile to PDF
199
+ </button>
200
+ </div>
201
+
202
+ <!-- Status Messages -->
203
+ <div id="statusArea" class="hidden mt-6 animate-fade-in"></div>
204
+
205
+ <!-- Results -->
206
+ <div id="resultArea" class="hidden mt-6 animate-fade-in"></div>
207
+ </div>
208
+ </div>
209
+
210
+ <!-- Features Section -->
211
+ <div class="grid md:grid-cols-3 gap-6 mt-12">
212
+ <div class="glass-effect rounded-xl p-6 text-center">
213
+ <div class="text-4xl mb-3">⚡</div>
214
+ <h3 class="text-lg font-bold text-gray-800 mb-2">Lightning Fast</h3>
215
+ <p class="text-sm text-gray-600">Compile your documents in seconds with optimized pdflatex</p>
216
+ </div>
217
+ <div class="glass-effect rounded-xl p-6 text-center">
218
+ <div class="text-4xl mb-3">🔒</div>
219
+ <h3 class="text-lg font-bold text-gray-800 mb-2">Secure & Private</h3>
220
+ <p class="text-sm text-gray-600">Your documents are processed securely and deleted immediately</p>
221
+ </div>
222
+ <div class="glass-effect rounded-xl p-6 text-center">
223
+ <div class="text-4xl mb-3">📦</div>
224
+ <h3 class="text-lg font-bold text-gray-800 mb-2">Full TeX Live</h3>
225
+ <p class="text-sm text-gray-600">Access to complete LaTeX distribution with all packages</p>
226
+ </div>
227
+ </div>
228
+
229
+ <!-- Footer -->
230
+ <div class="text-center mt-12 text-white text-sm opacity-80">
231
+ <p>Powered by pdflatex • Open Source • Free Forever</p>
232
+ </div>
233
+ </div>
234
+
235
+ <script>
236
+ let selectedFile = null;
237
+
238
+ function showTextarea() {
239
+ document.getElementById('textareaSection').classList.remove('hidden');
240
+ document.getElementById('fileSection').classList.add('hidden');
241
+ document.getElementById('btnTextarea').className = 'flex-1 px-6 py-4 font-semibold transition tab-active';
242
+ document.getElementById('btnFile').className = 'flex-1 px-6 py-4 font-semibold transition tab-inactive';
243
+ selectedFile = null;
244
+ }
245
+
246
+ function showFileUpload() {
247
+ document.getElementById('textareaSection').classList.add('hidden');
248
+ document.getElementById('fileSection').classList.remove('hidden');
249
+ document.getElementById('btnFile').className = 'flex-1 px-6 py-4 font-semibold transition tab-active';
250
+ document.getElementById('btnTextarea').className = 'flex-1 px-6 py-4 font-semibold transition tab-inactive';
251
+ }
252
+
253
+ function clearCode() {
254
+ document.getElementById('latexCode').value = '';
255
+ }
256
+
257
+ function handleFileSelect(event) {
258
+ selectedFile = event.target.files[0];
259
+ if (selectedFile) {
260
+ document.getElementById('fileName').innerHTML = `
261
+ <span class="inline-flex items-center">
262
+ <svg class="w-5 h-5 mr-2 text-green-500" fill="currentColor" viewBox="0 0 20 20">
263
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path>
264
+ </svg>
265
+ ${selectedFile.name}
266
+ </span>
267
+ `;
268
+ }
269
+ }
270
+
271
+ function showStatus(message, type = 'info') {
272
+ const statusArea = document.getElementById('statusArea');
273
+ statusArea.classList.remove('hidden');
274
+
275
+ const styles = {
276
+ info: 'bg-blue-50 border-blue-200 text-blue-800',
277
+ success: 'bg-green-50 border-green-200 text-green-800',
278
+ error: 'bg-red-50 border-red-200 text-red-800',
279
+ warning: 'bg-yellow-50 border-yellow-200 text-yellow-800'
280
+ };
281
+
282
+ const icons = {
283
+ info: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>',
284
+ success: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>',
285
+ error: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>',
286
+ warning: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path>'
287
+ };
288
+
289
+ statusArea.innerHTML = `
290
+ <div class="border-2 ${styles[type]} rounded-xl p-4 flex items-start">
291
+ <svg class="w-6 h-6 mr-3 flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
292
+ ${icons[type]}
293
+ </svg>
294
+ <p class="font-medium flex-1">${message}</p>
295
+ </div>
296
+ `;
297
+ }
298
+
299
+ function showResult(pdfBlob) {
300
+ const resultArea = document.getElementById('resultArea');
301
+ resultArea.classList.remove('hidden');
302
+
303
+ const url = URL.createObjectURL(pdfBlob);
304
+
305
+ resultArea.innerHTML = `
306
+ <div class="bg-gradient-to-r from-green-50 to-emerald-50 border-2 border-green-200 rounded-xl p-8 text-center">
307
+ <div class="mb-6">
308
+ <svg class="mx-auto h-16 w-16 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
309
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
310
+ </svg>
311
+ </div>
312
+ <h3 class="text-2xl font-bold text-green-800 mb-2">Success!</h3>
313
+ <p class="text-green-700 mb-6">Your PDF has been compiled successfully</p>
314
+ <div class="flex flex-wrap gap-4 justify-center">
315
+ <a href="${url}" download="compiled.pdf" class="inline-flex items-center px-8 py-3 bg-green-600 text-white rounded-lg hover:bg-green-700 transition font-semibold shadow-lg hover:shadow-xl">
316
+ <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
317
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path>
318
+ </svg>
319
+ Download PDF
320
+ </a>
321
+ <a href="${url}" target="_blank" class="inline-flex items-center px-8 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition font-semibold shadow-lg hover:shadow-xl">
322
+ <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
323
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
324
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path>
325
+ </svg>
326
+ Preview
327
+ </a>
328
+ </div>
329
+ </div>
330
+ `;
331
+ }
332
+
333
+ function showError(errorMsg, log) {
334
+ const resultArea = document.getElementById('resultArea');
335
+ resultArea.classList.remove('hidden');
336
+
337
+ resultArea.innerHTML = `
338
+ <div class="bg-red-50 border-2 border-red-200 rounded-xl p-8">
339
+ <div class="flex items-start mb-4">
340
+ <svg class="h-8 w-8 text-red-500 mr-3 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
341
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
342
+ </svg>
343
+ <div class="flex-1">
344
+ <h3 class="text-xl font-bold text-red-800 mb-2">Compilation Failed</h3>
345
+ <p class="text-red-700 mb-4">${errorMsg}</p>
346
+ ${log ? `
347
+ <details class="bg-white rounded-lg border-2 border-red-200 p-4 cursor-pointer hover:border-red-300 transition">
348
+ <summary class="font-semibold text-red-800 hover:text-red-900">View Error Log</summary>
349
+ <pre class="mt-4 text-xs text-gray-700 overflow-x-auto whitespace-pre-wrap max-h-80 overflow-y-auto p-3 bg-gray-50 rounded">${log}</pre>
350
+ </details>
351
+ ` : ''}
352
+ </div>
353
+ </div>
354
+ </div>
355
+ `;
356
+ }
357
+
358
+ async function compileLatex() {
359
+ const compileBtn = document.getElementById('compileBtn');
360
+ compileBtn.disabled = true;
361
+ compileBtn.innerHTML = `
362
+ <svg class="animate-spin w-6 h-6 mr-3" fill="none" viewBox="0 0 24 24">
363
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
364
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
365
+ </svg>
366
+ Compiling...
367
+ `;
368
+
369
+ showStatus('🔄 Compiling your LaTeX document...', 'info');
370
+ document.getElementById('resultArea').classList.add('hidden');
371
+
372
+ try {
373
+ let response;
374
+
375
+ if (!document.getElementById('textareaSection').classList.contains('hidden')) {
376
+ const code = document.getElementById('latexCode').value;
377
+
378
+ if (!code.trim()) {
379
+ showStatus('⚠️ Please enter some LaTeX code', 'warning');
380
+ compileBtn.disabled = false;
381
+ compileBtn.innerHTML = '<svg class="w-6 h-6 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>Compile to PDF';
382
+ return;
383
+ }
384
+
385
+ response = await fetch('/compile', {
386
+ method: 'POST',
387
+ headers: { 'Content-Type': 'application/json' },
388
+ body: JSON.stringify({ code: code })
389
+ });
390
+ } else {
391
+ if (!selectedFile) {
392
+ showStatus('⚠️ Please select a .tex file', 'warning');
393
+ compileBtn.disabled = false;
394
+ compileBtn.innerHTML = '<svg class="w-6 h-6 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>Compile to PDF';
395
+ return;
396
+ }
397
+
398
+ const formData = new FormData();
399
+ formData.append('file', selectedFile);
400
+ response = await fetch('/compile', { method: 'POST', body: formData });
401
+ }
402
+
403
+ if (response.ok) {
404
+ const blob = await response.blob();
405
+ showStatus('✅ Compilation successful!', 'success');
406
+ showResult(blob);
407
+ } else {
408
+ const error = await response.json();
409
+ showStatus('❌ Compilation failed', 'error');
410
+ showError(error.error, error.log);
411
+ }
412
+ } catch (error) {
413
+ showStatus('❌ Network error', 'error');
414
+ showError('Failed to connect to server: ' + error.message);
415
+ } finally {
416
+ compileBtn.disabled = false;
417
+ compileBtn.innerHTML = '<svg class="w-6 h-6 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>Compile to PDF';
418
+ }
419
+ }
420
+ </script>
421
+ </body>
422
+ </html>