Skip to content

Commit ddad336

Browse files
committed
feat: 添加历史会话侧边栏 + 动态时间注入 + 历史检索工具
1 parent 8bd1382 commit ddad336

2 files changed

Lines changed: 74 additions & 30 deletions

File tree

‎frontend/src/components/ChatArea.tsx‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,14 @@ export default function ChatArea() {
1515

1616
// 自动滚动到底部
1717
useEffect(() => {
18-
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
19-
}, [messages, sse.tokens]);
18+
// 使用 setTimeout 延迟滚动,等待 Markdown 渲染及 DOM 高度计算完成
19+
const timer = setTimeout(() => {
20+
bottomRef.current?.scrollIntoView({ behavior: "auto", block: "end" });
21+
}, 100);
22+
23+
return () => clearTimeout(timer);
24+
// 监听消息列表、流式 token 以及状态变化(done/error 时兜底滚动)
25+
}, [messages, sse.tokens, sse.status]);
2026

2127
// 构建流式中的临时消息 — 只在流式进行中(connecting/streaming)显示,done 后的消息由 ChatProvider 加入 messages
2228
const streamingMsg: ChatMessage | null =
@@ -70,4 +76,4 @@ export default function ChatArea() {
7076
<ChatInput onSubmit={sendMessage} disabled={isBusy} />
7177
</div>
7278
);
73-
}
79+
}

‎frontend/src/providers/ChatProvider.tsx‎

Lines changed: 65 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,42 @@
11
"use client";
22

3-
// 智能搜索助手 — 聊天状态管理 Provider
3+
// 智能搜索助手 — 聊天状态管理 Provider (前端自管历史会话版)
44
import React, { createContext, useContext, useState, useCallback, useRef, useEffect } from "react";
55
import type { ChatMessage, UserSettings, SessionSummary } from "../lib/types";
66
import { useSSE } from "../hooks/useSSE";
7-
import { generateSessionId, getOrCreateUserId, getSessions, getSession, deleteSession as apiDeleteSession } from "../lib/api";
7+
import { generateSessionId, getOrCreateUserId, getSession, deleteSession as apiDeleteSession } from "../lib/api";
8+
9+
const LOCAL_SESSIONS_KEY = "qa_local_sessions_v1";
10+
11+
// ── 本地存储辅助函数 ──
12+
const getLocalSessions = (): SessionSummary[] => {
13+
if (typeof window === "undefined") return [];
14+
try {
15+
const raw = localStorage.getItem(LOCAL_SESSIONS_KEY);
16+
return raw ? JSON.parse(raw) : [];
17+
} catch {
18+
return [];
19+
}
20+
};
21+
22+
const saveLocalSessions = (data: SessionSummary[]) => {
23+
if (typeof window === "undefined") return;
24+
localStorage.setItem(LOCAL_SESSIONS_KEY, JSON.stringify(data));
25+
};
826

927
interface ChatContextType {
1028
messages: ChatMessage[];
1129
sessionId: string;
1230
userId: string;
1331
settings: UserSettings;
1432
stats: { totalQueries: number; totalTokens: number };
15-
// 会话列表
1633
sessions: SessionSummary[];
1734
sessionsLoading: boolean;
18-
// SSE 状态
1935
sse: ReturnType<typeof useSSE>;
20-
// 操作
2136
sendMessage: (content: string) => Promise<void>;
2237
clearHistory: () => void;
2338
newSession: () => void;
2439
updateSettings: (s: Partial<UserSettings>) => void;
25-
// 会话管理
2640
refreshSessions: () => Promise<void>;
2741
switchSession: (sid: string) => Promise<void>;
2842
removeSession: (sid: string) => Promise<void>;
@@ -48,39 +62,32 @@ export default function ChatProvider({
4862
const [messages, setMessages] = useState<ChatMessage[]>([]);
4963
const [settings, setSettings] = useState<UserSettings>(defaultSettings);
5064
const [stats, setStats] = useState({ totalQueries: 0, totalTokens: 0 });
65+
66+
// 会话列表现在完全由前端维护
5167
const [sessions, setSessions] = useState<SessionSummary[]>([]);
5268
const [sessionsLoading, setSessionsLoading] = useState(false);
69+
5370
const sse = useSSE();
5471
const msgCounter = useRef(0);
5572

56-
// 首次挂载时初始化 userId
73+
// 初始化 userId 和 本地列表
5774
useEffect(() => {
5875
setUserId(getOrCreateUserId());
76+
setSessions(getLocalSessions());
5977
}, []);
6078

61-
// ── 加载会话列表 ──
79+
// 刷新本地列表
6280
const refreshSessions = useCallback(async () => {
63-
if (!userId) return;
64-
setSessionsLoading(true);
65-
try {
66-
const data = await getSessions(userId);
67-
setSessions(data.sessions || []);
68-
} catch (e) {
69-
console.error("加载会话列表失败", e);
70-
} finally {
71-
setSessionsLoading(false);
72-
}
73-
}, [userId]);
74-
75-
useEffect(() => {
76-
if (userId) refreshSessions();
77-
}, [userId, refreshSessions]);
81+
setSessions(getLocalSessions());
82+
}, []);
7883

7984
// ── 切换会话 ──
8085
const switchSession = useCallback(
8186
async (sid: string) => {
8287
if (!userId) return;
88+
setSessionsLoading(true);
8389
try {
90+
// 依然从后端(Redis)拉取该会话的历史记录
8491
const info = await getSession(sid, userId);
8592
const history: ChatMessage[] = (info.history || []).map(
8693
(m, i) => ({
@@ -94,6 +101,8 @@ export default function ChatProvider({
94101
sse.reset();
95102
} catch (e) {
96103
console.error("切换会话失败", e);
104+
} finally {
105+
setSessionsLoading(false);
97106
}
98107
},
99108
[userId, sse]
@@ -104,17 +113,23 @@ export default function ChatProvider({
104113
async (sid: string) => {
105114
if (!userId) return;
106115
try {
116+
// 通知后端删除
107117
await apiDeleteSession(sid, userId);
118+
119+
// 从前端本地列表中移除
120+
const current = getLocalSessions().filter((s) => s.session_id !== sid);
121+
saveLocalSessions(current);
122+
setSessions(current);
123+
108124
if (sid === sessionId) {
109125
setMessages([]);
110126
setSessionId(generateSessionId());
111127
}
112-
await refreshSessions();
113128
} catch (e) {
114129
console.error("删除会话失败", e);
115130
}
116131
},
117-
[userId, sessionId, refreshSessions]
132+
[userId, sessionId]
118133
);
119134

120135
const clearHistory = useCallback(() => {
@@ -132,10 +147,35 @@ export default function ChatProvider({
132147
setSettings((prev) => ({ ...prev, ...s }));
133148
}, []);
134149

150+
// ── 发送消息 ──
135151
const sendMessage = useCallback(
136152
async (content: string) => {
137153
if (!content.trim() || !userId) return;
138154

155+
// 1. 更新前端会话列表
156+
let currentSessions = getLocalSessions();
157+
const exists = currentSessions.find((s) => s.session_id === sessionId);
158+
159+
if (!exists) {
160+
// 新会话,加入列表
161+
const newSession: SessionSummary = {
162+
session_id: sessionId,
163+
title: content.slice(0, 20) || "新对话", // 截取前20字当标题
164+
preview: content.slice(0, 50),
165+
message_count: 1,
166+
};
167+
currentSessions = [newSession, ...currentSessions];
168+
} else {
169+
// 已有会话,更新预览并移到最前
170+
currentSessions = currentSessions.filter((s) => s.session_id !== sessionId);
171+
exists.preview = content.slice(0, 50);
172+
exists.message_count = (exists.message_count || 0) + 1;
173+
currentSessions = [exists, ...currentSessions];
174+
}
175+
saveLocalSessions(currentSessions);
176+
setSessions(currentSessions);
177+
178+
// 2. 发送 SSE 请求
139179
const userMsg: ChatMessage = {
140180
id: `u_${msgCounter.current++}`,
141181
role: "user",
@@ -172,8 +212,6 @@ export default function ChatProvider({
172212
totalQueries: prev.totalQueries + 1,
173213
totalTokens: prev.totalTokens + (sse.meta?.tokens_used ?? 0),
174214
}));
175-
// 消息完成后刷新侧边栏
176-
refreshSessions();
177215
} else if (sse.status === "error" && sse.error) {
178216
const errorMsg: ChatMessage = {
179217
id: `e_${msgCounter.current++}`,

0 commit comments

Comments
 (0)