- Telegram collector (Telethon MTProto) with checkpoint-based message fetching - Claude API summarization via proxy (haiku model) - Dashboard page with date picker, markdown summary, raw message drill-down - Separate container lifecycle from dashboard for stable Telethon sessions - Shared SQLite DB mounted read-only into dashboard
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
import os
|
||||
import logging
|
||||
from datetime import datetime, timedelta, timezone
|
||||
import aiosqlite
|
||||
import httpx
|
||||
from db import DB_PATH
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
API_KEY = os.environ.get("ANTHROPIC_API_KEY", "")
|
||||
BASE_URL = os.environ.get("ANTHROPIC_BASE_URL", "https://www.bytecatcode.org")
|
||||
MODEL = "claude-haiku-4-5-20251001"
|
||||
CST = timezone(timedelta(hours=8))
|
||||
|
||||
PROMPT = """You are a chat group digest assistant. Summarize the following chat messages into a concise daily digest.
|
||||
|
||||
Rules:
|
||||
- Group by topic/theme, not by chat group
|
||||
- Highlight key decisions, shared links, and action items
|
||||
- Skip greetings, small talk, and low-value chatter
|
||||
- Use markdown formatting
|
||||
- Write in the same language as the messages (likely Chinese)
|
||||
- Be concise
|
||||
|
||||
Messages:
|
||||
{messages}"""
|
||||
|
||||
async def summarize(date_str: str = None):
|
||||
now = datetime.now(CST)
|
||||
if not date_str:
|
||||
date_str = now.strftime("%Y-%m-%d")
|
||||
|
||||
start = datetime.strptime(date_str, "%Y-%m-%d").replace(tzinfo=CST)
|
||||
end = start + timedelta(days=1)
|
||||
|
||||
async with aiosqlite.connect(DB_PATH) as db:
|
||||
cursor = await db.execute(
|
||||
"SELECT group_name, sender_name, text, timestamp FROM messages WHERE timestamp >= ? AND timestamp < ? ORDER BY timestamp",
|
||||
(start.isoformat(), end.isoformat())
|
||||
)
|
||||
rows = await cursor.fetchall()
|
||||
|
||||
if not rows:
|
||||
log.info("No messages for %s, skipping summary", date_str)
|
||||
return None
|
||||
|
||||
# Format messages for the prompt
|
||||
formatted = []
|
||||
for group_name, sender, text, ts in rows:
|
||||
formatted.append(f"[{group_name}] {sender}: {text}")
|
||||
messages_text = "\n".join(formatted)
|
||||
|
||||
# Call Claude API
|
||||
async with httpx.AsyncClient(timeout=120) as client:
|
||||
resp = await client.post(
|
||||
f"{BASE_URL}/v1/messages",
|
||||
headers={"x-api-key": API_KEY, "anthropic-version": "2023-06-01", "content-type": "application/json"},
|
||||
json={
|
||||
"model": MODEL,
|
||||
"max_tokens": 4096,
|
||||
"messages": [{"role": "user", "content": PROMPT.format(messages=messages_text)}]
|
||||
}
|
||||
)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
summary = data["content"][0]["text"]
|
||||
|
||||
# Store summary
|
||||
async with aiosqlite.connect(DB_PATH) as db:
|
||||
await db.execute(
|
||||
"INSERT OR REPLACE INTO summaries (date, content, created_at) VALUES (?,?,?)",
|
||||
(date_str, summary, now.isoformat())
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
log.info("Generated summary for %s (%d messages)", date_str, len(rows))
|
||||
return summary
|
||||
Reference in New Issue
Block a user