Tungdabiban commited on
Commit
ff3a585
·
verified ·
1 Parent(s): 3f0c00b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +61 -109
app.py CHANGED
@@ -4,7 +4,6 @@ Model: vinai/bartpho-syllable-base
4
  """
5
 
6
  import re
7
- import io
8
  from typing import Optional
9
  from fastapi import FastAPI, File, UploadFile, HTTPException
10
  from fastapi.middleware.cors import CORSMiddleware
@@ -13,13 +12,6 @@ from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
13
  import torch
14
  import fitz # PyMuPDF
15
 
16
- # Optional: newspaper3k for URL extraction
17
- try:
18
- from newspaper import Article
19
- NEWSPAPER_AVAILABLE = True
20
- except ImportError:
21
- NEWSPAPER_AVAILABLE = False
22
-
23
  # ============================================================
24
  # Initialize FastAPI App
25
  # ============================================================
@@ -29,7 +21,7 @@ app = FastAPI(
29
  version="1.0.0"
30
  )
31
 
32
- # CORS middleware
33
  app.add_middleware(
34
  CORSMiddleware,
35
  allow_origins=["*"],
@@ -53,15 +45,14 @@ print("Model loaded successfully!")
53
  # Request Models
54
  # ============================================================
55
  class SummarizeRequest(BaseModel):
56
- text: Optional[str] = None
57
- url: Optional[str] = None
58
 
59
 
60
  # ============================================================
61
  # Helper Functions
62
  # ============================================================
63
 
64
- def chunk_text_by_words(text: str, max_words: int = 700) -> list[str]:
65
  """
66
  Chia văn bản thành các đoạn tối đa max_words từ.
67
  Giữ nguyên câu hoàn chỉnh khi có thể.
@@ -82,23 +73,19 @@ def chunk_text_by_words(text: str, max_words: int = 700) -> list[str]:
82
 
83
  # Nếu câu đơn lẻ dài hơn max_words, chia nhỏ câu đó
84
  if sentence_word_count > max_words:
85
- # Lưu chunk hiện tại trước
86
  if current_chunk:
87
  chunks.append(' '.join(current_chunk))
88
  current_chunk = []
89
  current_word_count = 0
90
 
91
- # Chia câu dài thành các phần
92
  for i in range(0, sentence_word_count, max_words):
93
  chunk_words = sentence_words[i:i + max_words]
94
  chunks.append(' '.join(chunk_words))
95
 
96
  # Nếu thêm câu này vượt quá giới hạn
97
  elif current_word_count + sentence_word_count > max_words:
98
- # Lưu chunk hiện tại
99
  if current_chunk:
100
  chunks.append(' '.join(current_chunk))
101
- # Bắt đầu chunk mới với câu này
102
  current_chunk = [sentence]
103
  current_word_count = sentence_word_count
104
 
@@ -113,46 +100,39 @@ def chunk_text_by_words(text: str, max_words: int = 700) -> list[str]:
113
  return chunks
114
 
115
 
116
- def fix_truncated_sentence(text: str) -> str:
117
  """
118
- Xử câu bị cụt cuối.
119
- - Nếu câu cuối không kết thúc bằng dấu câu, thêm dấu chấm
120
- - Hoặc xóa câu bị cụt nếu quá ngắn
121
  """
122
  text = text.strip()
123
 
124
  if not text:
125
  return text
126
 
127
- # Kiểm tra nếu kết thúc bằng dấu câu
128
  if text[-1] in '.!?':
129
  return text
130
 
131
- # Tìm câu cuối cùng hoàn chỉnh
132
- last_sentence_end = max(
133
- text.rfind('.'),
134
- text.rfind('!'),
135
- text.rfind('?')
136
- )
137
 
138
  if last_sentence_end > 0:
139
- # Lấy phần sau dấu câu cuối
140
- incomplete_part = text[last_sentence_end + 1:].strip()
141
-
142
- # Nếu phần không hoàn chỉnh quá ngắn (ít hơn 5 từ), xóa nó
143
- if len(incomplete_part.split()) < 5:
144
- return text[:last_sentence_end + 1]
145
- else:
146
- # Thêm dấu chấm để kết thúc
147
- return text + '.'
148
 
149
  # Nếu không có dấu câu nào, thêm dấu chấm
150
  return text + '.'
151
 
152
 
153
- def format_as_bullet_points(summaries: list[str]) -> list[str]:
154
  """
155
- Chuyển đổi các đoạn tóm tắt thành danh sách bullet points.
 
156
  """
157
  bullet_points = []
158
 
@@ -162,17 +142,18 @@ def format_as_bullet_points(summaries: list[str]) -> list[str]:
162
 
163
  for sentence in sentences:
164
  sentence = sentence.strip()
165
- if sentence and len(sentence) > 10: # Bỏ qua câu quá ngắn
166
  # Đảm bảo câu kết thúc đúng
167
- sentence = fix_truncated_sentence(sentence)
168
- bullet_points.append(sentence)
169
 
170
- return bullet_points
171
 
172
 
173
  def generate_summary(text: str) -> str:
174
  """
175
- Sinh tóm tắt với các tham số chống cụt.
 
176
  """
177
  try:
178
  # Tokenize input
@@ -183,21 +164,25 @@ def generate_summary(text: str) -> str:
183
  return_tensors="pt"
184
  )
185
 
186
- # Generate summary
187
  with torch.no_grad():
188
  summary_ids = model.generate(
189
  inputs["input_ids"],
190
  attention_mask=inputs["attention_mask"],
191
- max_length=300,
192
  min_length=100,
193
- no_repeat_ngram_size=3,
194
- repetition_penalty=2.5,
195
  num_beams=4,
 
 
196
  early_stopping=True
197
  )
198
 
199
  # Decode output
200
  summary = tokenizer.decode(summary_ids[0], skip_special_tokens=True)
 
 
 
 
201
  return summary
202
  except Exception as e:
203
  print(f"Error generating summary: {e}")
@@ -206,10 +191,17 @@ def generate_summary(text: str) -> str:
206
 
207
  def summarize_long_text(text: str) -> list[str]:
208
  """
209
- Tóm tắt văn bản dài bằng cách chia nhỏ và tóm tắt từng phần.
210
  """
211
- # Chia văn bản thành các chunk 700 từ
212
- chunks = chunk_text_by_words(text, max_words=700)
 
 
 
 
 
 
 
213
 
214
  summaries = []
215
  for i, chunk in enumerate(chunks):
@@ -221,40 +213,10 @@ def summarize_long_text(text: str) -> list[str]:
221
  return summaries
222
 
223
 
224
- def extract_text_from_url(url: str) -> str:
225
- """
226
- Trích xuất văn bản từ URL báo chí sử dụng newspaper3k.
227
- """
228
- if not NEWSPAPER_AVAILABLE:
229
- raise HTTPException(
230
- status_code=400,
231
- detail="newspaper3k không được cài đặt. Vui lòng gửi text trực tiếp."
232
- )
233
-
234
- try:
235
- article = Article(url, language='vi')
236
- article.download()
237
- article.parse()
238
-
239
- text = article.text
240
- if not text:
241
- raise HTTPException(
242
- status_code=400,
243
- detail="Không thể trích xuất văn bản từ URL."
244
- )
245
-
246
- return text
247
- except Exception as e:
248
- raise HTTPException(
249
- status_code=400,
250
- detail=f"Lỗi khi trích xuất URL: {str(e)}"
251
- )
252
-
253
-
254
  def extract_text_from_pdf_bytes(pdf_bytes: bytes) -> str:
255
  """
256
  Đọc PDF từ byte stream sử dụng PyMuPDF.
257
- Không lưu file ra ổ cứng.
258
  """
259
  try:
260
  # Mở PDF từ byte stream
@@ -278,6 +240,8 @@ def extract_text_from_pdf_bytes(pdf_bytes: bytes) -> str:
278
  )
279
 
280
  return full_text
 
 
281
  except Exception as e:
282
  raise HTTPException(
283
  status_code=400,
@@ -308,25 +272,12 @@ async def health_check():
308
  @app.post("/summarize")
309
  async def summarize_text(request: SummarizeRequest):
310
  """
311
- Tóm tắt văn bản hoặc URL báo chí.
312
-
313
- - Gửi `text` để tóm tắt văn bản trực tiếp
314
- - Gửi `url` để trích xuất và tóm tắt bài báo
315
  """
316
- # Validate input
317
- if not request.text and not request.url:
318
- raise HTTPException(
319
- status_code=400,
320
- detail="Vui lòng cung cấp 'text' hoặc 'url'."
321
- )
322
 
323
- # Get text from URL or use provided text
324
- if request.url:
325
- text = extract_text_from_url(request.url)
326
- else:
327
- text = request.text
328
-
329
- # Validate text length
330
  if not text or len(text.strip()) < 50:
331
  raise HTTPException(
332
  status_code=400,
@@ -348,8 +299,9 @@ async def summarize_text(request: SummarizeRequest):
348
  return {
349
  "success": True,
350
  "original_length": len(text),
 
351
  "num_chunks": len(summaries),
352
- "bullet_points": bullet_points
353
  }
354
 
355
 
@@ -357,9 +309,8 @@ async def summarize_text(request: SummarizeRequest):
357
  async def upload_pdf(file: UploadFile = File(...)):
358
  """
359
  Upload và tóm tắt file PDF.
360
-
361
- - Đọc trực tiếp từ byte stream, không lưu file ra ổ cứng
362
- - Hỗ trợ file PDF có text (không hỗ trợ ảnh scan)
363
  """
364
  # Validate file type
365
  if not file.filename.lower().endswith('.pdf'):
@@ -368,10 +319,10 @@ async def upload_pdf(file: UploadFile = File(...)):
368
  detail="Chỉ hỗ trợ file PDF."
369
  )
370
 
371
- # Read file content directly from byte stream
372
- pdf_bytes = await file.read()
373
 
374
- if len(pdf_bytes) == 0:
375
  raise HTTPException(
376
  status_code=400,
377
  detail="File rỗng."
@@ -379,14 +330,14 @@ async def upload_pdf(file: UploadFile = File(...)):
379
 
380
  # Limit file size (10MB max)
381
  max_size = 10 * 1024 * 1024 # 10MB
382
- if len(pdf_bytes) > max_size:
383
  raise HTTPException(
384
  status_code=400,
385
  detail="File quá lớn. Giới hạn 10MB."
386
  )
387
 
388
- # Extract text from PDF bytes
389
- text = extract_text_from_pdf_bytes(pdf_bytes)
390
 
391
  # Validate extracted text
392
  if len(text.strip()) < 50:
@@ -411,8 +362,9 @@ async def upload_pdf(file: UploadFile = File(...)):
411
  "success": True,
412
  "filename": file.filename,
413
  "original_length": len(text),
 
414
  "num_chunks": len(summaries),
415
- "bullet_points": bullet_points
416
  }
417
 
418
 
 
4
  """
5
 
6
  import re
 
7
  from typing import Optional
8
  from fastapi import FastAPI, File, UploadFile, HTTPException
9
  from fastapi.middleware.cors import CORSMiddleware
 
12
  import torch
13
  import fitz # PyMuPDF
14
 
 
 
 
 
 
 
 
15
  # ============================================================
16
  # Initialize FastAPI App
17
  # ============================================================
 
21
  version="1.0.0"
22
  )
23
 
24
+ # CORS middleware - Allow All Origins for GitHub Pages
25
  app.add_middleware(
26
  CORSMiddleware,
27
  allow_origins=["*"],
 
45
  # Request Models
46
  # ============================================================
47
  class SummarizeRequest(BaseModel):
48
+ text: str
 
49
 
50
 
51
  # ============================================================
52
  # Helper Functions
53
  # ============================================================
54
 
55
+ def chunk_text_by_words(text: str, max_words: int = 800) -> list[str]:
56
  """
57
  Chia văn bản thành các đoạn tối đa max_words từ.
58
  Giữ nguyên câu hoàn chỉnh khi có thể.
 
73
 
74
  # Nếu câu đơn lẻ dài hơn max_words, chia nhỏ câu đó
75
  if sentence_word_count > max_words:
 
76
  if current_chunk:
77
  chunks.append(' '.join(current_chunk))
78
  current_chunk = []
79
  current_word_count = 0
80
 
 
81
  for i in range(0, sentence_word_count, max_words):
82
  chunk_words = sentence_words[i:i + max_words]
83
  chunks.append(' '.join(chunk_words))
84
 
85
  # Nếu thêm câu này vượt quá giới hạn
86
  elif current_word_count + sentence_word_count > max_words:
 
87
  if current_chunk:
88
  chunks.append(' '.join(current_chunk))
 
89
  current_chunk = [sentence]
90
  current_word_count = sentence_word_count
91
 
 
100
  return chunks
101
 
102
 
103
+ def fix_truncated_text(text: str) -> str:
104
  """
105
+ Nếu kết quả không kết thúc bằng dấu câu,
106
+ tự động cắt đến dấu chấm gần nhất.
 
107
  """
108
  text = text.strip()
109
 
110
  if not text:
111
  return text
112
 
113
+ # Nếu đã kết thúc bằng dấu câu, trả về nguyên
114
  if text[-1] in '.!?':
115
  return text
116
 
117
+ # Tìm dấu câu gần nhất
118
+ last_period = text.rfind('.')
119
+ last_exclaim = text.rfind('!')
120
+ last_question = text.rfind('?')
121
+
122
+ last_sentence_end = max(last_period, last_exclaim, last_question)
123
 
124
  if last_sentence_end > 0:
125
+ # Cắt đến dấu câu gần nhất
126
+ return text[:last_sentence_end + 1]
 
 
 
 
 
 
 
127
 
128
  # Nếu không có dấu câu nào, thêm dấu chấm
129
  return text + '.'
130
 
131
 
132
+ def format_as_bullet_points(summaries: list[str]) -> str:
133
  """
134
+ Chuyển đổi các đoạn tóm tắt thành Bullet Points.
135
+ Mỗi ý một dòng, bắt đầu bằng '•'.
136
  """
137
  bullet_points = []
138
 
 
142
 
143
  for sentence in sentences:
144
  sentence = sentence.strip()
145
+ if sentence and len(sentence) > 15: # Bỏ qua câu quá ng��n
146
  # Đảm bảo câu kết thúc đúng
147
+ sentence = fix_truncated_text(sentence)
148
+ bullet_points.append(f"• {sentence}")
149
 
150
+ return '\n'.join(bullet_points)
151
 
152
 
153
  def generate_summary(text: str) -> str:
154
  """
155
+ Sinh tóm tắt với torch.no_grad() để tiết kiệm RAM.
156
+ Tham số: max_length=350, min_length=100, num_beams=4, repetition_penalty=2.5
157
  """
158
  try:
159
  # Tokenize input
 
164
  return_tensors="pt"
165
  )
166
 
167
+ # Generate với torch.no_grad() để tiết kiệm RAM
168
  with torch.no_grad():
169
  summary_ids = model.generate(
170
  inputs["input_ids"],
171
  attention_mask=inputs["attention_mask"],
172
+ max_length=350,
173
  min_length=100,
 
 
174
  num_beams=4,
175
+ repetition_penalty=2.5,
176
+ no_repeat_ngram_size=3,
177
  early_stopping=True
178
  )
179
 
180
  # Decode output
181
  summary = tokenizer.decode(summary_ids[0], skip_special_tokens=True)
182
+
183
+ # Fix truncated text
184
+ summary = fix_truncated_text(summary)
185
+
186
  return summary
187
  except Exception as e:
188
  print(f"Error generating summary: {e}")
 
191
 
192
  def summarize_long_text(text: str) -> list[str]:
193
  """
194
+ Nếu văn bản > 800 từ, chia nhỏ và tóm tắt từng phần.
195
  """
196
+ word_count = len(text.split())
197
+
198
+ # Nếu văn bản ngắn, tóm tắt trực tiếp
199
+ if word_count <= 800:
200
+ summary = generate_summary(text)
201
+ return [summary] if summary else []
202
+
203
+ # Chia nhỏ văn bản dài
204
+ chunks = chunk_text_by_words(text, max_words=800)
205
 
206
  summaries = []
207
  for i, chunk in enumerate(chunks):
 
213
  return summaries
214
 
215
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
  def extract_text_from_pdf_bytes(pdf_bytes: bytes) -> str:
217
  """
218
  Đọc PDF từ byte stream sử dụng PyMuPDF.
219
+ KHÔNG lưu file ra đĩa.
220
  """
221
  try:
222
  # Mở PDF từ byte stream
 
240
  )
241
 
242
  return full_text
243
+ except HTTPException:
244
+ raise
245
  except Exception as e:
246
  raise HTTPException(
247
  status_code=400,
 
272
  @app.post("/summarize")
273
  async def summarize_text(request: SummarizeRequest):
274
  """
275
+ Tóm tắt văn bản tiếng Việt.
276
+ Trả về danh sách Bullet Points.
 
 
277
  """
278
+ text = request.text
 
 
 
 
 
279
 
280
+ # Validate text
 
 
 
 
 
 
281
  if not text or len(text.strip()) < 50:
282
  raise HTTPException(
283
  status_code=400,
 
299
  return {
300
  "success": True,
301
  "original_length": len(text),
302
+ "word_count": len(text.split()),
303
  "num_chunks": len(summaries),
304
+ "summary": bullet_points
305
  }
306
 
307
 
 
309
  async def upload_pdf(file: UploadFile = File(...)):
310
  """
311
  Upload và tóm tắt file PDF.
312
+ Đọc qua byte stream, KHÔNG lưu file ra đĩa.
313
+ Trả về danh sách Bullet Points.
 
314
  """
315
  # Validate file type
316
  if not file.filename.lower().endswith('.pdf'):
 
319
  detail="Chỉ hỗ trợ file PDF."
320
  )
321
 
322
+ # Đọc file qua contents = await file.read()
323
+ contents = await file.read()
324
 
325
+ if len(contents) == 0:
326
  raise HTTPException(
327
  status_code=400,
328
  detail="File rỗng."
 
330
 
331
  # Limit file size (10MB max)
332
  max_size = 10 * 1024 * 1024 # 10MB
333
+ if len(contents) > max_size:
334
  raise HTTPException(
335
  status_code=400,
336
  detail="File quá lớn. Giới hạn 10MB."
337
  )
338
 
339
+ # Extract text from PDF bytes (dùng fitz.open(stream=contents, filetype='pdf'))
340
+ text = extract_text_from_pdf_bytes(contents)
341
 
342
  # Validate extracted text
343
  if len(text.strip()) < 50:
 
362
  "success": True,
363
  "filename": file.filename,
364
  "original_length": len(text),
365
+ "word_count": len(text.split()),
366
  "num_chunks": len(summaries),
367
+ "summary": bullet_points
368
  }
369
 
370