Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -7,10 +7,14 @@ import re
|
|
| 7 |
from typing import Optional
|
| 8 |
from fastapi import FastAPI, File, UploadFile, HTTPException
|
| 9 |
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
| 10 |
from pydantic import BaseModel
|
| 11 |
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
|
| 12 |
import torch
|
| 13 |
import fitz # PyMuPDF
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
# ============================================================
|
| 16 |
# Initialize FastAPI App
|
|
@@ -303,26 +307,85 @@ async def summarize_text(request: SummarizeRequest):
|
|
| 303 |
detail="Văn bản quá ngắn để tóm tắt (cần ít nhất 50 ký tự)."
|
| 304 |
)
|
| 305 |
|
| 306 |
-
#
|
| 307 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 308 |
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
status_code=500,
|
| 312 |
-
detail="Không thể tạo tóm tắt."
|
| 313 |
-
)
|
| 314 |
|
| 315 |
-
|
| 316 |
-
bullet_points = format_as_bullet_points(summaries, max_points=max_points)
|
| 317 |
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 326 |
|
| 327 |
|
| 328 |
@app.post("/upload-pdf")
|
|
@@ -330,7 +393,7 @@ async def upload_pdf(file: UploadFile = File(...), length_level: int = 1):
|
|
| 330 |
"""
|
| 331 |
Upload và tóm tắt file PDF.
|
| 332 |
Đọc qua byte stream, KHÔNG lưu file ra đĩa.
|
| 333 |
-
Trả về
|
| 334 |
length_level: 0 = Ngắn (2-3 ý), 1 = Trung bình (4-5 ý), 2 = Chi tiết (6+ ý)
|
| 335 |
"""
|
| 336 |
# Map length_level to max_points
|
|
@@ -365,7 +428,7 @@ async def upload_pdf(file: UploadFile = File(...), length_level: int = 1):
|
|
| 365 |
detail="File quá lớn. Giới hạn 10MB."
|
| 366 |
)
|
| 367 |
|
| 368 |
-
# Extract text from PDF bytes
|
| 369 |
text = extract_text_from_pdf_bytes(contents)
|
| 370 |
|
| 371 |
# Validate extracted text
|
|
@@ -375,27 +438,11 @@ async def upload_pdf(file: UploadFile = File(...), length_level: int = 1):
|
|
| 375 |
detail="Văn bản trích xuất từ PDF quá ngắn."
|
| 376 |
)
|
| 377 |
|
| 378 |
-
#
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
status_code=500,
|
| 384 |
-
detail="Không thể tạo tóm tắt."
|
| 385 |
-
)
|
| 386 |
-
|
| 387 |
-
# Format as bullet points với giới hạn số lượng
|
| 388 |
-
bullet_points = format_as_bullet_points(summaries, max_points=max_points)
|
| 389 |
-
|
| 390 |
-
return {
|
| 391 |
-
"success": True,
|
| 392 |
-
"filename": file.filename,
|
| 393 |
-
"original_length": len(text),
|
| 394 |
-
"word_count": len(text.split()),
|
| 395 |
-
"num_chunks": len(summaries),
|
| 396 |
-
"length_level": length_level,
|
| 397 |
-
"summary": bullet_points
|
| 398 |
-
}
|
| 399 |
|
| 400 |
|
| 401 |
# ============================================================
|
|
|
|
| 7 |
from typing import Optional
|
| 8 |
from fastapi import FastAPI, File, UploadFile, HTTPException
|
| 9 |
from fastapi.middleware.cors import CORSMiddleware
|
| 10 |
+
from fastapi.responses import StreamingResponse
|
| 11 |
from pydantic import BaseModel
|
| 12 |
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
|
| 13 |
import torch
|
| 14 |
import fitz # PyMuPDF
|
| 15 |
+
import json
|
| 16 |
+
import gc
|
| 17 |
+
import asyncio
|
| 18 |
|
| 19 |
# ============================================================
|
| 20 |
# Initialize FastAPI App
|
|
|
|
| 307 |
detail="Văn bản quá ngắn để tóm tắt (cần ít nhất 50 ký tự)."
|
| 308 |
)
|
| 309 |
|
| 310 |
+
# Return StreamingResponse
|
| 311 |
+
return StreamingResponse(
|
| 312 |
+
stream_summary_generator(text, max_points),
|
| 313 |
+
media_type="application/x-ndjson"
|
| 314 |
+
)
|
| 315 |
+
|
| 316 |
+
|
| 317 |
+
async def stream_summary_generator(text: str, max_points: int = None):
|
| 318 |
+
"""
|
| 319 |
+
Generator function to stream summary chunks.
|
| 320 |
+
Implements: Recursive chunking, Context-aware summarization, Memory optimization.
|
| 321 |
+
YIELDS JSON: {"text": "• Point 1\n", "done": False} + "\n"
|
| 322 |
+
"""
|
| 323 |
+
chunks = chunk_text_by_words(text, max_words=800)
|
| 324 |
+
total_chunks = len(chunks)
|
| 325 |
|
| 326 |
+
# Context cho chunk tiếp theo (summary của chunk trước)
|
| 327 |
+
context_summary = ""
|
|
|
|
|
|
|
|
|
|
| 328 |
|
| 329 |
+
bullet_count = 0
|
|
|
|
| 330 |
|
| 331 |
+
for i, chunk in enumerate(chunks):
|
| 332 |
+
# 1. Prepare input: Context + Current Chunk
|
| 333 |
+
# Nếu có context, nối vào đầu chunk (có phân cách)
|
| 334 |
+
if context_summary:
|
| 335 |
+
# Giới hạn context để tránh quá dài (lấy 200 ký tự cuối của summary trước)
|
| 336 |
+
short_context = context_summary[-200:] if len(context_summary) > 200 else context_summary
|
| 337 |
+
input_text = f"Tóm tắt tiếp theo ngữ cảnh: {short_context}\nNội dung: {chunk}"
|
| 338 |
+
else:
|
| 339 |
+
input_text = chunk
|
| 340 |
+
|
| 341 |
+
# 2. Generate Summary
|
| 342 |
+
# Chạy trong threadpool để không chặn event loop của FastAPI
|
| 343 |
+
try:
|
| 344 |
+
summary_part = await asyncio.to_thread(generate_summary, input_text)
|
| 345 |
+
except Exception as e:
|
| 346 |
+
error_json = json.dumps({"error": str(e), "done": True})
|
| 347 |
+
yield error_json + "\n"
|
| 348 |
+
return
|
| 349 |
+
|
| 350 |
+
# 3. Format as bullets
|
| 351 |
+
# Chỉ lấy max_points còn lại nếu có giới hạn
|
| 352 |
+
points_limit = None
|
| 353 |
+
if max_points is not None:
|
| 354 |
+
points_limit = max_points - bullet_count
|
| 355 |
+
if points_limit <= 0:
|
| 356 |
+
break # Đã đủ số ý
|
| 357 |
+
|
| 358 |
+
bullets_text = format_as_bullet_points([summary_part], max_points=points_limit)
|
| 359 |
+
|
| 360 |
+
if bullets_text:
|
| 361 |
+
# Update context for next iteration
|
| 362 |
+
context_summary = summary_part.replace('\n', ' ')
|
| 363 |
+
|
| 364 |
+
# Count bullets
|
| 365 |
+
new_points = bullets_text.count('•')
|
| 366 |
+
bullet_count += new_points
|
| 367 |
+
|
| 368 |
+
# Yield Result
|
| 369 |
+
result_json = json.dumps({
|
| 370 |
+
"text": bullets_text + "\n",
|
| 371 |
+
"done": False,
|
| 372 |
+
"progress": int((i + 1) / total_chunks * 100)
|
| 373 |
+
})
|
| 374 |
+
yield result_json + "\n"
|
| 375 |
+
|
| 376 |
+
# 4. Memory Optimization
|
| 377 |
+
try:
|
| 378 |
+
del input_text
|
| 379 |
+
del summary_part
|
| 380 |
+
except UnboundLocalError:
|
| 381 |
+
pass
|
| 382 |
+
gc.collect() # Force garbage collection
|
| 383 |
+
|
| 384 |
+
# Nhường CPU cho request khác 1 chút
|
| 385 |
+
await asyncio.sleep(0.1)
|
| 386 |
+
|
| 387 |
+
# Final message
|
| 388 |
+
yield json.dumps({"text": "", "done": True, "progress": 100}) + "\n"
|
| 389 |
|
| 390 |
|
| 391 |
@app.post("/upload-pdf")
|
|
|
|
| 393 |
"""
|
| 394 |
Upload và tóm tắt file PDF.
|
| 395 |
Đọc qua byte stream, KHÔNG lưu file ra đĩa.
|
| 396 |
+
Trả về StreamingResponse (NDJSON).
|
| 397 |
length_level: 0 = Ngắn (2-3 ý), 1 = Trung bình (4-5 ý), 2 = Chi tiết (6+ ý)
|
| 398 |
"""
|
| 399 |
# Map length_level to max_points
|
|
|
|
| 428 |
detail="File quá lớn. Giới hạn 10MB."
|
| 429 |
)
|
| 430 |
|
| 431 |
+
# Extract text from PDF bytes
|
| 432 |
text = extract_text_from_pdf_bytes(contents)
|
| 433 |
|
| 434 |
# Validate extracted text
|
|
|
|
| 438 |
detail="Văn bản trích xuất từ PDF quá ngắn."
|
| 439 |
)
|
| 440 |
|
| 441 |
+
# Return StreamingResponse
|
| 442 |
+
return StreamingResponse(
|
| 443 |
+
stream_summary_generator(text, max_points),
|
| 444 |
+
media_type="application/x-ndjson"
|
| 445 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 446 |
|
| 447 |
|
| 448 |
# ============================================================
|