feat: persist photos and enrich conversation history
This commit is contained in:
@@ -1,15 +1,20 @@
|
||||
"""对话历史查询 API。"""
|
||||
|
||||
from db.models import ConversationMessage, ConversationSession
|
||||
import asyncio
|
||||
|
||||
from db.models import ConversationArtifact, ConversationMessage, ConversationSession
|
||||
from db.session import get_session
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Response
|
||||
from loguru import logger
|
||||
from schemas import (
|
||||
ConversationArtifactOut,
|
||||
ConversationDetailOut,
|
||||
ConversationListOut,
|
||||
ConversationMessageOut,
|
||||
ConversationOut,
|
||||
)
|
||||
from services.auth import require_admin
|
||||
from services.object_storage import delete_object, get_object, key_from_storage_uri
|
||||
from sqlalchemy import func, or_, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
@@ -100,6 +105,17 @@ async def get_conversation(
|
||||
.order_by(ConversationMessage.sequence)
|
||||
)
|
||||
).scalars().all()
|
||||
artifacts = (
|
||||
await session.execute(
|
||||
select(ConversationArtifact)
|
||||
.where(ConversationArtifact.session_id == conversation_id)
|
||||
.order_by(ConversationArtifact.created_at)
|
||||
)
|
||||
).scalars().all()
|
||||
artifacts_by_message: dict[str, list[ConversationArtifact]] = {}
|
||||
for artifact in artifacts:
|
||||
if artifact.message_id:
|
||||
artifacts_by_message.setdefault(artifact.message_id, []).append(artifact)
|
||||
return ConversationDetailOut(
|
||||
**_session_out(conversation).model_dump(),
|
||||
extra=conversation.extra or {},
|
||||
@@ -112,12 +128,52 @@ async def get_conversation(
|
||||
content=message.content,
|
||||
occurred_at=message.occurred_at,
|
||||
extra=message.extra or {},
|
||||
artifacts=[
|
||||
ConversationArtifactOut(
|
||||
id=artifact.id,
|
||||
kind=artifact.kind,
|
||||
content_url=(
|
||||
f"/api/conversations/{conversation_id}/artifacts/"
|
||||
f"{artifact.id}/content"
|
||||
),
|
||||
mime_type=artifact.mime_type,
|
||||
size_bytes=artifact.size_bytes,
|
||||
duration_ms=artifact.duration_ms,
|
||||
extra=artifact.extra or {},
|
||||
)
|
||||
for artifact in artifacts_by_message.get(message.id, [])
|
||||
],
|
||||
)
|
||||
for message in messages
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{conversation_id}/artifacts/{artifact_id}/content")
|
||||
async def get_conversation_artifact(
|
||||
conversation_id: str,
|
||||
artifact_id: str,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
artifact = await session.get(ConversationArtifact, artifact_id)
|
||||
if not artifact or artifact.session_id != conversation_id:
|
||||
raise HTTPException(404, "会话附件不存在")
|
||||
try:
|
||||
key = key_from_storage_uri(artifact.storage_uri)
|
||||
data = await asyncio.to_thread(get_object, key)
|
||||
except Exception as exc:
|
||||
logger.warning(f"读取会话附件失败: {artifact_id}: {exc}")
|
||||
raise HTTPException(404, "会话附件不可用") from exc
|
||||
return Response(
|
||||
content=data,
|
||||
media_type=artifact.mime_type or "application/octet-stream",
|
||||
headers={
|
||||
"Cache-Control": "private, max-age=300",
|
||||
"Content-Disposition": f'inline; filename="{artifact.id}"',
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router.delete("/{conversation_id}")
|
||||
async def delete_conversation(
|
||||
conversation_id: str,
|
||||
@@ -126,6 +182,19 @@ async def delete_conversation(
|
||||
conversation = await session.get(ConversationSession, conversation_id)
|
||||
if not conversation:
|
||||
raise HTTPException(404, "对话记录不存在")
|
||||
storage_uris = (
|
||||
await session.execute(
|
||||
select(ConversationArtifact.storage_uri).where(
|
||||
ConversationArtifact.session_id == conversation_id
|
||||
)
|
||||
)
|
||||
).scalars().all()
|
||||
await session.delete(conversation)
|
||||
await session.commit()
|
||||
for storage_uri in storage_uris:
|
||||
try:
|
||||
key = key_from_storage_uri(storage_uri)
|
||||
await asyncio.to_thread(delete_object, key)
|
||||
except Exception as exc:
|
||||
logger.warning(f"清理已删除会话的附件失败: {storage_uri}: {exc}")
|
||||
return {"ok": True}
|
||||
|
||||
Reference in New Issue
Block a user