Spaces:
Running
Running
Commit
·
a6d62e8
1
Parent(s):
2faaa56
Update logic generate mindmap, summary and nlp
Browse files- app/jobs/enrichment_job.py +38 -6
- app/services/mindmap_service.py +62 -29
- app/services/summary_service.py +47 -20
app/jobs/enrichment_job.py
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from app.services.storage import get_note, update_note
|
| 2 |
from app.services.summary_service import generate_summary
|
| 3 |
from app.services.mindmap_service import generate_mindmap
|
|
@@ -5,6 +28,7 @@ from app.services.mindmap_service import generate_mindmap
|
|
| 5 |
async def run_enrichment(note_id: str, tasks: list):
|
| 6 |
note = get_note(note_id)
|
| 7 |
if not note:
|
|
|
|
| 8 |
return
|
| 9 |
|
| 10 |
text = note.get("normalized_text") or note["raw_text"]
|
|
@@ -12,10 +36,18 @@ async def run_enrichment(note_id: str, tasks: list):
|
|
| 12 |
update_note(note_id, status="processing")
|
| 13 |
updates = {}
|
| 14 |
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
| 20 |
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# from app.services.storage import get_note, update_note
|
| 2 |
+
# from app.services.summary_service import generate_summary
|
| 3 |
+
# from app.services.mindmap_service import generate_mindmap
|
| 4 |
+
|
| 5 |
+
# async def run_enrichment(note_id: str, tasks: list):
|
| 6 |
+
# note = get_note(note_id)
|
| 7 |
+
# if not note:
|
| 8 |
+
# return
|
| 9 |
+
|
| 10 |
+
# text = note.get("normalized_text") or note["raw_text"]
|
| 11 |
+
|
| 12 |
+
# update_note(note_id, status="processing")
|
| 13 |
+
# updates = {}
|
| 14 |
+
|
| 15 |
+
# if "summary" in tasks:
|
| 16 |
+
# updates["summary"] = await generate_summary(text)
|
| 17 |
+
|
| 18 |
+
# if "mindmap" in tasks:
|
| 19 |
+
# updates["mindmap"] = await generate_mindmap(text)
|
| 20 |
+
|
| 21 |
+
# update_note(note_id, data=updates, status="ready")
|
| 22 |
+
|
| 23 |
+
import logging
|
| 24 |
from app.services.storage import get_note, update_note
|
| 25 |
from app.services.summary_service import generate_summary
|
| 26 |
from app.services.mindmap_service import generate_mindmap
|
|
|
|
| 28 |
async def run_enrichment(note_id: str, tasks: list):
|
| 29 |
note = get_note(note_id)
|
| 30 |
if not note:
|
| 31 |
+
logging.warning(f"[enrichment] Note not found: {note_id}")
|
| 32 |
return
|
| 33 |
|
| 34 |
text = note.get("normalized_text") or note["raw_text"]
|
|
|
|
| 36 |
update_note(note_id, status="processing")
|
| 37 |
updates = {}
|
| 38 |
|
| 39 |
+
try:
|
| 40 |
+
if "summary" in tasks:
|
| 41 |
+
try:
|
| 42 |
+
updates["summary"] = await generate_summary(text)
|
| 43 |
+
except Exception as e:
|
| 44 |
+
logging.exception(f"[enrichment] generate_summary failed for note_id={note_id}: {e}")
|
| 45 |
|
| 46 |
+
if "mindmap" in tasks:
|
| 47 |
+
try:
|
| 48 |
+
updates["mindmap"] = await generate_mindmap(text)
|
| 49 |
+
except Exception as e:
|
| 50 |
+
logging.exception(f"[enrichment] generate_mindmap failed for note_id={note_id}: {e}")
|
| 51 |
+
finally:
|
| 52 |
+
# Dù thành công hay thất bại, vẫn set status=ready để client ngừng poll
|
| 53 |
+
update_note(note_id, data=updates, status="ready")
|
app/services/mindmap_service.py
CHANGED
|
@@ -1,56 +1,89 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
from app.config import GEMINI_API_KEY
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
if GEMINI_API_KEY:
|
| 6 |
-
|
| 7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
else:
|
| 9 |
-
|
| 10 |
|
| 11 |
|
| 12 |
async def generate_mindmap(text: str) -> dict:
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
return {}
|
| 15 |
|
| 16 |
prompt = f"""
|
| 17 |
-
Bạn là chuyên gia tạo Sơ đồ tư duy. Hãy phân tích văn bản sau và tạo
|
|
|
|
| 18 |
Yêu cầu:
|
| 19 |
1. Xác định Ý chính làm Root.
|
| 20 |
2. Phân tách ý phụ thành nhánh con (tối đa 3 cấp).
|
| 21 |
3. Nhãn (label) ngắn gọn (< 7 từ).
|
| 22 |
-
4. Màu sắc (colorHex):
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
-
Cấu trúc JSON bắt buộc
|
| 25 |
{{
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
}}
|
| 38 |
|
| 39 |
Văn bản:
|
| 40 |
-
{text}
|
| 41 |
"""
|
| 42 |
|
| 43 |
loop = asyncio.get_event_loop()
|
| 44 |
|
| 45 |
def call():
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
|
|
|
| 50 |
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
|
| 56 |
return {}
|
|
|
|
| 1 |
+
# app/services/mindmap_service.py
|
| 2 |
+
import asyncio
|
| 3 |
+
import json
|
| 4 |
+
import logging
|
| 5 |
+
|
| 6 |
from app.config import GEMINI_API_KEY
|
| 7 |
+
|
| 8 |
+
import google.genai as genai
|
| 9 |
+
from google.api_core.exceptions import GoogleAPIError
|
| 10 |
+
|
| 11 |
+
_MINDMAP_MODEL = "gemini-1.5-flash"
|
| 12 |
+
_gemini_client = None
|
| 13 |
|
| 14 |
if GEMINI_API_KEY:
|
| 15 |
+
try:
|
| 16 |
+
_gemini_client = genai.Client(api_key=GEMINI_API_KEY)
|
| 17 |
+
logging.info(f"[mindmap_service] Initialized google.genai client with model={_MINDMAP_MODEL}")
|
| 18 |
+
except Exception as e:
|
| 19 |
+
logging.exception(f"[mindmap_service] Failed to init google.genai client: {e}")
|
| 20 |
+
_gemini_client = None
|
| 21 |
else:
|
| 22 |
+
logging.warning("[mindmap_service] GEMINI_API_KEY is not set, mindmap generation will be disabled")
|
| 23 |
|
| 24 |
|
| 25 |
async def generate_mindmap(text: str) -> dict:
|
| 26 |
+
"""
|
| 27 |
+
Sinh cấu trúc mindmap JSON từ văn bản.
|
| 28 |
+
Fallback: trả {} nếu không có model hoặc lỗi.
|
| 29 |
+
"""
|
| 30 |
+
if not _gemini_client:
|
| 31 |
return {}
|
| 32 |
|
| 33 |
prompt = f"""
|
| 34 |
+
Bạn là chuyên gia tạo Sơ đồ tư duy. Hãy phân tích văn bản sau và tạo CẤU TRÚC JSON Mindmap.
|
| 35 |
+
|
| 36 |
Yêu cầu:
|
| 37 |
1. Xác định Ý chính làm Root.
|
| 38 |
2. Phân tách ý phụ thành nhánh con (tối đa 3 cấp).
|
| 39 |
3. Nhãn (label) ngắn gọn (< 7 từ).
|
| 40 |
+
4. Màu sắc (colorHex):
|
| 41 |
+
- Root: "#6200EE"
|
| 42 |
+
- Các nhánh con: sử dụng một trong các màu: "#F59E2B", "#2ECF9A", "#2F9BFF"
|
| 43 |
+
5. CHỈ TRẢ VỀ JSON, không giải thích thêm.
|
| 44 |
|
| 45 |
+
Cấu trúc JSON bắt buộc:
|
| 46 |
{{
|
| 47 |
+
"root": {{
|
| 48 |
+
"label": "Chủ đề",
|
| 49 |
+
"colorHex": "#6200EE",
|
| 50 |
+
"children": [
|
| 51 |
+
{{
|
| 52 |
+
"label": "Ý 1",
|
| 53 |
+
"colorHex": "#F59E2B",
|
| 54 |
+
"children": []
|
| 55 |
+
}}
|
| 56 |
+
]
|
| 57 |
+
}}
|
| 58 |
}}
|
| 59 |
|
| 60 |
Văn bản:
|
| 61 |
+
\"\"\"{text}\"\"\"
|
| 62 |
"""
|
| 63 |
|
| 64 |
loop = asyncio.get_event_loop()
|
| 65 |
|
| 66 |
def call():
|
| 67 |
+
resp = _gemini_client.models.generate_content(
|
| 68 |
+
model=_MINDMAP_MODEL,
|
| 69 |
+
contents=prompt,
|
| 70 |
+
)
|
| 71 |
+
return resp.text or ""
|
| 72 |
|
| 73 |
+
try:
|
| 74 |
+
raw = await loop.run_in_executor(None, call)
|
| 75 |
+
start = raw.find("{")
|
| 76 |
+
end = raw.rfind("}")
|
| 77 |
+
if start != -1 and end != -1:
|
| 78 |
+
try:
|
| 79 |
+
return json.loads(raw[start:end + 1])
|
| 80 |
+
except Exception as e:
|
| 81 |
+
logging.warning(f"[mindmap_service] Failed to parse mindmap JSON: {e}")
|
| 82 |
+
else:
|
| 83 |
+
logging.warning("[mindmap_service] Mindmap response has no JSON block")
|
| 84 |
+
except GoogleAPIError as e:
|
| 85 |
+
logging.error(f"[mindmap_service] Gemini API error: {e}")
|
| 86 |
+
except Exception as e:
|
| 87 |
+
logging.exception(f"[mindmap_service] generate_mindmap failed: {e}")
|
| 88 |
|
| 89 |
return {}
|
app/services/summary_service.py
CHANGED
|
@@ -1,35 +1,62 @@
|
|
| 1 |
import asyncio
|
|
|
|
|
|
|
| 2 |
from app.config import GEMINI_API_KEY
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
if GEMINI_API_KEY:
|
| 6 |
-
|
| 7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
else:
|
| 9 |
-
|
| 10 |
|
| 11 |
|
| 12 |
async def generate_summary(text: str) -> str:
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
return ""
|
| 15 |
|
| 16 |
prompt = f"""
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
|
|
|
| 27 |
|
| 28 |
loop = asyncio.get_event_loop()
|
| 29 |
|
| 30 |
def call():
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import asyncio
|
| 2 |
+
import logging
|
| 3 |
+
|
| 4 |
from app.config import GEMINI_API_KEY
|
| 5 |
+
|
| 6 |
+
import google.genai as genai
|
| 7 |
+
from google.api_core.exceptions import GoogleAPIError
|
| 8 |
+
|
| 9 |
+
_SUMMARY_MODEL = "gemini-1.5-flash"
|
| 10 |
+
_gemini_client = None
|
| 11 |
|
| 12 |
if GEMINI_API_KEY:
|
| 13 |
+
try:
|
| 14 |
+
_gemini_client = genai.Client(api_key=GEMINI_API_KEY)
|
| 15 |
+
logging.info(f"[summary_service] Initialized google.genai client with model={_SUMMARY_MODEL}")
|
| 16 |
+
except Exception as e:
|
| 17 |
+
logging.exception(f"[summary_service] Failed to init google.genai client: {e}")
|
| 18 |
+
_gemini_client = None
|
| 19 |
else:
|
| 20 |
+
logging.warning("[summary_service] GEMINI_API_KEY is not set, summary will be empty")
|
| 21 |
|
| 22 |
|
| 23 |
async def generate_summary(text: str) -> str:
|
| 24 |
+
"""
|
| 25 |
+
Tạo tóm tắt ngắn gọn 3-5 câu, một đoạn văn duy nhất.
|
| 26 |
+
Fallback: trả "" nếu không có model hoặc lỗi.
|
| 27 |
+
"""
|
| 28 |
+
if not _gemini_client:
|
| 29 |
return ""
|
| 30 |
|
| 31 |
prompt = f"""
|
| 32 |
+
Bạn là chuyên gia tóm tắt. Hãy tóm tắt văn bản sau thành một đoạn văn duy nhất.
|
| 33 |
+
|
| 34 |
+
Yêu cầu:
|
| 35 |
+
1. Viết khoảng 3-5 câu, tổng hợp đầy đủ chủ đề và các ý chính.
|
| 36 |
+
2. Viết liền mạch, KHÔNG xuống dòng, KHÔNG dùng gạch đầu dòng hay đánh số.
|
| 37 |
+
3. Chỉ dựa trên thông tin được cung cấp, tuyệt đối KHÔNG tự thêm thông tin bên ngoài.
|
| 38 |
+
4. Trả về VĂN BẢN THUẦN (plain text), không bọc trong ``` hoặc JSON.
|
| 39 |
+
|
| 40 |
+
Văn bản:
|
| 41 |
+
\"\"\"{text}\"\"\"
|
| 42 |
+
"""
|
| 43 |
|
| 44 |
loop = asyncio.get_event_loop()
|
| 45 |
|
| 46 |
def call():
|
| 47 |
+
resp = _gemini_client.models.generate_content(
|
| 48 |
+
model=_SUMMARY_MODEL,
|
| 49 |
+
contents=prompt,
|
| 50 |
+
)
|
| 51 |
+
return (resp.text or "").strip()
|
| 52 |
+
|
| 53 |
+
try:
|
| 54 |
+
result = await loop.run_in_executor(None, call)
|
| 55 |
+
# clean backticks nếu model có lỡ bọc
|
| 56 |
+
return result.replace("```", "").strip()
|
| 57 |
+
except GoogleAPIError as e:
|
| 58 |
+
logging.error(f"[summary_service] Gemini API error: {e}")
|
| 59 |
+
except Exception as e:
|
| 60 |
+
logging.exception(f"[summary_service] generate_summary failed: {e}")
|
| 61 |
+
|
| 62 |
+
return ""
|