118 lines
3.7 KiB
Python
118 lines
3.7 KiB
Python
import re
|
||
from typing import Any, Dict, List, Optional
|
||
|
||
|
||
_MARKDOWN_PREFIX_RE = re.compile(r"^\s{0,3}(?:[#>*-]+|\d+[.)])\s*")
|
||
_TABLE_LINE_RE = re.compile(r"^\s*\|.*\|\s*$")
|
||
_SEPARATOR_LINE_RE = re.compile(r"^\s*[-=:_`~]{3,}\s*$")
|
||
|
||
|
||
def _clean_topic_line(raw: Any) -> str:
|
||
text = str(raw or "").strip()
|
||
if not text:
|
||
return ""
|
||
if _SEPARATOR_LINE_RE.fullmatch(text):
|
||
return ""
|
||
if _TABLE_LINE_RE.fullmatch(text):
|
||
return ""
|
||
text = _MARKDOWN_PREFIX_RE.sub("", text).strip()
|
||
return text
|
||
|
||
|
||
def _clean_topic_lines(content: str) -> List[str]:
|
||
rows: List[str] = []
|
||
for line in str(content or "").splitlines():
|
||
cleaned = _clean_topic_line(line)
|
||
if cleaned:
|
||
rows.append(cleaned)
|
||
return rows
|
||
|
||
|
||
def _extract_highlights(content: str) -> List[str]:
|
||
rows: List[str] = []
|
||
for line in str(content or "").splitlines():
|
||
raw = str(line or "").strip()
|
||
if not raw:
|
||
continue
|
||
cleaned = _clean_topic_line(raw)
|
||
if not cleaned:
|
||
continue
|
||
if raw.lstrip().startswith(("-", "*")) or ":" in cleaned or ":" in cleaned:
|
||
value = cleaned[:120]
|
||
if value not in rows:
|
||
rows.append(value)
|
||
if len(rows) >= 3:
|
||
break
|
||
return rows
|
||
|
||
|
||
def _unique_rows(rows: List[str]) -> List[str]:
|
||
deduped: List[str] = []
|
||
seen = set()
|
||
for row in rows:
|
||
value = str(row or "").strip()
|
||
if not value or value in seen:
|
||
continue
|
||
seen.add(value)
|
||
deduped.append(value)
|
||
return deduped
|
||
|
||
|
||
def _build_summary_card_view(title: str, content: str) -> Dict[str, Any]:
|
||
lines = _clean_topic_lines(content)
|
||
fallback_title = title or (lines[0] if lines else "")
|
||
summary_source = [line for line in lines if line != fallback_title]
|
||
narrative_lines = [
|
||
line for line in summary_source
|
||
if not line.startswith(("-", "*")) and ":" not in line and ":" not in line
|
||
]
|
||
summary = " ".join((narrative_lines or summary_source)[:2]).strip()
|
||
if not summary and lines:
|
||
summary = lines[0]
|
||
summary = summary[:220].strip()
|
||
highlights = _unique_rows(_extract_highlights(content))[:3]
|
||
snippet_source = _unique_rows(
|
||
[line for line in summary_source if line != summary and line not in highlights]
|
||
)
|
||
snippet = " ".join(snippet_source[:2]).strip()[:180].strip()
|
||
return {
|
||
"type": "summary_card",
|
||
"title": fallback_title[:120],
|
||
"summary": summary,
|
||
"highlights": highlights,
|
||
"snippet": snippet,
|
||
}
|
||
|
||
|
||
def build_topic_publish_payload(bot_id: str, packet: Dict[str, Any], message_id: Optional[int]) -> Optional[Dict[str, Any]]:
|
||
packet_type = str(packet.get("type") or "").strip().upper()
|
||
is_progress = bool(packet.get("is_progress"))
|
||
is_tool_hint = bool(packet.get("is_tool_hint"))
|
||
if packet_type == "BUS_EVENT" and is_progress:
|
||
return None
|
||
|
||
if packet_type == "BUS_EVENT":
|
||
content = str(packet.get("content") or packet.get("text") or "").strip()
|
||
else:
|
||
content = str(packet.get("text") or "").strip()
|
||
if not content:
|
||
return None
|
||
|
||
lines = _clean_topic_lines(content)
|
||
title = (lines[0] if lines else content[:120]).strip()
|
||
if len(title) > 120:
|
||
title = f"{title[:117].rstrip()}..."
|
||
|
||
source_channel = str(packet.get("channel") or "dashboard").strip().lower() or "dashboard"
|
||
dedupe_key = f"{bot_id}:message:{message_id}" if message_id else ""
|
||
return {
|
||
"title": title,
|
||
"content": content,
|
||
"level": "info",
|
||
"source": source_channel,
|
||
"dedupe_key": dedupe_key,
|
||
"is_progress": is_progress,
|
||
"is_tool_hint": is_tool_hint,
|
||
"view": _build_summary_card_view(title, content),
|
||
}
|