feat: add class share drafts

This commit is contained in:
2026-07-31 16:07:18 +08:00
parent d8fde93b69
commit 429636f3e8
15 changed files with 410 additions and 5 deletions

View File

@@ -0,0 +1,113 @@
from __future__ import annotations
from datetime import UTC, datetime
from fastapi import HTTPException, status
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.models.chat import ChatSession, TopicSession
from app.models.growth import ShareDraft, TopicSummary
from app.models.user import User
from app.services.entitlement_service import EntitlementService
from app.services.growth_profile_service import GrowthProfileService, topic_summary_dict
class ShareDraftService:
@staticmethod
def generate_for_session(db: Session, *, user: User, session: ChatSession) -> ShareDraft:
entitlement = EntitlementService.active_entitlement(db, user)
if not entitlement.allow_share_draft:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="当前权益暂不支持生成班级分享稿")
topic = _latest_topic(db, user=user, session=session)
if topic is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="当前会话还没有可生成分享稿的主题")
summary = GrowthProfileService.generate_topic_summary(db, user=user, topic=topic)
draft = ShareDraft(
user_id=user.id,
topic_session_id=topic.id,
summary_id=summary.id,
content=_render_share_draft(topic=topic, summary=summary),
source="topic_summary",
)
topic.share_draft_generated = 1
db.add_all([topic, draft])
db.commit()
db.refresh(draft)
return draft
@staticmethod
def list_user_drafts(db: Session, *, user: User, limit: int = 20) -> list[ShareDraft]:
return list(
db.scalars(
select(ShareDraft)
.where(ShareDraft.user_id == user.id)
.order_by(ShareDraft.created_at.desc(), ShareDraft.id.desc())
.limit(limit)
)
)
@staticmethod
def mark_copied(db: Session, *, user: User, draft_id: int) -> ShareDraft:
draft = db.scalar(select(ShareDraft).where(ShareDraft.id == draft_id, ShareDraft.user_id == user.id))
if draft is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="分享稿不存在")
draft.copied = 1
draft.copied_at = _now()
db.add(draft)
db.commit()
db.refresh(draft)
return draft
def share_draft_dict(draft: ShareDraft) -> dict:
return {
"id": draft.id,
"userId": draft.user_id,
"topicSessionId": draft.topic_session_id,
"summaryId": draft.summary_id,
"content": draft.content,
"source": draft.source,
"copied": bool(draft.copied),
"copiedAt": draft.copied_at,
"createdAt": draft.created_at,
}
def _latest_topic(db: Session, *, user: User, session: ChatSession) -> TopicSession | None:
return db.scalar(
select(TopicSession)
.where(TopicSession.user_id == user.id, TopicSession.chat_session_id == session.id)
.order_by(TopicSession.updated_at.desc(), TopicSession.id.desc())
.limit(1)
)
def _render_share_draft(*, topic: TopicSession, summary: TopicSummary) -> str:
data = topic_summary_dict(summary)
return (
"【实修分享稿草稿】\n"
"说明:这是根据我本次对话整理出的分享草稿,系统不会自动发送到任何群,"
"我会按真实情况删改后再决定是否发到班级群。\n\n"
"大家好,我想分享一下这次实修里看到的一点东西。\n\n"
"1. 我这次观察到的议题\n"
f"{topic.core_question or topic.title}\n\n"
"2. 我看见了什么\n"
f"{data.get('summary') or '(请用自己的话补充真实看见)'}\n\n"
"3. 我感受到的情绪和身体反应\n"
f"情绪:{data.get('emotions') or '(请补充)'}\n"
f"身体:{data.get('bodyFeelings') or '(请补充)'}\n\n"
"4. 我做了什么功课 / 准备继续做什么\n"
f"{data.get('recommendedHomework') or '(请补充)'}\n\n"
"5. 当下的一点变化\n"
f"{data.get('insights') or '(请补充真实变化,不需要夸大)'}\n\n"
"6. 我还在继续观察的方向\n"
f"{data.get('nextObservation') or '(请补充)'}\n\n"
"备注:这只是我的阶段性观察,不代表已经彻底解决,也不是建议别人照搬。"
)
def _now() -> datetime:
return datetime.now(UTC).replace(tzinfo=None)