Skip to content

Commit ea50d8d

Browse files
author
User
committed
添加聊天管理功能
1 parent adc3ba9 commit ea50d8d

9 files changed

Lines changed: 911 additions & 8 deletions

File tree

backend/logging_config.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,23 @@ def setup_logging():
6262
root_logger.addHandler(console_handler)
6363

6464
# 设置第三方库的日志级别
65+
# 配置Uvicorn日志
6566
logging.getLogger("uvicorn").setLevel(logging.INFO)
67+
logging.getLogger("uvicorn.access").setLevel(logging.INFO)
68+
logging.getLogger("uvicorn.error").setLevel(logging.INFO)
69+
70+
# 过滤掉无效HTTP请求的警告(通常来自扫描器或格式错误的请求)
71+
class InvalidRequestFilter(logging.Filter):
72+
"""过滤无效HTTP请求的警告"""
73+
def filter(self, record):
74+
# 过滤掉"Invalid HTTP request received"警告
75+
if "Invalid HTTP request received" in str(record.getMessage()):
76+
return False
77+
return True
78+
79+
# 应用过滤器到uvicorn.error日志记录器
80+
invalid_request_filter = InvalidRequestFilter()
81+
logging.getLogger("uvicorn.error").addFilter(invalid_request_filter)
6682
logging.getLogger("fastapi").setLevel(logging.INFO)
6783
logging.getLogger("sqlalchemy.engine").setLevel(logging.WARNING)
6884
logging.getLogger("chromadb").setLevel(logging.WARNING)

backend/main.py

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
from fastapi import FastAPI, HTTPException, Depends, UploadFile, File, Form
1+
from fastapi import FastAPI, HTTPException, Depends, UploadFile, File, Form, Request
22
from fastapi.middleware.cors import CORSMiddleware
3-
from fastapi.responses import JSONResponse
3+
from fastapi.responses import JSONResponse, Response
44
from fastapi.staticfiles import StaticFiles
5+
from starlette.middleware.base import BaseHTTPMiddleware
6+
from starlette.types import Message
57
import sys
68
import os
79
import json
@@ -59,6 +61,30 @@
5961
allow_headers=["*"],
6062
)
6163

64+
# 添加中间件来静默处理常见请求,减少日志噪音
65+
class SilentCommonRequestsMiddleware(BaseHTTPMiddleware):
66+
"""静默处理常见请求(favicon、robots.txt等),减少日志噪音"""
67+
68+
async def dispatch(self, request: Request, call_next):
69+
path = request.url.path
70+
71+
# 静默处理的路径列表
72+
silent_paths = [
73+
"/favicon.ico",
74+
"/robots.txt",
75+
"/.well-known/security.txt",
76+
"/.well-known/",
77+
]
78+
79+
# 如果是静默路径,直接返回空响应
80+
if any(path.startswith(silent) for silent in silent_paths):
81+
return Response(status_code=204) # No Content
82+
83+
# 继续处理其他请求
84+
return await call_next(request)
85+
86+
app.add_middleware(SilentCommonRequestsMiddleware)
87+
6288
# 文件存储配置(使用项目根目录)
6389
UPLOAD_DIR = Path(project_root) / "uploads"
6490
UPLOAD_DIR.mkdir(exist_ok=True)
@@ -165,6 +191,29 @@ async def root():
165191
"plugins": plugin_stats
166192
}
167193

194+
@app.get("/favicon.ico")
195+
async def favicon():
196+
"""处理favicon请求,返回空响应"""
197+
return Response(status_code=204)
198+
199+
@app.get("/robots.txt")
200+
async def robots():
201+
"""处理robots.txt请求"""
202+
return Response(
203+
content="User-agent: *\nDisallow: /",
204+
media_type="text/plain",
205+
status_code=200
206+
)
207+
208+
@app.get("/.well-known/security.txt")
209+
async def security_txt():
210+
"""处理security.txt请求"""
211+
return Response(
212+
content="# Security Policy\nContact: security@example.com\n",
213+
media_type="text/plain",
214+
status_code=200
215+
)
216+
168217
@app.post("/chat", response_model=ChatResponse)
169218
async def chat(request: ChatRequest):
170219
"""聊天接口"""

backend/routers/chat.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,33 @@ async def get_user_sessions(user_id: str, limit: int = 50):
177177
raise HTTPException(status_code=500, detail=str(e))
178178

179179

180+
@router.get("/users/{user_id}/sessions/search")
181+
async def search_user_sessions(user_id: str, keyword: str = "", limit: int = 50):
182+
"""搜索用户会话"""
183+
try:
184+
result = await chat_service.search_user_sessions(user_id, keyword, limit)
185+
return result
186+
except Exception as e:
187+
logger.error(f"搜索用户会话错误: {e}")
188+
raise HTTPException(status_code=500, detail=str(e))
189+
190+
191+
@router.post("/sessions/batch-delete")
192+
async def delete_sessions_batch(session_ids: List[str]):
193+
"""批量删除会话"""
194+
try:
195+
if not session_ids:
196+
raise HTTPException(status_code=400, detail="会话ID列表不能为空")
197+
198+
result = await chat_service.delete_sessions_batch(session_ids)
199+
return result
200+
except HTTPException:
201+
raise
202+
except Exception as e:
203+
logger.error(f"批量删除会话错误: {e}")
204+
raise HTTPException(status_code=500, detail=str(e))
205+
206+
180207
@router.delete("/sessions/{session_id}")
181208
async def delete_session(session_id: str):
182209
"""删除会话"""

backend/services/chat_service.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,141 @@ async def delete_session(self, session_id: str) -> bool:
534534
print(f"删除会话失败: {e}")
535535
return False
536536

537+
async def search_user_sessions(
538+
self,
539+
user_id: str,
540+
keyword: str = "",
541+
limit: int = 50
542+
) -> Dict[str, Any]:
543+
"""
544+
搜索用户会话
545+
546+
Args:
547+
user_id: 用户ID
548+
keyword: 搜索关键词
549+
limit: 限制数量
550+
551+
Returns:
552+
会话列表
553+
"""
554+
try:
555+
with DatabaseManager() as db:
556+
from backend.database import ChatMessage
557+
558+
sessions = db.get_user_sessions(user_id, limit * 2) # 获取更多以便筛选
559+
560+
session_list = []
561+
keyword_lower = keyword.lower() if keyword else ""
562+
563+
for session in sessions:
564+
# 检查会话是否有消息
565+
message_count = db.db.query(ChatMessage)\
566+
.filter(ChatMessage.session_id == session.session_id)\
567+
.count()
568+
569+
if message_count == 0:
570+
continue
571+
572+
# 获取会话的第一条消息作为标题
573+
first_message = db.db.query(ChatMessage)\
574+
.filter(ChatMessage.session_id == session.session_id)\
575+
.filter(ChatMessage.role == 'user')\
576+
.order_by(ChatMessage.created_at.asc())\
577+
.first()
578+
579+
# 获取会话的最后一条消息作为预览
580+
last_message = db.db.query(ChatMessage)\
581+
.filter(ChatMessage.session_id == session.session_id)\
582+
.order_by(ChatMessage.created_at.desc())\
583+
.first()
584+
585+
title = first_message.content[:30] + "..." if first_message and len(first_message.content) > 30 else (first_message.content if first_message else "新对话")
586+
587+
# 生成预览文本
588+
preview = ""
589+
if last_message:
590+
preview = last_message.content[:50] + "..." if len(last_message.content) > 50 else last_message.content
591+
592+
# 如果有关键词,进行搜索过滤
593+
if keyword_lower:
594+
title_lower = title.lower()
595+
preview_lower = preview.lower()
596+
if keyword_lower not in title_lower and keyword_lower not in preview_lower:
597+
continue
598+
599+
session_list.append({
600+
"session_id": session.session_id,
601+
"title": title,
602+
"preview": preview,
603+
"message_count": message_count,
604+
"created_at": session.created_at.isoformat() if session.created_at else None,
605+
"updated_at": session.updated_at.isoformat() if session.updated_at else None
606+
})
607+
608+
# 限制返回数量
609+
if len(session_list) >= limit:
610+
break
611+
612+
return {
613+
"user_id": user_id,
614+
"sessions": session_list,
615+
"total": len(session_list),
616+
"keyword": keyword
617+
}
618+
except Exception as e:
619+
print(f"搜索用户会话失败: {e}")
620+
return {
621+
"user_id": user_id,
622+
"sessions": [],
623+
"total": 0,
624+
"keyword": keyword,
625+
"error": str(e)
626+
}
627+
628+
async def delete_sessions_batch(self, session_ids: List[str]) -> Dict[str, Any]:
629+
"""
630+
批量删除会话
631+
632+
Args:
633+
session_ids: 会话ID列表
634+
635+
Returns:
636+
删除结果
637+
"""
638+
try:
639+
success_count = 0
640+
failed_count = 0
641+
failed_sessions = []
642+
643+
for session_id in session_ids:
644+
try:
645+
success = await self.delete_session(session_id)
646+
if success:
647+
success_count += 1
648+
else:
649+
failed_count += 1
650+
failed_sessions.append(session_id)
651+
except Exception as e:
652+
failed_count += 1
653+
failed_sessions.append(session_id)
654+
print(f"删除会话 {session_id} 失败: {e}")
655+
656+
return {
657+
"success_count": success_count,
658+
"failed_count": failed_count,
659+
"failed_sessions": failed_sessions,
660+
"total": len(session_ids)
661+
}
662+
except Exception as e:
663+
print(f"批量删除会话失败: {e}")
664+
return {
665+
"success_count": 0,
666+
"failed_count": len(session_ids),
667+
"failed_sessions": session_ids,
668+
"total": len(session_ids),
669+
"error": str(e)
670+
}
671+
537672
async def get_user_emotion_trends(self, user_id: str, days: int = 7) -> Dict[str, Any]:
538673
"""
539674
获取用户情绪趋势

frontend/src/App.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import ChatContainer from './components/ChatContainer';
55
import FeedbackModal from './components/FeedbackModal';
66
import PersonalizationPanel from './components/PersonalizationPanel';
77
import StyleComparison from './components/StyleComparison';
8+
import HistoryManagementModal from './components/HistoryManagementModal';
89
import { motion } from 'framer-motion';
910
import { X } from 'lucide-react';
1011
import { useChat, useFileUpload, useKeyboard, useSession, useFeedback, useURLDetection } from './hooks';
@@ -25,6 +26,7 @@ function App() {
2526
const [inputValue, setInputValue] = useState('');
2627
const [showPersonalizationPanel, setShowPersonalizationPanel] = useState(false);
2728
const [showStyleComparison, setShowStyleComparison] = useState(false);
29+
const [showHistoryManagement, setShowHistoryManagement] = useState(false);
2830

2931
// Refs
3032
const inputRef = useRef(null);
@@ -112,6 +114,24 @@ function App() {
112114
deleteConversationHook(targetSessionId, sessionId, setMessages, setSessionId, setSuggestions);
113115
}, [deleteConversationHook, sessionId, setSessionId, setMessages, setSuggestions]);
114116

117+
// 处理历史消息管理中的会话选择
118+
const handleHistorySessionSelect = useCallback((targetSessionId) => {
119+
loadSessionHistory(targetSessionId, setMessages, setSuggestions);
120+
setSessionId(targetSessionId);
121+
}, [loadSessionHistory, setMessages, setSuggestions, setSessionId]);
122+
123+
// 处理批量删除后的回调
124+
const handleSessionsDeleted = useCallback((deletedSessionIds) => {
125+
// 如果当前会话被删除,清空消息
126+
if (deletedSessionIds.includes(sessionId)) {
127+
setMessages([]);
128+
setSessionId(null);
129+
setSuggestions([]);
130+
}
131+
// 刷新历史会话列表
132+
loadHistorySessions();
133+
}, [sessionId, setMessages, setSessionId, setSuggestions, loadHistorySessions]);
134+
115135
// 键盘处理
116136
const { handleKeyPress, handleTabNavigation } = useKeyboard(
117137
startNewChat,
@@ -185,6 +205,7 @@ function App() {
185205
onDeleteSession={handleDeleteSession}
186206
onOpenPersonalization={() => setShowPersonalizationPanel(true)}
187207
onOpenStyleComparison={() => setShowStyleComparison(true)}
208+
onOpenHistoryManagement={() => setShowHistoryManagement(true)}
188209
/>
189210

190211
<ChatContainer
@@ -226,6 +247,14 @@ function App() {
226247
onCommentChange={setFeedbackComment}
227248
onSubmit={submitFeedback}
228249
/>
250+
251+
<HistoryManagementModal
252+
show={showHistoryManagement}
253+
onClose={() => setShowHistoryManagement(false)}
254+
userId={currentUserId}
255+
onSessionSelect={handleHistorySessionSelect}
256+
onSessionsDeleted={handleSessionsDeleted}
257+
/>
229258
</AppContainer>
230259
);
231260
}

0 commit comments

Comments
 (0)