feat: AI 摘要持久化 — DB 缓存 + 自动加载

后端:
- 新增 ai_summaries 表 (week_start/week_end/generated_by/summary)
- get_cached_summary(): 按周+用户查缓存
- delete_cached_summary(): 生成前清理旧缓存
- generate_summary(): 自动保存到 DB
- API: GET /ai/summary(加载) POST(生成) DELETE(清除)

前端:
- onMounted 自动 GET 缓存摘要, 有则直接展示
- 按钮: 无缓存→'AI 生成摘要', 有缓存→'重新生成'
- 标题栏显示生成时间戳
This commit is contained in:
2026-06-25 11:45:04 +08:00
parent e681beba24
commit 5b38246801
6 changed files with 175 additions and 19 deletions
+83 -4
View File
@@ -5,9 +5,11 @@ import httpx
from app.config import settings
from app.services.dashboard import get_weekly_report, get_week_range
from app.services.light_board import get_light_board
from app.models.ai_summary import AISummary
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from uuid import UUID
from datetime import date
from datetime import date, datetime
SUMMARY_SYSTEM_PROMPT = """你是一位经验丰富的政企客户经理团队的周报分析助手。你的分析将被直接提交给支局长作为工作周报的文字摘要。
@@ -141,14 +143,70 @@ def build_summary_prompt(
return data_block
async def get_cached_summary(
db: AsyncSession,
user_id: UUID,
reference_date: date | None = None,
period: str = "week",
) -> dict | None:
"""Load a previously generated summary for this week/user."""
ref = reference_date or date.today()
monday, sunday = get_week_range(ref)
result = await db.execute(
select(AISummary)
.where(
AISummary.week_start == monday,
AISummary.period == period,
AISummary.generated_by == user_id,
)
.order_by(AISummary.created_at.desc())
.limit(1)
)
row = result.scalar()
if not row:
return None
return {
"summary": row.summary,
"week_start": str(row.week_start),
"week_end": str(row.week_end),
"period": row.period,
"created_at": str(row.created_at),
"cached": True,
}
async def delete_cached_summary(
db: AsyncSession,
user_id: UUID,
reference_date: date | None = None,
period: str = "week",
) -> bool:
"""Delete a cached summary so it can be regenerated."""
ref = reference_date or date.today()
monday, sunday = get_week_range(ref)
result = await db.execute(
select(AISummary).where(
AISummary.week_start == monday,
AISummary.period == period,
AISummary.generated_by == user_id,
)
)
rows = result.scalars().all()
for row in rows:
await db.delete(row)
if rows:
await db.commit()
return len(rows) > 0
async def generate_summary(
db: AsyncSession,
user_id: UUID,
role: str,
reference_date: date | None = None,
period: str = "week",
) -> str:
"""Generate an AI-powered weekly summary.
) -> dict:
"""Generate an AI-powered weekly summary, save to DB, return it.
Raises ValueError if AI config is missing, httpx.HTTPError on API failure.
"""
@@ -157,6 +215,7 @@ async def generate_summary(
# Gather data
ref = reference_date or date.today()
monday, sunday = get_week_range(ref)
weekly_report = await get_weekly_report(
db=db, user_id=user_id, role=role, reference_date=ref,
)
@@ -187,4 +246,24 @@ async def generate_summary(
if not content:
raise ValueError("AI returned empty response")
return content
# Delete old cached entry for this week/user, then save new
await delete_cached_summary(db, user_id, reference_date=ref, period=period)
row = AISummary(
week_start=monday,
week_end=sunday,
period=period,
generated_by=user_id,
role=role,
summary=content,
)
db.add(row)
await db.commit()
return {
"summary": content,
"week_start": str(monday),
"week_end": str(sunday),
"period": period,
"created_at": str(row.created_at),
"cached": False,
}