Update app.py
Browse files
app.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
"""
|
| 2 |
Backend API cho HT_MATH_WEB - Chạy trên Hugging Face Spaces (Docker Version)
|
| 3 |
-
Phiên bản: 9.
|
| 4 |
Tác giả: Hoàng Tấn Thiên
|
| 5 |
"""
|
| 6 |
|
|
@@ -27,7 +27,6 @@ import google.generativeai as genai
|
|
| 27 |
# --- PANDOC IMPORT ---
|
| 28 |
try:
|
| 29 |
import pypandoc
|
| 30 |
-
# print(f"INFO: Pandoc version detected: {pypandoc.get_pandoc_version()}")
|
| 31 |
except ImportError:
|
| 32 |
print("CRITICAL WARNING: pypandoc module not found.")
|
| 33 |
except OSError:
|
|
@@ -58,7 +57,7 @@ if SUPABASE_AVAILABLE and SUPABASE_URL and SUPABASE_KEY:
|
|
| 58 |
except Exception as e:
|
| 59 |
print(f"Warning: Không thể kết nối Supabase: {e}")
|
| 60 |
|
| 61 |
-
app = FastAPI(title="HT_MATH_WEB API", version="9.
|
| 62 |
|
| 63 |
app.add_middleware(
|
| 64 |
CORSMiddleware,
|
|
@@ -105,7 +104,7 @@ def check_rate_limit(request: Request):
|
|
| 105 |
pass
|
| 106 |
ip_rate_limits[client_ip] = now
|
| 107 |
|
| 108 |
-
# ===== PROMPTS (
|
| 109 |
DIRECT_GEMINI_PROMPT_TEXT_ONLY = r"""**TRÍCH XUẤT VĂN BẢN THUẦN TÚY**
|
| 110 |
⚠️ YÊU CẦU BẮT BUỘC:
|
| 111 |
- PHẢI trích xuất TOÀN BỘ nội dung xuất hiện trong ảnh/PDF
|
|
@@ -199,7 +198,7 @@ def stitch_text(text_a: str, text_b: str, min_overlap_chars: int = 20) -> str:
|
|
| 199 |
for i in range(scan_window, 0, -1):
|
| 200 |
tail_a = "\n".join(a_lines[-i:]).strip()
|
| 201 |
head_b = "\n".join(b_lines[:i]).strip()
|
| 202 |
-
# So sánh lỏng hơn
|
| 203 |
if len(tail_a) >= min_overlap_chars and tail_a.replace(" ","") == head_b.replace(" ",""):
|
| 204 |
best_overlap_idx = i
|
| 205 |
break
|
|
@@ -207,7 +206,6 @@ def stitch_text(text_a: str, text_b: str, min_overlap_chars: int = 20) -> str:
|
|
| 207 |
if best_overlap_idx > 0:
|
| 208 |
return text_a + "\n" + "\n".join(b_lines[best_overlap_idx:])
|
| 209 |
else:
|
| 210 |
-
# Nếu không tìm thấy điểm nối, nối tiếp luôn bằng 2 dòng trống để an toàn
|
| 211 |
return text_a + "\n\n" + text_b
|
| 212 |
|
| 213 |
def clean_latex_formulas(text: str) -> str:
|
|
@@ -224,7 +222,7 @@ def verify_password(password: str, hashed: str) -> bool:
|
|
| 224 |
@app.get("/")
|
| 225 |
@app.get("/health")
|
| 226 |
async def root():
|
| 227 |
-
return {"status": "ok", "version": "9.
|
| 228 |
|
| 229 |
@app.get("/api/models")
|
| 230 |
async def get_models():
|
|
@@ -315,7 +313,7 @@ async def upload_image(file: UploadFile = File(...)):
|
|
| 315 |
async def process_image_with_gemini(image: Image.Image, model_id: str, prompt: str, manager: ApiKeyManager, max_retries: int = 3) -> str:
|
| 316 |
"""Gửi ảnh lên Gemini với settings tắt bộ lọc an toàn để tránh mất nội dung"""
|
| 317 |
|
| 318 |
-
# Cấu hình tắt bộ lọc an toàn
|
| 319 |
safety_settings = [
|
| 320 |
{"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"},
|
| 321 |
{"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_NONE"},
|
|
@@ -333,7 +331,6 @@ async def process_image_with_gemini(image: Image.Image, model_id: str, prompt: s
|
|
| 333 |
genai.configure(api_key=api_key)
|
| 334 |
generation_config = {"temperature": 0.0, "top_p": 1.0, "max_output_tokens": 8192}
|
| 335 |
|
| 336 |
-
# Khởi tạo model kèm safety_settings
|
| 337 |
model = genai.GenerativeModel(
|
| 338 |
model_id,
|
| 339 |
generation_config=generation_config,
|
|
@@ -358,9 +355,11 @@ async def process_image_with_gemini(image: Image.Image, model_id: str, prompt: s
|
|
| 358 |
return ""
|
| 359 |
|
| 360 |
async def process_large_image(image: Image.Image, model: str, prompt: str, semaphore: asyncio.Semaphore, manager: ApiKeyManager) -> str:
|
| 361 |
-
#
|
| 362 |
-
|
| 363 |
-
|
|
|
|
|
|
|
| 364 |
|
| 365 |
width, height = image.size
|
| 366 |
|
|
@@ -378,7 +377,6 @@ async def process_large_image(image: Image.Image, model: str, prompt: str, semap
|
|
| 378 |
if bottom == height: break
|
| 379 |
y += (CHUNK_HEIGHT - OVERLAP_HEIGHT)
|
| 380 |
|
| 381 |
-
# Nếu phải cắt, xử lý từng phần rồi nối lại
|
| 382 |
async def process_chunk(chunk_img, index):
|
| 383 |
async with semaphore:
|
| 384 |
text = await process_image_with_gemini(chunk_img, model, prompt, manager)
|
|
|
|
| 1 |
"""
|
| 2 |
Backend API cho HT_MATH_WEB - Chạy trên Hugging Face Spaces (Docker Version)
|
| 3 |
+
Phiên bản: 9.9 (Forced Split Mode - Fix Missing Content)
|
| 4 |
Tác giả: Hoàng Tấn Thiên
|
| 5 |
"""
|
| 6 |
|
|
|
|
| 27 |
# --- PANDOC IMPORT ---
|
| 28 |
try:
|
| 29 |
import pypandoc
|
|
|
|
| 30 |
except ImportError:
|
| 31 |
print("CRITICAL WARNING: pypandoc module not found.")
|
| 32 |
except OSError:
|
|
|
|
| 57 |
except Exception as e:
|
| 58 |
print(f"Warning: Không thể kết nối Supabase: {e}")
|
| 59 |
|
| 60 |
+
app = FastAPI(title="HT_MATH_WEB API", version="9.9")
|
| 61 |
|
| 62 |
app.add_middleware(
|
| 63 |
CORSMiddleware,
|
|
|
|
| 104 |
pass
|
| 105 |
ip_rate_limits[client_ip] = now
|
| 106 |
|
| 107 |
+
# ===== PROMPTS (CHUẨN MỰC) =====
|
| 108 |
DIRECT_GEMINI_PROMPT_TEXT_ONLY = r"""**TRÍCH XUẤT VĂN BẢN THUẦN TÚY**
|
| 109 |
⚠️ YÊU CẦU BẮT BUỘC:
|
| 110 |
- PHẢI trích xuất TOÀN BỘ nội dung xuất hiện trong ảnh/PDF
|
|
|
|
| 198 |
for i in range(scan_window, 0, -1):
|
| 199 |
tail_a = "\n".join(a_lines[-i:]).strip()
|
| 200 |
head_b = "\n".join(b_lines[:i]).strip()
|
| 201 |
+
# So sánh lỏng hơn để tìm điểm trùng
|
| 202 |
if len(tail_a) >= min_overlap_chars and tail_a.replace(" ","") == head_b.replace(" ",""):
|
| 203 |
best_overlap_idx = i
|
| 204 |
break
|
|
|
|
| 206 |
if best_overlap_idx > 0:
|
| 207 |
return text_a + "\n" + "\n".join(b_lines[best_overlap_idx:])
|
| 208 |
else:
|
|
|
|
| 209 |
return text_a + "\n\n" + text_b
|
| 210 |
|
| 211 |
def clean_latex_formulas(text: str) -> str:
|
|
|
|
| 222 |
@app.get("/")
|
| 223 |
@app.get("/health")
|
| 224 |
async def root():
|
| 225 |
+
return {"status": "ok", "version": "9.9"}
|
| 226 |
|
| 227 |
@app.get("/api/models")
|
| 228 |
async def get_models():
|
|
|
|
| 313 |
async def process_image_with_gemini(image: Image.Image, model_id: str, prompt: str, manager: ApiKeyManager, max_retries: int = 3) -> str:
|
| 314 |
"""Gửi ảnh lên Gemini với settings tắt bộ lọc an toàn để tránh mất nội dung"""
|
| 315 |
|
| 316 |
+
# Cấu hình tắt bộ lọc an toàn
|
| 317 |
safety_settings = [
|
| 318 |
{"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"},
|
| 319 |
{"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_NONE"},
|
|
|
|
| 331 |
genai.configure(api_key=api_key)
|
| 332 |
generation_config = {"temperature": 0.0, "top_p": 1.0, "max_output_tokens": 8192}
|
| 333 |
|
|
|
|
| 334 |
model = genai.GenerativeModel(
|
| 335 |
model_id,
|
| 336 |
generation_config=generation_config,
|
|
|
|
| 355 |
return ""
|
| 356 |
|
| 357 |
async def process_large_image(image: Image.Image, model: str, prompt: str, semaphore: asyncio.Semaphore, manager: ApiKeyManager) -> str:
|
| 358 |
+
# THAY ĐỔI QUAN TRỌNG: Giảm CHUNK_HEIGHT xuống 1500 (Khoảng 2/3 trang A4 @ 200dpi)
|
| 359 |
+
# Điều này ÉP BUỘC code phải cắt trang A4 thành ít nhất 2 phần
|
| 360 |
+
# Giúp AI không bị quá tải và bỏ sót nửa trang sau.
|
| 361 |
+
CHUNK_HEIGHT = 1500
|
| 362 |
+
OVERLAP_HEIGHT = 300
|
| 363 |
|
| 364 |
width, height = image.size
|
| 365 |
|
|
|
|
| 377 |
if bottom == height: break
|
| 378 |
y += (CHUNK_HEIGHT - OVERLAP_HEIGHT)
|
| 379 |
|
|
|
|
| 380 |
async def process_chunk(chunk_img, index):
|
| 381 |
async with semaphore:
|
| 382 |
text = await process_image_with_gemini(chunk_img, model, prompt, manager)
|