Commit 245d84f
Changed files (1)
src
src/ai/summary.py
@@ -4,6 +4,7 @@ import base64
import hashlib
import json
import re
+import zlib
from pathlib import Path
from loguru import logger
@@ -22,21 +23,50 @@ from database.r2 import set_cf_r2
from messages.help import social_media_help
from messages.sender import send2tg
from messages.utils import equal_prefix, set_reaction, startswith_prefix
-from networking import download_file
+from networking import download_file, shorten_url
from utils import count_subtitles, rand_number
+MERMAID_TEMPLATE = """
+graph LR
+ A[核心主题] --> B[子标题1]
+ A --> C[子标题2]
+ A --> D[子标题3]
+ A --> E[子标题4]
+
+
+ B --> B1[二级标题1-1]
+ B --> B2[二级标题1-2]
+ B1 --> B11[核心观点1-1-1]
+ B1 --> B12[核心观点1-1-2]
+ B2 --> B21[争议点1-2-1]
+ B2 --> B22[争议点1-2-2]
+
+
+ C --> C1[关键数据2-1]
+ C --> C2[主要结论2-1]
+ C --> C3[补充结论2-2]
+
+ D --> D1[核心问题3-1]
+ D --> D2[潜在风险3-2]
+ D --> D3[影响因素3-3]
+
+ E --> E1[发展趋势4-1]
+ E --> E2[行动建议4-2]
+ E --> E3[未来结论4-3]
+""".strip()
+
JSON_SCHEMA = {
- "title": "Content Summary",
- "description": "提炼出资料的核心内容,生成符合指定JSON格式的全文总结、分片内容和思维导图",
+ "title": "Content Extraction",
+ "description": "精准提炼资料的核心主题、关键观点、主要结论及各片段核心内容,确保输出内容全面覆盖资料的关键信息,用户仅通过总结即可掌握信息全貌。",
"type": "object",
"properties": {
- "abstract": {
+ "overview": {
"title": "全文总结",
- "description": "需涵盖资料核心主题、关键观点和主要结论,用连贯的一段话概括资料的主要内容,避免过于简略。如果内容过长,也可考虑分段总结。",
+ "description": "需涵盖资料核心主题、关键观点和主要结论,采用连贯语言表述,若内容复杂可分段,但需逻辑清晰。禁止过于简略(如仅用一句话概括长文档),确保信息密度足够支撑用户理解。",
"type": "string",
},
"sections": {
- "description": "将资料划分为不同的片段,每个片段需拟定简洁准确的标题,匹配1个相关emoji,并总结该片段的核心内容",
+ "description": "需将文档划分为逻辑连贯的片段(如按章节、主题、时间线划分);每个片段需拟定**简洁准确**的标题(体现片段核心)、匹配1个相关emoji;并说明该片段的核心内容。",
"title": "分片内容",
"type": "array",
"items": {
@@ -44,10 +74,10 @@ JSON_SCHEMA = {
"properties": {
"title": {"type": "string", "description": "该片段的标题"},
"emoji": {"type": "string", "description": "匹配该片段的emoji,例如💡、💰、⚠️等"},
- "summary": {"type": "string", "description": "概括该片段的核心内容"},
+ "content": {"type": "string", "description": "详细说明该片段的核心事件、具体观点或结论,禁止仅用1-2句话泛泛概括,需传递足够细节。"},
"start": {
"type": ["string", "null"],
- "description": "如果资料内容为包含时间戳的文字稿(如播客、视频、音频的转录稿),设置此字段为该片段的开始时间, 格式为(HH:MM:SS或MM:SS)。如果没有时间戳,则无需输出此字段。",
+ "description": "如果资料为含时间戳的文字稿(如播客/视频/音频的转录稿),需补充start字段HH:MM:SS或MM:SS;无时间戳则无需输出start字段。",
},
},
},
@@ -55,11 +85,11 @@ JSON_SCHEMA = {
"mermaid": {
"title": "思维导图",
"type": "string",
- "pattern": "^flowchart LR",
- "description": "以Mermaid flowchart格式表示的全文思维导图,以'flowchart LR'开头",
+ "pattern": "^graph LR",
+ "description": f"以Mermaid graph格式表示的全文思维导图,以'graph LR'开头。需清晰呈现文档的逻辑结构(如核心主题→子主题→关键观点/结论),节点层级明确,便于用户快速梳理文档框架。一个示例Mermaid代码如下:\n{MERMAID_TEMPLATE}",
},
},
- "required": ["abstract", "sections", "mermaid"],
+ "required": ["overview", "sections", "mermaid"],
"additionalProperties": False,
}
@@ -90,14 +120,13 @@ async def ai_summary(client: Client, message: Message, summary_model_id: str = A
res = await openai_responses_api(client, message, **params)
if not res.get("texts"):
continue
- texts, mermaid_path = await parse_summary(res["texts"])
+ texts, _, mermaid_path = await parse_summary(res["texts"])
media = [{"photo": mermaid_path}] if Path(mermaid_path).is_file() else []
await send2tg(client, message, texts=texts, media=media, **kwargs)
await set_reaction(client, this_msg, "")
return
-
async def summarize(article: str, reference: str | None = None, model: str = "gemini") -> dict:
if count_subtitles(article) < 200: # skip short article
return {}
@@ -112,43 +141,45 @@ async def summarize(article: str, reference: str | None = None, model: str = "ge
)
if not res.get("texts", ""):
return {}
- texts, _ = await parse_summary(res["texts"])
+ texts, mermaid_url, mermaid_path = await parse_summary(res["texts"])
res["texts"] = texts
+ res["mermaid_url"] = mermaid_url
+ res["mermaid_path"] = mermaid_path
return res
-async def parse_summary(texts: str) -> tuple[str, str]:
+async def parse_summary(texts: str) -> tuple[str, str, str]:
"""Parse the summary JSON string.
Returns:
- (summary_texts, mermaid_img_path)
+ (summary_texts, mermaid_url, mermaid_path)
"""
try:
summary = json.loads(texts)
mermaid = beautify_mermaid(summary["mermaid"])
- mermaid_url, mermaid_path = await save_mermaid_jpg_to_r2(mermaid)
- parsed = f"{summary['abstract'].strip()}"
- if mermaid_url:
- logger.success(f"Mermaid: {mermaid_url}")
- parsed += f"\n🧠**[思维导图]({mermaid_url})**\n"
+ img_url, pako_url, mermaid_path = await publish_mermaid(mermaid)
+ parsed = f"{summary['overview'].strip()}"
+ if img_url:
+ logger.success(f"Mermaid: {pako_url}")
+ parsed += f"\n🧠**[思维导图]({pako_url})**\n"
parsed += "\n⚡️**章节速览**"
for section in summary["sections"]:
parsed += f"\n{section['emoji']}**{section['title']}**"
if section.get("start"):
parsed += f" [{section['start']}]"
- parsed += f"\n{section['summary']}"
+ parsed += f"\n{section['content']}"
logger.success(parsed)
except Exception as e:
logger.error(f"Error parsing summary: {e}")
- return texts, ""
- return parsed, mermaid_path
+ return texts, "", ""
+ return parsed, img_url, mermaid_path
def system_prompt(reference: str | None = None) -> str:
- prompt = "你是一位专业的内容总结大师,任务是基于用户提供的资料提炼出核心内容,生成符合指定JSON格式的全文总结、分片内容和思维导图。"
+ prompt = f"你是一位专业的内容提炼大师,任务是基于用户提供的资料,生成用户无需阅读完整原文档就能清晰理解主要事件、观点、结论的内容,生成符合指定JSON格式的全文总结、分片内容和思维导图。思维导图Mermaid语法说明文档:{mermaid_syntax()}"
if reference:
prompt += f"\n{reference}"
- return prompt.strip() + mermaid_syntax()
+ return prompt.strip()
def beautify_mermaid(mermaid: str) -> str:
@@ -171,20 +202,29 @@ def beautify_mermaid(mermaid: str) -> str:
return f"---\nconfig:\n theme: neo\n look: neo\n---\n{mermaid.strip()}"
-async def save_mermaid_jpg_to_r2(mermaid: str) -> tuple[str, str]:
+async def publish_mermaid(mermaid: str) -> tuple[str, str, str]:
"""Save Mermaid image to R2.
Returns:
- (image_url, local_path)
+ (image_url, pako_url, local_path)
"""
b64_str = base64.urlsafe_b64encode(mermaid.encode("utf-8")).decode("ascii")
- save_path = Path(DOWNLOAD_DIR) / f"{hashlib.sha256(mermaid.encode()).hexdigest()}.jpg"
+ save_path = Path(DOWNLOAD_DIR) / f"{hashlib.md5(mermaid.encode()).hexdigest()}.jpg" # noqa: S324
+ r2_key = f"TTL/365d/{save_path.name}"
+ img_url = f"{DB.CF_R2_PUBLIC_URL}/{r2_key}"
+ img_url = await shorten_url(img_url)
await download_file(f"https://mermaid.ink/img/{b64_str}?type=jpeg&theme=forest&width=2160", path=save_path, suffix=".jpg")
+ mermaid = mermaid.replace("\ngraph LR", f"\n%% {img_url}\ngraph LR")
+ # generate pako url for mermaid image
+ json_str = json.dumps({"code": mermaid.strip()}, separators=(",", ":"))
+ compressed_bytes = zlib.compress(json_str.encode("utf-8"), level=9)
+ pako_b64_str = base64.urlsafe_b64encode(compressed_bytes).decode("utf-8").rstrip("=")
+ pako_url = await shorten_url(f"https://mermaid.live/view#pako:{pako_b64_str}")
+
if save_path.is_file():
- r2_key = f"TTL/365d/{save_path.name}"
await set_cf_r2(r2_key, data=save_path.read_bytes(), mime_type="image/jpeg", silent=True)
- return f"{DB.CF_R2_PUBLIC_URL}/{r2_key}", save_path.as_posix()
- return "", ""
+ return img_url, pako_url, save_path.as_posix()
+ return "", "", ""
def summary_params(reference: str | None = None) -> dict:
@@ -199,9 +239,9 @@ def summary_params(reference: str | None = None) -> dict:
"text": {
"format": {
"type": "json_schema",
- "name": "ContentSummary",
+ "name": "ContentExtraction",
"strict": True,
- "description": "提炼出资料的核心内容,生成符合指定JSON格式的全文总结、分片内容和思维导图",
+ "description": "精准提炼资料的核心主题、关键观点、主要结论及各片段核心内容,确保输出内容全面覆盖资料的关键信息,用户仅通过总结即可掌握信息全貌。",
"schema": JSON_SCHEMA,
}
},
@@ -215,14 +255,14 @@ def summary_params(reference: str | None = None) -> dict:
def mermaid_syntax() -> str:
return """
-# Mermaid Flowcharts - Basic Syntax
+# Mermaid Graph - Basic Syntax
-Flowcharts are composed of **nodes** (geometric shapes) and **edges** (arrows or lines). The Mermaid code defines how nodes and edges are made and accommodates different arrow types, multi-directional arrows, and any linking to and from subgraphs.
+Graph is composed of **nodes** (geometric shapes) and **edges** (arrows or lines). The Mermaid code defines how nodes and edges are made and accommodates different arrow types, multi-directional arrows, and any linking to and from subgraphs.
-### A node (default)
+## A node (default)
```mermaid
-flowchart LR
+graph LR
id
```
@@ -237,7 +277,7 @@ found for the node that will be used. Also if you define edges for the node late
one previously defined will be used when rendering the box.
```mermaid
-flowchart LR
+graph LR
id1[This is the text in the box]
```
@@ -246,45 +286,10 @@ flowchart LR
### A node with round edges
```mermaid
-flowchart LR
+graph LR
id1(This is the text in the box)
```
-### A stadium-shaped node
-
-```mermaid
-flowchart LR
- id1([This is the text in the box])
-```
-
-### A node in a subroutine shape
-
-```mermaid
-flowchart LR
- id1[[This is the text in the box]]
-```
-
-### A node in a cylindrical shape
-
-```mermaid
-flowchart LR
- id1[(Database)]
-```
-
-### A node in the form of a circle
-
-```mermaid
-flowchart LR
- id1((This is the text in the circle))
-```
-
-### A node in an asymmetric shape
-
-```mermaid
-flowchart LR
- id1>This is the text in the box]
-```
-
## Links between nodes
Nodes can be connected with links/edges. It is possible to have different types of links or attach a text string to a link.
@@ -292,136 +297,56 @@ Nodes can be connected with links/edges. It is possible to have different types
### A link with arrow head
```mermaid
-flowchart LR
+graph LR
A-->B
```
### An open link
```mermaid
-flowchart LR
+graph LR
A --- B
```
### Text on links
```mermaid
-flowchart LR
- A-- This is the text! ---B
-```
-
-or
-
-```mermaid
-flowchart LR
+graph LR
A---|This is the text|B
```
### A link with arrow head and text
```mermaid
-flowchart LR
+graph LR
A-->|text|B
```
-or
-
-```mermaid
-flowchart LR
- A-- text -->B
-```
-
### Dotted link
```mermaid
-flowchart LR
+graph LR
A-.->B;
```
### Dotted link with text
```mermaid
-flowchart LR
+graph LR
A-. text .-> B
```
### Thick link
```mermaid
-flowchart LR
+graph LR
A ==> B
```
### Thick link with text
```mermaid
-flowchart LR
+graph LR
A == text ==> B
```
-
-### An invisible link
-
-This can be a useful tool in some instances where you want to alter the default positioning of a node.
-
-```mermaid
-flowchart LR
- A ~~~ B
-```
-
-### Chaining of links
-
-It is possible declare many links in the same line as per below:
-
-```mermaid
-flowchart LR
- A -- text --> B -- text2 --> C
-```
-
-It is also possible to declare multiple nodes links in the same line as per below:
-
-```mermaid
-flowchart LR
- a --> b & c--> d
-```
-
-You can then describe dependencies in a very expressive way. Like the one-liner below:
-
-```mermaid
-flowchart TB
- A & B--> C & D
-```
-
-If you describe the same diagram using the basic syntax, it will take four lines. A
-word of warning, one could go overboard with this making the flowchart harder to read in
-markdown form. The Swedish word `lagom` comes to mind. It means, not too much and not too little.
-This goes for expressive syntaxes as well.
-
-```mermaid
-flowchart TB
- A --> C
- A --> D
- B --> C
- B --> D
-```
-
-## New arrow types
-
-There are new types of arrows supported:
-
-- circle edge
-- cross edge
-
-### Circle edge example
-
-```mermaid
-flowchart LR
- A --o B
-```
-
-### Cross edge example
-
-```mermaid
-flowchart LR
- A --x B
-```
"""