|
|
@@ -17,6 +17,74 @@ import httpx
|
|
|
router = APIRouter()
|
|
|
|
|
|
|
|
|
+def _build_conversation_preview(content: str, limit: int = 50) -> str:
|
|
|
+ content = (content or "").strip()
|
|
|
+ if len(content) <= limit:
|
|
|
+ return content
|
|
|
+ return content[:limit] + "..."
|
|
|
+
|
|
|
+
|
|
|
+def _to_frontend_timestamp(timestamp: Optional[int]) -> Optional[int]:
|
|
|
+ if not timestamp:
|
|
|
+ return None
|
|
|
+ return timestamp if timestamp >= 10**12 else timestamp * 1000
|
|
|
+
|
|
|
+
|
|
|
+def _build_conversation_title(conversation: AIConversation) -> str:
|
|
|
+ if conversation.business_type == 3 and (conversation.exam_name or "").strip():
|
|
|
+ return conversation.exam_name.strip()
|
|
|
+ return _build_conversation_preview(conversation.content or "", limit=30)
|
|
|
+
|
|
|
+
|
|
|
+def _refresh_conversation_snapshot(db: Session, conversation_id: int, user_id: int) -> None:
|
|
|
+ latest_message = (
|
|
|
+ db.query(AIMessage)
|
|
|
+ .filter(
|
|
|
+ AIMessage.ai_conversation_id == conversation_id,
|
|
|
+ AIMessage.user_id == user_id,
|
|
|
+ AIMessage.is_deleted == 0,
|
|
|
+ )
|
|
|
+ .order_by(AIMessage.id.desc())
|
|
|
+ .first()
|
|
|
+ )
|
|
|
+
|
|
|
+ if not latest_message:
|
|
|
+ db.query(AIConversation).filter(
|
|
|
+ AIConversation.id == conversation_id,
|
|
|
+ AIConversation.user_id == user_id,
|
|
|
+ ).update({"is_deleted": 1, "updated_at": int(time.time())})
|
|
|
+ return
|
|
|
+
|
|
|
+ latest_user_message = (
|
|
|
+ db.query(AIMessage)
|
|
|
+ .filter(
|
|
|
+ AIMessage.ai_conversation_id == conversation_id,
|
|
|
+ AIMessage.user_id == user_id,
|
|
|
+ AIMessage.type == "user",
|
|
|
+ AIMessage.is_deleted == 0,
|
|
|
+ )
|
|
|
+ .order_by(AIMessage.id.desc())
|
|
|
+ .first()
|
|
|
+ )
|
|
|
+
|
|
|
+ preview_source = (
|
|
|
+ latest_user_message.content
|
|
|
+ if latest_user_message and latest_user_message.content
|
|
|
+ else latest_message.content
|
|
|
+ )
|
|
|
+ preview_content = _build_conversation_preview(preview_source or "", limit=100)
|
|
|
+
|
|
|
+ db.query(AIConversation).filter(
|
|
|
+ AIConversation.id == conversation_id,
|
|
|
+ AIConversation.user_id == user_id,
|
|
|
+ ).update(
|
|
|
+ {
|
|
|
+ "content": preview_content or " ",
|
|
|
+ "updated_at": int(time.time()),
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
|
# 辅助函数
|
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
|
@@ -108,7 +176,7 @@ async def send_deepseek_message(
|
|
|
# 创建或获取对话
|
|
|
if not data.conversation_id:
|
|
|
conversation = AIConversation(
|
|
|
- user_id=user.userCode,
|
|
|
+ user_id=user.user_id,
|
|
|
content=message[:100],
|
|
|
business_type=data.business_type,
|
|
|
exam_name=data.exam_name if data.business_type == 3 else "",
|
|
|
@@ -122,6 +190,17 @@ async def send_deepseek_message(
|
|
|
conv_id = conversation.id
|
|
|
else:
|
|
|
conv_id = data.conversation_id
|
|
|
+ db.query(AIConversation).filter(
|
|
|
+ AIConversation.id == conv_id,
|
|
|
+ AIConversation.user_id == user.user_id,
|
|
|
+ AIConversation.is_deleted == 0,
|
|
|
+ ).update({
|
|
|
+ "content": message[:100],
|
|
|
+ "business_type": data.business_type,
|
|
|
+ "exam_name": data.exam_name if data.business_type == 3 else "",
|
|
|
+ "updated_at": int(time.time()),
|
|
|
+ })
|
|
|
+ db.commit()
|
|
|
|
|
|
response_text = ""
|
|
|
|
|
|
@@ -241,7 +320,7 @@ async def send_deepseek_message(
|
|
|
"data": {
|
|
|
"conversation_id": conv_id,
|
|
|
"response": response_text,
|
|
|
- "user_id": user.userCode,
|
|
|
+ "user_id": user.user_id,
|
|
|
"business_type": data.business_type,
|
|
|
},
|
|
|
}
|
|
|
@@ -251,32 +330,82 @@ async def send_deepseek_message(
|
|
|
|
|
|
|
|
|
@router.get("/get_history_record")
|
|
|
-async def get_history_record(request: Request, db: Session = Depends(get_db)):
|
|
|
- """获取对话历史记录列表"""
|
|
|
+async def get_history_record(
|
|
|
+ request: Request,
|
|
|
+ ai_conversation_id: int = 0,
|
|
|
+ business_type: Optional[int] = None,
|
|
|
+ db: Session = Depends(get_db),
|
|
|
+):
|
|
|
+ """兼容前端的历史记录查询:ai_conversation_id=0 返回对话列表,否则返回消息详情。"""
|
|
|
user = request.state.user
|
|
|
if not user:
|
|
|
return {"statusCode": 401, "msg": "未授权"}
|
|
|
- conversations = (
|
|
|
- db.query(AIConversation)
|
|
|
- .filter(
|
|
|
- AIConversation.user_id == user.userCode,
|
|
|
- AIConversation.is_deleted == 0,
|
|
|
+
|
|
|
+ if ai_conversation_id > 0:
|
|
|
+ messages = (
|
|
|
+ db.query(AIMessage)
|
|
|
+ .filter(
|
|
|
+ AIMessage.ai_conversation_id == ai_conversation_id,
|
|
|
+ AIMessage.user_id == user.user_id,
|
|
|
+ AIMessage.is_deleted == 0,
|
|
|
+ )
|
|
|
+ .order_by(AIMessage.id.asc())
|
|
|
+ .all()
|
|
|
+ )
|
|
|
+
|
|
|
+ return {
|
|
|
+ "statusCode": 200,
|
|
|
+ "msg": "success",
|
|
|
+ "total": len(messages),
|
|
|
+ "data": [
|
|
|
+ {
|
|
|
+ "id": message.id,
|
|
|
+ "ai_conversation_id": message.ai_conversation_id,
|
|
|
+ "user_id": message.user_id,
|
|
|
+ "type": message.type,
|
|
|
+ "content": message.content,
|
|
|
+ "user_feedback": message.user_feedback,
|
|
|
+ "prev_user_id": message.prev_user_id,
|
|
|
+ "search_source": message.search_source or "",
|
|
|
+ "guess_you_want": message.guess_you_want or "",
|
|
|
+ "created_at": _to_frontend_timestamp(message.created_at),
|
|
|
+ "updated_at": _to_frontend_timestamp(message.updated_at),
|
|
|
+ }
|
|
|
+ for message in messages
|
|
|
+ ],
|
|
|
+ }
|
|
|
+
|
|
|
+ conversations_query = db.query(AIConversation).filter(
|
|
|
+ AIConversation.user_id == user.user_id,
|
|
|
+ AIConversation.is_deleted == 0,
|
|
|
+ )
|
|
|
+
|
|
|
+ if business_type is not None:
|
|
|
+ conversations_query = conversations_query.filter(
|
|
|
+ AIConversation.business_type == business_type
|
|
|
)
|
|
|
- .order_by(AIConversation.created_at.desc())
|
|
|
+
|
|
|
+ total = conversations_query.count()
|
|
|
+ conversations = (
|
|
|
+ conversations_query
|
|
|
+ .order_by(AIConversation.updated_at.desc(), AIConversation.id.desc())
|
|
|
.limit(50)
|
|
|
.all()
|
|
|
)
|
|
|
+
|
|
|
return {
|
|
|
"statusCode": 200,
|
|
|
"msg": "success",
|
|
|
+ "total": total,
|
|
|
"data": [
|
|
|
{
|
|
|
"id": conv.id,
|
|
|
- "content": (conv.content or "")[:50]
|
|
|
- + ("..." if len(conv.content or "") > 50 else ""),
|
|
|
+ "title": _build_conversation_title(conv),
|
|
|
+ "content": conv.content or "",
|
|
|
"business_type": conv.business_type,
|
|
|
- "exam_name": conv.exam_name,
|
|
|
- "created_at": conv.created_at,
|
|
|
+ "exam_name": conv.exam_name or "",
|
|
|
+ "created_at": _to_frontend_timestamp(conv.created_at),
|
|
|
+ "updated_at": _to_frontend_timestamp(conv.updated_at),
|
|
|
}
|
|
|
for conv in conversations
|
|
|
],
|
|
|
@@ -284,7 +413,8 @@ async def get_history_record(request: Request, db: Session = Depends(get_db)):
|
|
|
|
|
|
|
|
|
class DeleteConversationRequest(BaseModel):
|
|
|
- ai_conversation_id: int
|
|
|
+ ai_conversation_id: int = 0
|
|
|
+ ai_message_id: int = 0
|
|
|
|
|
|
|
|
|
@router.post("/delete_conversation")
|
|
|
@@ -299,14 +429,50 @@ async def delete_conversation(
|
|
|
if not user:
|
|
|
return {"statusCode": 401, "msg": "未授权"}
|
|
|
|
|
|
+ now_ts = int(time.time())
|
|
|
+
|
|
|
+ if data.ai_message_id:
|
|
|
+ ai_message = (
|
|
|
+ db.query(AIMessage)
|
|
|
+ .filter(
|
|
|
+ AIMessage.id == data.ai_message_id,
|
|
|
+ AIMessage.user_id == user.user_id,
|
|
|
+ AIMessage.type == "ai",
|
|
|
+ AIMessage.is_deleted == 0,
|
|
|
+ )
|
|
|
+ .first()
|
|
|
+ )
|
|
|
+ if not ai_message:
|
|
|
+ return {"statusCode": 404, "msg": "消息不存在"}
|
|
|
+
|
|
|
+ db.query(AIMessage).filter(
|
|
|
+ AIMessage.id == ai_message.id,
|
|
|
+ AIMessage.user_id == user.user_id,
|
|
|
+ ).update({"is_deleted": 1, "updated_at": now_ts})
|
|
|
+
|
|
|
+ if ai_message.prev_user_id:
|
|
|
+ db.query(AIMessage).filter(
|
|
|
+ AIMessage.id == ai_message.prev_user_id,
|
|
|
+ AIMessage.user_id == user.user_id,
|
|
|
+ AIMessage.ai_conversation_id == ai_message.ai_conversation_id,
|
|
|
+ ).update({"is_deleted": 1, "updated_at": now_ts})
|
|
|
+
|
|
|
+ _refresh_conversation_snapshot(db, ai_message.ai_conversation_id, user.user_id)
|
|
|
+ db.commit()
|
|
|
+ return {"statusCode": 200, "msg": "删除成功"}
|
|
|
+
|
|
|
+ if not data.ai_conversation_id:
|
|
|
+ return {"statusCode": 400, "msg": "缺少删除参数"}
|
|
|
+
|
|
|
db.query(AIConversation).filter(
|
|
|
AIConversation.id == data.ai_conversation_id,
|
|
|
- AIConversation.user_id == user.userCode,
|
|
|
- ).update({"is_deleted": 1, "updated_at": int(time.time())})
|
|
|
+ AIConversation.user_id == user.user_id,
|
|
|
+ ).update({"is_deleted": 1, "updated_at": now_ts})
|
|
|
|
|
|
db.query(AIMessage).filter(
|
|
|
- AIMessage.ai_conversation_id == data.ai_conversation_id
|
|
|
- ).update({"is_deleted": 1, "updated_at": int(time.time())})
|
|
|
+ AIMessage.ai_conversation_id == data.ai_conversation_id,
|
|
|
+ AIMessage.user_id == user.user_id,
|
|
|
+ ).update({"is_deleted": 1, "updated_at": now_ts})
|
|
|
|
|
|
db.commit()
|
|
|
return {"statusCode": 200, "msg": "删除成功"}
|
|
|
@@ -326,7 +492,7 @@ async def delete_history_record(
|
|
|
return {"statusCode": 401, "msg": "未授权"}
|
|
|
db.query(AIConversation).filter(
|
|
|
AIConversation.id == data.ai_conversation_id,
|
|
|
- AIConversation.user_id == user.userCode,
|
|
|
+ AIConversation.user_id == user.user_id,
|
|
|
).update({"is_deleted": 1, "updated_at": int(time.time())})
|
|
|
db.commit()
|
|
|
return {"statusCode": 200, "msg": "删除成功"}
|
|
|
@@ -426,8 +592,8 @@ async def stream_chat_with_db(request: Request, data: StreamChatWithDBRequest):
|
|
|
# 1. 创建或获取对话
|
|
|
if data.ai_conversation_id == 0:
|
|
|
conversation = AIConversation(
|
|
|
- user_id=user.userCode,
|
|
|
- content=message[:100],
|
|
|
+ user_id=user.user_id,
|
|
|
+ content=_build_conversation_preview(message, limit=100),
|
|
|
business_type=data.business_type,
|
|
|
exam_name=data.exam_name,
|
|
|
created_at=int(time.time()),
|
|
|
@@ -439,12 +605,49 @@ async def stream_chat_with_db(request: Request, data: StreamChatWithDBRequest):
|
|
|
db.refresh(conversation)
|
|
|
conv_id = conversation.id
|
|
|
else:
|
|
|
- conv_id = data.ai_conversation_id
|
|
|
+ existing_conversation = (
|
|
|
+ db.query(AIConversation)
|
|
|
+ .filter(
|
|
|
+ AIConversation.id == data.ai_conversation_id,
|
|
|
+ AIConversation.user_id == user.user_id,
|
|
|
+ AIConversation.is_deleted == 0,
|
|
|
+ )
|
|
|
+ .first()
|
|
|
+ )
|
|
|
+
|
|
|
+ if existing_conversation:
|
|
|
+ conv_id = existing_conversation.id
|
|
|
+ db.query(AIConversation).filter(
|
|
|
+ AIConversation.id == conv_id,
|
|
|
+ AIConversation.user_id == user.user_id,
|
|
|
+ ).update(
|
|
|
+ {
|
|
|
+ "content": _build_conversation_preview(message, limit=100),
|
|
|
+ "business_type": data.business_type,
|
|
|
+ "exam_name": data.exam_name if data.business_type == 3 else "",
|
|
|
+ "updated_at": int(time.time()),
|
|
|
+ }
|
|
|
+ )
|
|
|
+ db.commit()
|
|
|
+ else:
|
|
|
+ conversation = AIConversation(
|
|
|
+ user_id=user.user_id,
|
|
|
+ content=_build_conversation_preview(message, limit=100),
|
|
|
+ business_type=data.business_type,
|
|
|
+ exam_name=data.exam_name if data.business_type == 3 else "",
|
|
|
+ created_at=int(time.time()),
|
|
|
+ updated_at=int(time.time()),
|
|
|
+ is_deleted=0,
|
|
|
+ )
|
|
|
+ db.add(conversation)
|
|
|
+ db.commit()
|
|
|
+ db.refresh(conversation)
|
|
|
+ conv_id = conversation.id
|
|
|
|
|
|
# 2. 插入用户消息
|
|
|
user_msg = AIMessage(
|
|
|
ai_conversation_id=conv_id,
|
|
|
- user_id=user.userCode,
|
|
|
+ user_id=user.user_id,
|
|
|
type="user",
|
|
|
content=message,
|
|
|
created_at=int(time.time()),
|
|
|
@@ -458,7 +661,7 @@ async def stream_chat_with_db(request: Request, data: StreamChatWithDBRequest):
|
|
|
# 3. 插入 AI 占位消息
|
|
|
ai_msg = AIMessage(
|
|
|
ai_conversation_id=conv_id,
|
|
|
- user_id=user.userCode,
|
|
|
+ user_id=user.user_id,
|
|
|
type="ai",
|
|
|
content="",
|
|
|
prev_user_id=user_msg.id,
|
|
|
@@ -530,8 +733,20 @@ async def stream_chat_with_db(request: Request, data: StreamChatWithDBRequest):
|
|
|
|
|
|
# 9. 更新 AI 消息内容
|
|
|
if full_response:
|
|
|
+ now_ts = int(time.time())
|
|
|
db.query(AIMessage).filter(AIMessage.id == ai_msg.id).update(
|
|
|
- {"content": full_response, "updated_at": int(time.time())}
|
|
|
+ {"content": full_response, "updated_at": now_ts}
|
|
|
+ )
|
|
|
+ db.query(AIConversation).filter(
|
|
|
+ AIConversation.id == conv_id,
|
|
|
+ AIConversation.user_id == user.user_id,
|
|
|
+ ).update(
|
|
|
+ {
|
|
|
+ "content": _build_conversation_preview(message, limit=100),
|
|
|
+ "business_type": data.business_type,
|
|
|
+ "exam_name": data.exam_name if data.business_type == 3 else "",
|
|
|
+ "updated_at": now_ts,
|
|
|
+ }
|
|
|
)
|
|
|
db.commit()
|
|
|
|
|
|
@@ -661,7 +876,7 @@ async def online_search(question: str, request: Request, db: Session = Depends(g
|
|
|
"max_text_len": 4000 # 最大文本长度
|
|
|
},
|
|
|
"response_mode": "blocking",
|
|
|
- "user": getattr(user, "account", str(user.userCode)),
|
|
|
+ "user": getattr(user, "account", str(user.user_id)),
|
|
|
}
|
|
|
|
|
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
|
@@ -744,7 +959,7 @@ async def intent_recognition(
|
|
|
if data.save_to_db and intent_type in ("greeting", "问候", "faq", "常见问题"):
|
|
|
if data.ai_conversation_id == 0:
|
|
|
conversation = AIConversation(
|
|
|
- user_id=user.userCode,
|
|
|
+ user_id=user.user_id,
|
|
|
content=data.message[:100],
|
|
|
business_type=0,
|
|
|
created_at=int(time.time()),
|
|
|
@@ -760,7 +975,7 @@ async def intent_recognition(
|
|
|
|
|
|
user_msg = AIMessage(
|
|
|
ai_conversation_id=conv_id,
|
|
|
- user_id=user.userCode,
|
|
|
+ user_id=user.user_id,
|
|
|
type="user",
|
|
|
content=data.message,
|
|
|
created_at=int(time.time()),
|
|
|
@@ -772,7 +987,7 @@ async def intent_recognition(
|
|
|
|
|
|
ai_msg = AIMessage(
|
|
|
ai_conversation_id=conv_id,
|
|
|
- user_id=user.userCode,
|
|
|
+ user_id=user.user_id,
|
|
|
type="ai",
|
|
|
content=response_text,
|
|
|
prev_user_id=user_msg.id,
|