hoangthiencm commited on
Commit
e3ed003
·
verified ·
1 Parent(s): 7075b0f

Upload 4 files

Browse files
Files changed (4) hide show
  1. Dockerfile +23 -0
  2. README.md +45 -10
  3. app.py +402 -0
  4. requirements.txt +9 -0
Dockerfile ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install system dependencies
6
+ RUN apt-get update && apt-get install -y \
7
+ libgl1-mesa-glx \
8
+ libglib2.0-0 \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ # Copy requirements and install Python dependencies
12
+ COPY requirements.txt .
13
+ RUN pip install --no-cache-dir -r requirements.txt
14
+
15
+ # Copy application code
16
+ COPY app.py .
17
+
18
+ # Expose port
19
+ EXPOSE 7860
20
+
21
+ # Run the application
22
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
23
+
README.md CHANGED
@@ -1,10 +1,45 @@
1
- ---
2
- title: Ht Math Web Backend
3
- emoji: 🔥
4
- colorFrom: indigo
5
- colorTo: blue
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # HT_MATH_WEB Backend
2
+
3
+ Backend API cho ứng dụng chuyển đổi PDF/Ảnh sang Markdown với LaTeX.
4
+
5
+ ## Cấu hình trên Hugging Face Spaces
6
+
7
+ 1. Tạo một Space mới trên Hugging Face
8
+ 2. Chọn SDK: **Docker**
9
+ 3. Upload các file: `app.py`, `requirements.txt`
10
+ 4. Thêm các biến môi trường trong Settings:
11
+
12
+ ### Biến môi trường cần thiết:
13
+
14
+ ```
15
+ GEMINI_API_KEYS=your_key1,your_key2,your_key3
16
+ GEMINI_MODELS=gemini-1.5-flash,gemini-1.5-pro
17
+ SUPABASE_URL=https://your-project.supabase.co
18
+ SUPABASE_KEY=your-supabase-anon-key
19
+ MAX_THREADS=3
20
+ ```
21
+
22
+ ### Tạo Dockerfile (nếu cần):
23
+
24
+ ```dockerfile
25
+ FROM python:3.10-slim
26
+
27
+ WORKDIR /app
28
+
29
+ COPY requirements.txt .
30
+ RUN pip install --no-cache-dir -r requirements.txt
31
+
32
+ COPY app.py .
33
+
34
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
35
+ ```
36
+
37
+ ## API Endpoints
38
+
39
+ - `GET /` - Health check
40
+ - `GET /api/models` - Lấy danh sách models
41
+ - `POST /api/register` - Đăng ký user
42
+ - `POST /api/login` - Đăng nhập
43
+ - `POST /api/convert` - Chuyển đổi file PDF/ảnh
44
+ - `POST /api/convert-base64` - Chuyển đổi ảnh từ base64
45
+
app.py ADDED
@@ -0,0 +1,402 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Backend API cho HT_MATH_WEB - Chạy trên Hugging Face Spaces
3
+ Tác giả: Hoàng Tấn Thiên
4
+ """
5
+
6
+ import os
7
+ import io
8
+ import json
9
+ import base64
10
+ import tempfile
11
+ import time
12
+ import re
13
+ from typing import List, Optional
14
+ from fastapi import FastAPI, File, UploadFile, HTTPException, Depends, Form
15
+ from fastapi.middleware.cors import CORSMiddleware
16
+ from fastapi.responses import JSONResponse, StreamingResponse
17
+ from pydantic import BaseModel
18
+ import fitz # PyMuPDF
19
+ from PIL import Image
20
+ import google.generativeai as genai
21
+ try:
22
+ from supabase import create_client, Client
23
+ SUPABASE_AVAILABLE = True
24
+ except ImportError:
25
+ SUPABASE_AVAILABLE = False
26
+ Client = None
27
+ create_client = None
28
+
29
+ import hashlib
30
+ import secrets
31
+
32
+ # ===== CẤU HÌNH =====
33
+ # Các biến môi trường (sẽ được set trên Hugging Face Spaces)
34
+ GEMINI_API_KEYS = os.getenv("GEMINI_API_KEYS", "").split(",") # Danh sách API keys cách nhau bởi dấu phẩy
35
+ GEMINI_MODELS = os.getenv("GEMINI_MODELS", "gemini-1.5-flash,gemini-1.5-pro").split(",") # Danh sách models
36
+ SUPABASE_URL = os.getenv("SUPABASE_URL", "")
37
+ SUPABASE_KEY = os.getenv("SUPABASE_KEY", "")
38
+ MAX_THREADS = int(os.getenv("MAX_THREADS", "3"))
39
+
40
+ # Khởi tạo Supabase client
41
+ supabase = None
42
+ if SUPABASE_AVAILABLE and SUPABASE_URL and SUPABASE_KEY:
43
+ try:
44
+ supabase = create_client(SUPABASE_URL, SUPABASE_KEY)
45
+ except Exception as e:
46
+ print(f"Warning: Không thể kết nối Supabase: {e}")
47
+
48
+ # Khởi tạo FastAPI
49
+ app = FastAPI(title="HT_MATH_WEB API", version="6.0")
50
+
51
+ # CORS middleware
52
+ app.add_middleware(
53
+ CORSMiddleware,
54
+ allow_origins=["*"], # Trong production, chỉ định domain cụ thể
55
+ allow_credentials=True,
56
+ allow_methods=["*"],
57
+ allow_headers=["*"],
58
+ )
59
+
60
+ # ===== PROMPTS (Giữ nguyên từ desktop app) =====
61
+ DIRECT_GEMINI_PROMPT_TEXT_ONLY = r"""**TRÍCH XUẤT VĂN BẢN THUẦN TÚY**
62
+ Bạn là một chuyên gia trong việc trích xuất nội dung văn bản từ ảnh và PDF.
63
+ NHIỆM VỤ CỦA BẠN:
64
+ 1. **Trích xuất toàn bộ văn bản** từ hình ảnh/PDF được cung cấp.
65
+ 2. **Giữ nguyên định dạng văn bản gốc**, bao gồm các ký tự toán học, mà **KHÔNG** chuyển đổi chúng sang LaTeX. Ví dụ, biểu thức `x^2 + y^2 = r^2` phải được giữ nguyên, không đổi thành `$x^2 + y^2 = r^2$`.
66
+ 3. **Chuyển đổi sang định dạng Markdown** cơ bản cho tiêu đề và bảng biểu.
67
+ 4. Các đề mục, tiêu đề, Định nghĩa, Ví dụ, Bài tập, trắc nghiệm, Lưu ý đều in đậm (dùng markdown `**text**`).
68
+ 5. Không trích xuất Header, Footer, hoặc số trang.
69
+ 6. Giữ nguyên cấu trúc đoạn văn, bảng biểu, và danh sách so với file gốc.
70
+ QUY TẮC QUAN TRỌNG:
71
+ - **KHÔNG SỬ DỤNG LATEX**. Mọi công thức toán học phải được giữ ở dạng văn bản thuần túy như trong tài liệu gốc.
72
+ PHẢN HỒI:
73
+ - Chỉ trả về văn bản Markdown đã được trích xuất.
74
+ - KHÔNG đưa ra bất kỳ giải thích nào hoặc tự ý thêm vào nội dung.
75
+ """
76
+
77
+ DIRECT_GEMINI_PROMPT_LATEX = r"""**TRÍCH XUẤT SANG MARKDOWN VỚI CÔNG THỨC LATEX ($...$)**
78
+ Bạn là một trợ lý kỹ thuật có nhiệm vụ duy nhất là chuyển đổi hình ảnh chứa nội dung toán học sang định dạng Markdown.
79
+ Sự chính xác tuyệt đối trong việc tuân thủ các quy tắc dưới đây là yêu cầu bắt buộc để đầu ra tương thích với công cụ xử lý tự động.
80
+ **QUY TẮC BẤT DI BẤT DỊCH:**
81
+ 1. **CHỈ MARKDOWN:** Toàn bộ đầu ra phải là văn bản Markdown thuần túy. CẤM TUYỆT ĐỐI chứa các lệnh như `\documentclass`, `\usepackage`, `\begin{document}`.
82
+ 2. **QUY TẮC DẤU ĐÔ LA ($) - QUAN TRỌNG NHẤT:**
83
+ - **TẤT CẢ, KHÔNG NGOẠI LỆ,** mọi thứ liên quan đến toán học phải được bọc trong cặp dấu đô la `$ ... $`. Điều này bao gồm biến đơn lẻ, số, hàm số, phương trình, ký hiệu, và các biểu thức dù đơn giản hay phức tạp. Đằng sau dấu `$`mở và đằng trước dấu `$` đóng không được phép có khoảng trắng. Ví dụ $[nội dung]$
84
+ - **Bảng ví dụ về các lỗi sai phổ biến và cách sửa đúng (BẮT BUỘC TUÂN THEO):**
85
+ | Sai (Không chấp nhận) | Đúng (Yêu cầu) |
86
+ | :--- | :--- |
87
+ | `Vậy f(x)=2x-13.` | `Vậy $f(x)=2x-13$.` |
88
+ | `Cho M(x)=0.` | `Cho $M(x)=0$.` |
89
+ | `Thay b=a-15 vào` | `Thay $b=a-15$ vào` |
90
+ | `a=2; b=-13` | `$a=2$; $b=-13$` |
91
+ | `Đa thức M(x) có nghiệm x=0.` | `Đa thức $M(x)$ có nghiệm $x=0$.` |
92
+ 3. **QUY TẮC VỀ KHOẢNG TRẮNG (CỰC KỲ QUAN TRỌNG):**
93
+ - **LUÔN LUÔN** phải có một khoảng trắng (space) đứng **ngay trước** dấu `$` mở đầu một công thức, trừ khi dấu `$` đó nằm ở đầu một dòng mới.
94
+ - **Ví dụ:**
95
+ - **SAI:** `...suy ra⇒$b=a-15$`
96
+ - **ĐÚNG:** `...suy ra ⇒ $b=a-15$`
97
+ - **SAI:** `...với x=0.`
98
+ - **ĐÚNG:** `...với $x=0$.`
99
+ 4. **GIỮ NGUYÊN CẤU TRÚC:**
100
+ - Giữ nguyên bố cục đoạn văn, các mục danh sách, tiêu đề so với ảnh gốc.
101
+ - Các tiêu đề như "Bài 7:", "Dạng 4:", "PHẦN I." phải được **in đậm**.
102
+ - Bảng biểu phải được giữ nguyên số hàng, số cột.
103
+ "**GIỮ NGUYÊN NGẮT DÒNG:** Cố gắng giữ lại các dấu xuống dòng và ngắt đoạn một cách chính xác nhất có thể so với văn bản gốc."
104
+ 5. HƯớNG DẫN ĐịNH DạNG:**
105
+ - Các đề mục, tiêu đề, Định nghĩa, Ví dụ, Bài tập, trắc nghiệm, Lưu ý đều in đậm (dùng markdown)
106
+ - Không chuyển Header và Footer, chỉ số trang của ảnh hoặc file pdf.
107
+ - Giữ nguyên format gốc của văn bản (căn lề, khoảng cách, xuống dòng), không được tự ý thay đổi nội dung, hình thức.
108
+ 6. Xử lý CÔNG THứC TOáN HọC:**
109
+ - Chuyển đổi TẤT CẢ công thức toán học sang định dạng LaTeX sử dụng $...$
110
+ - Đảm bảo các biểu thức, biến số (x,y,z,t,m,n,a,b,c,d,e)đều được đặt trong LaTeX.
111
+ - Đoạn thẳng (ví dụ: $AB$, $CD$,...)
112
+ - Điểm trong hình học (ví dụ: từ B kẻ BC thì viết từ $B$ kẻ $BC$)
113
+ - Đơn vị diện tích: $cm^{2}$
114
+ - Đơn vị độ: ${90^{0}}$
115
+ - Tam giác: $\Delta$ không dùng $\Triangle$
116
+ - Song song: "//" không dùng "\\"
117
+ - Thay "\cong" bằng dấu "="
118
+ - Thay "\overline" bằng "\widehat"
119
+ - Tích phân có cận: $\int\limits_{0}^{2}{y^{2}dx}$
120
+ - Phân số: hiển thị phân số ở dạng LaTeX sử dụng $\frac{tử số}{mẫu số}$, ví dụ: $\frac{1}{2}$, $\frac{3}{4}$, $\frac{a}{b}$
121
+ - Số thập phân dùng dấu "," để ký hiệu. Ví dụ: $1,3$
122
+ 7. **Xử lý BảNG:**
123
+ - Bảo toàn cấu trúc bảng gốc - KHÔNG đổi hàng thành cột hoặc cột thành hàng.
124
+ - Số lượng hàng và cột trong bảng Markdown phải giống hệt như trong tài liệu gốc.
125
+ - Giữ nguyên thứ tự của các hàng và cột.
126
+ - Đặc biệt đối với bảng thống kê, bảng dữ liệu: PHẢI giữ nguyên cấu trúc với đúng số cột và hàng.
127
+ - Sử dụng định dạng bảng Markdown chuẩn với dấu | phân cách cột và dòng --- sau tiêu đề.
128
+ - Ví dụ, với bảng có dạng:
129
+ | Chiều cao (cm) | [150;158) | [158;161) | [161;164) | [164;167) |
130
+ |---------------|-----------|-----------|-----------|-----------|
131
+ | Số học sinh | 5 | 12 | 15 | 8 |
132
+ Phải giữ nguyên cấu trúc này, KHÔNG được chuyển thành:
133
+ | Chiều cao (cm) | Số học sinh |
134
+ |---------------|------------|
135
+ | [150;158) | 5 |
136
+ | [158;161) | 12 |
137
+ | [161;164) | 15 |
138
+ | [164;167) | 8 |
139
+ 8. **PHảN HỒI:**
140
+ - Chỉ trả về văn bản Markdown đã được trích xuất.
141
+ - KHÔNG đưa ra bất kỳ giải thích nào hoặc tự ý thêm vào nội dung.
142
+ **Dữ liệu đầU VàO:**
143
+ Hình ảnh/PDF gốc
144
+ **ĐầU RA MONG MUốN:**
145
+ Văn bản Markdown tiếng Việt hoàn chỉnh với công thức LaTeX và đã được sửa lỗi chính tả.
146
+ **BắT ĐầU THựC HIệN NGAY! **
147
+ """
148
+
149
+ # ===== MODELS =====
150
+ class ApiKeyManager:
151
+ """Quản lý API keys với cơ chế xoay vòng"""
152
+ def __init__(self, keys: List[str]):
153
+ self.api_keys = [k.strip() for k in keys if k.strip()]
154
+ self.current_index = 0
155
+
156
+ def get_next_key(self) -> Optional[str]:
157
+ if not self.api_keys:
158
+ return None
159
+ key = self.api_keys[self.current_index]
160
+ self.current_index = (self.current_index + 1) % len(self.api_keys)
161
+ return key
162
+
163
+ def get_key_count(self) -> int:
164
+ return len(self.api_keys)
165
+
166
+ # Khởi tạo key manager
167
+ key_manager = ApiKeyManager(GEMINI_API_KEYS)
168
+
169
+ # ===== HELPER FUNCTIONS =====
170
+ def clean_latex_formulas(text: str) -> str:
171
+ """Làm sạch công thức LaTeX"""
172
+ return re.sub(r'\$(.*?)\$', lambda m: f'${m.group(1).strip()}$', text)
173
+
174
+ def hash_password(password: str) -> str:
175
+ """Hash mật khẩu"""
176
+ return hashlib.sha256(password.encode()).hexdigest()
177
+
178
+ def verify_password(password: str, hashed: str) -> bool:
179
+ """Xác thực mật khẩu"""
180
+ return hash_password(password) == hashed
181
+
182
+ # ===== API ENDPOINTS =====
183
+
184
+ @app.get("/")
185
+ async def root():
186
+ """Health check endpoint"""
187
+ return {
188
+ "status": "ok",
189
+ "service": "HT_MATH_WEB API",
190
+ "version": "6.0",
191
+ "keys_loaded": key_manager.get_key_count(),
192
+ "models_available": GEMINI_MODELS
193
+ }
194
+
195
+ @app.get("/api/models")
196
+ async def get_models():
197
+ """Lấy danh sách models"""
198
+ return {"models": GEMINI_MODELS}
199
+
200
+ @app.post("/api/register")
201
+ async def register(email: str = Form(...), password: str = Form(...)):
202
+ """Đăng ký user mới"""
203
+ if not supabase:
204
+ raise HTTPException(status_code=500, detail="Database không khả dụng")
205
+
206
+ try:
207
+ # Kiểm tra email đã tồn tại chưa
208
+ result = supabase.table("users").select("email").eq("email", email).execute()
209
+ if result.data:
210
+ raise HTTPException(status_code=400, detail="Email đã được sử dụng")
211
+
212
+ # Tạo user mới
213
+ hashed_password = hash_password(password)
214
+ user_data = {
215
+ "email": email,
216
+ "password": hashed_password,
217
+ "created_at": time.strftime("%Y-%m-%d %H:%M:%S")
218
+ }
219
+
220
+ result = supabase.table("users").insert(user_data).execute()
221
+ return {"success": True, "message": "Đăng ký thành công"}
222
+ except HTTPException:
223
+ raise
224
+ except Exception as e:
225
+ raise HTTPException(status_code=500, detail=f"Lỗi đăng ký: {str(e)}")
226
+
227
+ @app.post("/api/login")
228
+ async def login(email: str = Form(...), password: str = Form(...)):
229
+ """Đăng nhập"""
230
+ if not supabase:
231
+ raise HTTPException(status_code=500, detail="Database không khả dụng")
232
+
233
+ try:
234
+ result = supabase.table("users").select("*").eq("email", email).execute()
235
+ if not result.data:
236
+ raise HTTPException(status_code=401, detail="Email hoặc mật khẩu không đúng")
237
+
238
+ user = result.data[0]
239
+ if not verify_password(password, user["password"]):
240
+ raise HTTPException(status_code=401, detail="Email hoặc mật khẩu không đúng")
241
+
242
+ # Tạo token đơn giản (trong production nên dùng JWT)
243
+ token = secrets.token_urlsafe(32)
244
+ # Lưu token vào session (có thể lưu vào Redis hoặc database)
245
+
246
+ return {
247
+ "success": True,
248
+ "token": token,
249
+ "email": email
250
+ }
251
+ except HTTPException:
252
+ raise
253
+ except Exception as e:
254
+ raise HTTPException(status_code=500, detail=f"Lỗi đăng nhập: {str(e)}")
255
+
256
+ async def process_image_with_gemini(
257
+ image: Image.Image,
258
+ model_id: str,
259
+ prompt: str,
260
+ max_retries: int = 3
261
+ ) -> str:
262
+ """Xử lý ảnh với Gemini API"""
263
+ for attempt in range(max_retries):
264
+ try:
265
+ api_key = key_manager.get_next_key()
266
+ if not api_key:
267
+ raise ValueError("Không tìm thấy API Key nào")
268
+
269
+ genai.configure(api_key=api_key)
270
+
271
+ safety_settings = [
272
+ {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"},
273
+ {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_NONE"},
274
+ {"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_NONE"},
275
+ {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_NONE"},
276
+ ]
277
+
278
+ generation_config = {
279
+ "temperature": 0.1,
280
+ "top_p": 0.95,
281
+ "top_k": 64,
282
+ "max_output_tokens": 8192,
283
+ }
284
+
285
+ model = genai.GenerativeModel(
286
+ model_name=model_id,
287
+ generation_config=generation_config,
288
+ safety_settings=safety_settings
289
+ )
290
+
291
+ response = model.generate_content([prompt, image])
292
+
293
+ if response.text:
294
+ return response.text
295
+ else:
296
+ raise ValueError("Phản hồi từ API rỗng")
297
+
298
+ except Exception as e:
299
+ error_str = str(e)
300
+ if "429" in error_str or "ResourceExhausted" in error_str or "Quota exceeded" in error_str:
301
+ if attempt < max_retries - 1:
302
+ time.sleep(2 * (attempt + 1))
303
+ continue
304
+ raise HTTPException(status_code=500, detail=f"Lỗi xử lý ảnh: {error_str}")
305
+
306
+ raise HTTPException(status_code=500, detail="Thất bại sau nhiều lần thử")
307
+
308
+ @app.post("/api/convert")
309
+ async def convert_file(
310
+ file: UploadFile = File(...),
311
+ model: str = Form("gemini-1.5-flash"),
312
+ mode: str = Form("latex") # "latex" hoặc "text_only"
313
+ ):
314
+ """Chuyển đổi PDF/ảnh sang Markdown"""
315
+ if key_manager.get_key_count() == 0:
316
+ raise HTTPException(status_code=500, detail="Chưa cấu hình API Key")
317
+
318
+ if model not in GEMINI_MODELS:
319
+ raise HTTPException(status_code=400, detail=f"Model không hợp lệ. Models khả dụng: {GEMINI_MODELS}")
320
+
321
+ prompt = DIRECT_GEMINI_PROMPT_LATEX if mode == "latex" else DIRECT_GEMINI_PROMPT_TEXT_ONLY
322
+
323
+ try:
324
+ # Đọc file
325
+ file_content = await file.read()
326
+ file_ext = os.path.splitext(file.filename)[1].lower()
327
+
328
+ results = []
329
+
330
+ if file_ext == ".pdf":
331
+ # Xử lý PDF
332
+ doc = fitz.open(stream=file_content, filetype="pdf")
333
+ for page_num in range(len(doc)):
334
+ page = doc[page_num]
335
+ pix = page.get_pixmap(dpi=250)
336
+ img = Image.open(io.BytesIO(pix.tobytes(output="png")))
337
+
338
+ text = await process_image_with_gemini(img, model, prompt)
339
+ results.append(text)
340
+
341
+ doc.close()
342
+ elif file_ext in [".png", ".jpg", ".jpeg", ".bmp"]:
343
+ # Xử lý ảnh
344
+ img = Image.open(io.BytesIO(file_content))
345
+ text = await process_image_with_gemini(img, model, prompt)
346
+ results.append(text)
347
+ else:
348
+ raise HTTPException(status_code=400, detail="Định dạng file không được hỗ trợ")
349
+
350
+ # Tổng hợp kết quả
351
+ final_text = "\n\n".join(results)
352
+ cleaned_text = clean_latex_formulas(final_text)
353
+
354
+ return {
355
+ "success": True,
356
+ "result": cleaned_text,
357
+ "pages_processed": len(results)
358
+ }
359
+
360
+ except HTTPException:
361
+ raise
362
+ except Exception as e:
363
+ raise HTTPException(status_code=500, detail=f"Lỗi xử lý file: {str(e)}")
364
+
365
+ @app.post("/api/convert-base64")
366
+ async def convert_base64(
367
+ image_data: str = Form(...), # Base64 encoded image
368
+ model: str = Form("gemini-1.5-flash"),
369
+ mode: str = Form("latex")
370
+ ):
371
+ """Chuyển đổi ảnh từ base64"""
372
+ if key_manager.get_key_count() == 0:
373
+ raise HTTPException(status_code=500, detail="Chưa cấu hình API Key")
374
+
375
+ if model not in GEMINI_MODELS:
376
+ raise HTTPException(status_code=400, detail=f"Model không hợp lệ")
377
+
378
+ prompt = DIRECT_GEMINI_PROMPT_LATEX if mode == "latex" else DIRECT_GEMINI_PROMPT_TEXT_ONLY
379
+
380
+ try:
381
+ # Decode base64
382
+ if "," in image_data:
383
+ image_data = image_data.split(",")[1] # Remove data:image/png;base64, prefix
384
+
385
+ image_bytes = base64.b64decode(image_data)
386
+ img = Image.open(io.BytesIO(image_bytes))
387
+
388
+ text = await process_image_with_gemini(img, model, prompt)
389
+ cleaned_text = clean_latex_formulas(text)
390
+
391
+ return {
392
+ "success": True,
393
+ "result": cleaned_text
394
+ }
395
+
396
+ except Exception as e:
397
+ raise HTTPException(status_code=500, detail=f"Lỗi xử lý ảnh: {str(e)}")
398
+
399
+ if __name__ == "__main__":
400
+ import uvicorn
401
+ uvicorn.run(app, host="0.0.0.0", port=7860)
402
+
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ fastapi==0.104.1
2
+ uvicorn[standard]==0.24.0
3
+ python-multipart==0.0.6
4
+ pillow==10.1.0
5
+ PyMuPDF==1.23.8
6
+ google-generativeai==0.3.2
7
+ supabase==2.0.3
8
+ pydantic==2.5.0
9
+