feat: add topic summaries and growth profiles

This commit is contained in:
2026-07-31 15:34:03 +08:00
parent 884dc765ad
commit 3e60a6da42
22 changed files with 1040 additions and 5 deletions

View File

@@ -15,11 +15,13 @@ from app.core.database import get_db
from app.core.dependencies import get_current_admin
from app.core.responses import api_success
from app.models.admin import Admin
from app.models.chat import ChatMessage, ChatSession
from app.models.chat import ChatMessage, ChatSession, TopicSession
from app.models.growth import TopicSummary
from app.models.logs import AiRequestLog, OperationLog
from app.models.user import User
from app.api.pagination import page_result
from app.services.question_insight_service import QuestionInsightService
from app.services.growth_profile_service import topic_dict, topic_summary_dict
router = APIRouter()
@@ -135,6 +137,7 @@ def chat_detail(
.where(AiRequestLog.session_id == session_id)
.order_by(AiRequestLog.created_at.asc(), AiRequestLog.id.asc())
).all()
topics = _topic_rows(db, session_id)
return api_success(
{
"session": _chat_row_dict(session, user),
@@ -146,6 +149,7 @@ def chat_detail(
page_size=messagePageSize,
),
"aiLogs": [_ai_log_dict(item, include_prompt=True) for item in ai_logs],
"topics": topics,
}
)
@@ -317,6 +321,22 @@ def _message_dict(message: ChatMessage) -> dict:
}
def _topic_rows(db: Session, session_id: int) -> list[dict]:
rows = db.execute(
select(TopicSession, TopicSummary)
.join(TopicSummary, TopicSummary.topic_session_id == TopicSession.id, isouter=True)
.where(TopicSession.chat_session_id == session_id)
.order_by(TopicSession.started_at.asc(), TopicSession.id.asc())
).all()
return [
{
**topic_dict(topic),
"summary": topic_summary_dict(summary) if summary else None,
}
for topic, summary in rows
]
def _ai_log_dict(log: AiRequestLog, *, include_prompt: bool = False, include_chunks: bool = True) -> dict:
data = {
"id": log.id,

View File

@@ -30,6 +30,7 @@ from app.services.chat_queue_runtime import (
)
from app.services.chat_queue_service import load_chat_queue_config
from app.services.chat_stream_service import ChatStreamService
from app.services.growth_profile_service import GrowthProfileService
from app.services.reasoning_policy_service import ReasoningPolicyService
router = APIRouter()
@@ -85,6 +86,16 @@ def delete_session(
return api_success()
@router.post("/session/{session_id}/topic/finish")
def finish_topic(
session_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
) -> dict:
session = ChatService._get_user_session(db, current_user, session_id)
return api_success(GrowthProfileService.finish_active_topic(db, user=current_user, session=session))
@router.post("/completions")
def completions(
payload: ChatCompletionRequest,

View File

@@ -9,6 +9,7 @@ from app.core.responses import api_success
from app.models.user import User
from app.schemas.user import UserProfile
from app.services.entitlement_service import EntitlementService, entitlement_dict
from app.services.growth_profile_service import GrowthProfileService, growth_profile_dict
from app.services.topic_session_service import TopicSessionService
router = APIRouter()
@@ -27,3 +28,28 @@ def profile(
)
data["entitlement"] = entitlement_dict(view)
return api_success(data)
@router.get("/growth-profile")
def growth_profile(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
) -> dict:
profile = GrowthProfileService.get_growth_profile(db, current_user.id)
summaries = GrowthProfileService.recent_topic_summaries(db, current_user.id, limit=10)
return api_success(
{
"profile": growth_profile_dict(profile),
"recentSummaries": [
{
"id": item.id,
"topicSessionId": item.topic_session_id,
"summary": item.summary,
"recommendedHomework": item.recommended_homework,
"nextObservation": item.next_observation,
"generatedAt": item.generated_at,
}
for item in summaries
],
}
)