feat: isolate external application conversations
This commit is contained in:
@@ -7,12 +7,15 @@ from collections.abc import AsyncIterator
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from fastapi.responses import StreamingResponse
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.dependencies import get_current_user
|
||||
from app.core.auth_context import ChatAccessScope, UserAuthContext
|
||||
from app.core.dependencies import get_current_user_context
|
||||
from app.core.responses import api_success
|
||||
from app.models.user import User
|
||||
from app.models.chat import TopicSession
|
||||
from app.models.growth import TopicSummary
|
||||
from app.schemas.chat import (
|
||||
ChatCompletionRequest,
|
||||
ChatMessageRead,
|
||||
@@ -40,14 +43,14 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@router.post("/session")
|
||||
def create_session(db: Session = Depends(get_db), current_user: User = Depends(get_current_user)) -> dict:
|
||||
session = ChatService.create_session(db, current_user)
|
||||
def create_session(db: Session = Depends(get_db), current: UserAuthContext = Depends(get_current_user_context)) -> dict:
|
||||
session = ChatService.create_session(db, current.user, current.chat_scope)
|
||||
return api_success(CreateSessionResponse(sessionId=session.id).model_dump())
|
||||
|
||||
|
||||
@router.get("/session/list")
|
||||
def list_sessions(db: Session = Depends(get_db), current_user: User = Depends(get_current_user)) -> dict:
|
||||
sessions = ChatService.list_sessions(db, current_user)
|
||||
def list_sessions(db: Session = Depends(get_db), current: UserAuthContext = Depends(get_current_user_context)) -> dict:
|
||||
sessions = ChatService.list_sessions(db, current.user, current.chat_scope)
|
||||
return api_success([ChatSessionRead.model_validate(session).model_dump() for session in sessions])
|
||||
|
||||
|
||||
@@ -55,9 +58,9 @@ def list_sessions(db: Session = Depends(get_db), current_user: User = Depends(ge
|
||||
def history(
|
||||
sessionId: int = Query(...),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current: UserAuthContext = Depends(get_current_user_context),
|
||||
) -> dict:
|
||||
messages = ChatService.get_history(db, current_user, sessionId)
|
||||
messages = ChatService.get_history(db, current.user, sessionId, current.chat_scope)
|
||||
reasoning_visible = ReasoningPolicyService.is_visible(db)
|
||||
result = []
|
||||
for message in messages:
|
||||
@@ -72,9 +75,9 @@ def history(
|
||||
def update_title(
|
||||
payload: UpdateSessionTitleRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current: UserAuthContext = Depends(get_current_user_context),
|
||||
) -> dict:
|
||||
session = ChatService.update_title(db, current_user, payload.sessionId, payload.title)
|
||||
session = ChatService.update_title(db, current.user, payload.sessionId, payload.title, current.chat_scope)
|
||||
return api_success(ChatSessionRead.model_validate(session).model_dump())
|
||||
|
||||
|
||||
@@ -82,9 +85,9 @@ def update_title(
|
||||
def delete_session(
|
||||
session_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current: UserAuthContext = Depends(get_current_user_context),
|
||||
) -> dict:
|
||||
ChatService.delete_session(db, current_user, session_id)
|
||||
ChatService.delete_session(db, current.user, session_id, current.chat_scope)
|
||||
return api_success()
|
||||
|
||||
|
||||
@@ -92,29 +95,37 @@ def delete_session(
|
||||
def finish_topic(
|
||||
session_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current: UserAuthContext = Depends(get_current_user_context),
|
||||
) -> dict:
|
||||
session = ChatService._get_user_session(db, current_user, session_id)
|
||||
return api_success(GrowthProfileService.finish_active_topic(db, user=current_user, session=session))
|
||||
session = ChatService._get_user_session(db, current.user, session_id, current.chat_scope)
|
||||
return api_success(GrowthProfileService.finish_active_topic(db, user=current.user, session=session))
|
||||
|
||||
|
||||
@router.get("/topic/settlement/{summary_id}")
|
||||
def topic_settlement(
|
||||
summary_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current: UserAuthContext = Depends(get_current_user_context),
|
||||
) -> dict:
|
||||
return api_success(GrowthProfileService.topic_settlement_result(db, user=current_user, summary_id=summary_id))
|
||||
topic_session_id = db.scalar(
|
||||
select(TopicSession.chat_session_id)
|
||||
.join(TopicSummary, TopicSummary.topic_session_id == TopicSession.id)
|
||||
.where(TopicSummary.id == summary_id, TopicSummary.user_id == current.user.id)
|
||||
)
|
||||
if topic_session_id is None:
|
||||
raise HTTPException(status_code=404, detail="主题沉淀任务不存在")
|
||||
ChatService._get_user_session(db, current.user, topic_session_id, current.chat_scope)
|
||||
return api_success(GrowthProfileService.topic_settlement_result(db, user=current.user, summary_id=summary_id))
|
||||
|
||||
|
||||
@router.post("/session/{session_id}/help-card")
|
||||
def generate_help_card(
|
||||
session_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current: UserAuthContext = Depends(get_current_user_context),
|
||||
) -> dict:
|
||||
session = ChatService._get_user_session(db, current_user, session_id)
|
||||
card = HelpCardService.generate_for_session(db, user=current_user, session=session)
|
||||
session = ChatService._get_user_session(db, current.user, session_id, current.chat_scope)
|
||||
card = HelpCardService.generate_for_session(db, user=current.user, session=session)
|
||||
return api_success(help_card_dict(card))
|
||||
|
||||
|
||||
@@ -122,27 +133,33 @@ def generate_help_card(
|
||||
def list_help_cards(
|
||||
limit: int = Query(default=20, ge=1, le=50),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current: UserAuthContext = Depends(get_current_user_context),
|
||||
) -> dict:
|
||||
return api_success([help_card_dict(card) for card in HelpCardService.list_user_cards(db, user=current_user, limit=limit)])
|
||||
cards = HelpCardService.list_user_cards(
|
||||
db,
|
||||
user=current.user,
|
||||
scope=current.chat_scope,
|
||||
limit=limit,
|
||||
)
|
||||
return api_success([help_card_dict(card) for card in cards])
|
||||
|
||||
|
||||
@router.post("/help-card/{card_id}/copied")
|
||||
def mark_help_card_copied(
|
||||
card_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current: UserAuthContext = Depends(get_current_user_context),
|
||||
) -> dict:
|
||||
return api_success(help_card_dict(HelpCardService.mark_copied(db, user=current_user, card_id=card_id)))
|
||||
return api_success(help_card_dict(HelpCardService.mark_copied(db, user=current.user, scope=current.chat_scope, card_id=card_id)))
|
||||
|
||||
|
||||
@router.delete("/help-card/{card_id}")
|
||||
def delete_help_card(
|
||||
card_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current: UserAuthContext = Depends(get_current_user_context),
|
||||
) -> dict:
|
||||
HelpCardService.delete(db, user=current_user, card_id=card_id)
|
||||
HelpCardService.delete(db, user=current.user, scope=current.chat_scope, card_id=card_id)
|
||||
return api_success()
|
||||
|
||||
|
||||
@@ -150,10 +167,10 @@ def delete_help_card(
|
||||
def generate_share_draft(
|
||||
session_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current: UserAuthContext = Depends(get_current_user_context),
|
||||
) -> dict:
|
||||
session = ChatService._get_user_session(db, current_user, session_id)
|
||||
draft = ShareDraftService.generate_for_session(db, user=current_user, session=session)
|
||||
session = ChatService._get_user_session(db, current.user, session_id, current.chat_scope)
|
||||
draft = ShareDraftService.generate_for_session(db, user=current.user, session=session)
|
||||
return api_success(share_draft_dict(draft))
|
||||
|
||||
|
||||
@@ -161,27 +178,33 @@ def generate_share_draft(
|
||||
def list_share_drafts(
|
||||
limit: int = Query(default=20, ge=1, le=50),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current: UserAuthContext = Depends(get_current_user_context),
|
||||
) -> dict:
|
||||
return api_success([share_draft_dict(draft) for draft in ShareDraftService.list_user_drafts(db, user=current_user, limit=limit)])
|
||||
drafts = ShareDraftService.list_user_drafts(
|
||||
db,
|
||||
user=current.user,
|
||||
scope=current.chat_scope,
|
||||
limit=limit,
|
||||
)
|
||||
return api_success([share_draft_dict(draft) for draft in drafts])
|
||||
|
||||
|
||||
@router.post("/share-draft/{draft_id}/copied")
|
||||
def mark_share_draft_copied(
|
||||
draft_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current: UserAuthContext = Depends(get_current_user_context),
|
||||
) -> dict:
|
||||
return api_success(share_draft_dict(ShareDraftService.mark_copied(db, user=current_user, draft_id=draft_id)))
|
||||
return api_success(share_draft_dict(ShareDraftService.mark_copied(db, user=current.user, scope=current.chat_scope, draft_id=draft_id)))
|
||||
|
||||
|
||||
@router.delete("/share-draft/{draft_id}")
|
||||
def delete_share_draft(
|
||||
draft_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current: UserAuthContext = Depends(get_current_user_context),
|
||||
) -> dict:
|
||||
ShareDraftService.delete(db, user=current_user, draft_id=draft_id)
|
||||
ShareDraftService.delete(db, user=current.user, scope=current.chat_scope, draft_id=draft_id)
|
||||
return api_success()
|
||||
|
||||
|
||||
@@ -189,10 +212,10 @@ def delete_share_draft(
|
||||
def completions(
|
||||
payload: ChatCompletionRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current: UserAuthContext = Depends(get_current_user_context),
|
||||
) -> StreamingResponse:
|
||||
return StreamingResponse(
|
||||
_chat_stream(payload, db, current_user),
|
||||
_chat_stream(payload, db, current),
|
||||
media_type="text/event-stream",
|
||||
headers={
|
||||
"Cache-Control": "no-cache, no-transform",
|
||||
@@ -206,13 +229,17 @@ def completions(
|
||||
def stop(
|
||||
payload: StopChatRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
current: UserAuthContext = Depends(get_current_user_context),
|
||||
) -> dict:
|
||||
ChatService.stop_generation(db, current_user, payload.sessionId)
|
||||
ChatService.stop_generation(db, current.user, payload.sessionId, current.chat_scope)
|
||||
return api_success()
|
||||
|
||||
|
||||
async def _chat_stream(payload: ChatCompletionRequest, db: Session, current_user: User) -> AsyncIterator[str]:
|
||||
async def _chat_stream(payload: ChatCompletionRequest, db: Session, current: UserAuthContext) -> AsyncIterator[str]:
|
||||
# Keep the internal stream helper compatible with direct service/test callers;
|
||||
# HTTP requests always pass a fully validated UserAuthContext.
|
||||
current_user = getattr(current, "user", current)
|
||||
chat_scope = getattr(current, "chat_scope", ChatAccessScope.direct())
|
||||
config = load_chat_queue_config(db)
|
||||
queue_lease = await request_chat_slot(config)
|
||||
queue_request = queue_lease.request
|
||||
@@ -274,6 +301,7 @@ async def _chat_stream(payload: ChatCompletionRequest, db: Session, current_user
|
||||
payload.sessionId,
|
||||
payload.message,
|
||||
retry_failed_question=payload.retry,
|
||||
scope=chat_scope,
|
||||
)
|
||||
async for segment in ReasoningPolicyService.iter_segments(chunks):
|
||||
if segment.kind == "content":
|
||||
|
||||
Reference in New Issue
Block a user