From cdea59af9276e520619c242f9636ce7d7cb6f260 Mon Sep 17 00:00:00 2001 From: Ding Shuo Date: Sat, 14 Mar 2026 21:37:43 +0800 Subject: [PATCH] Update PlanningAgent.tsx with history sidebar and session management features --- src/pages/PlanningAgent.tsx | 159 ++++++++++++++++++++++++++++- temp_output.txt | 198 ++++++++++++++++++++++++++++++++++++ 2 files changed, 356 insertions(+), 1 deletion(-) create mode 100644 temp_output.txt diff --git a/src/pages/PlanningAgent.tsx b/src/pages/PlanningAgent.tsx index 8de0614..ac0f8b1 100644 --- a/src/pages/PlanningAgent.tsx +++ b/src/pages/PlanningAgent.tsx @@ -20,12 +20,20 @@ type StreamEvent = { tool_name?: string; }; +type HistorySession = { + id: string; + sessionId: string; + summary: string; + ts: number; +}; + type State = { sessionId: string; userId: string; messages: ChatMessage[]; reasoning: Record; // Map message ID to its reasoning/traces chatting: boolean; + historySessions: HistorySession[]; error?: string; }; @@ -35,6 +43,8 @@ type Action = | { type: "update-reasoning"; id: string; content: string } | { type: "set-chatting"; v: boolean } | { type: "set-error"; v?: string } + | { type: "set-history"; sessions: HistorySession[] } + | { type: "load-session"; sessionId: string; messages: ChatMessage[] } | { type: "reset"; sessionId: string; userId: string }; /* ─── Helpers ─── */ @@ -47,6 +57,19 @@ function makeId(prefix: string) { return `${prefix}_${uid()}`; } +function getStoredHistory(): HistorySession[] { + try { + const raw = localStorage.getItem("safeos.planning.history"); + return raw ? JSON.parse(raw) : []; + } catch { + return []; + } +} + +function saveStoredHistory(sessions: HistorySession[]) { + localStorage.setItem("safeos.planning.history", JSON.stringify(sessions)); +} + function getSession() { try { const raw = localStorage.getItem("safeos.planning.session"); @@ -82,6 +105,18 @@ function reducer(state: State, action: Action): State { return { ...state, chatting: action.v }; case "set-error": return { ...state, error: action.v }; + case "set-history": + saveStoredHistory(action.sessions); + return { ...state, historySessions: action.sessions }; + case "load-session": + return { + ...state, + sessionId: action.sessionId, + messages: action.messages, + reasoning: {}, + chatting: false, + error: undefined, + }; case "reset": { localStorage.setItem( "safeos.planning.session", @@ -93,6 +128,7 @@ function reducer(state: State, action: Action): State { messages: [], reasoning: {}, chatting: false, + historySessions: state.historySessions, error: undefined, }; } @@ -175,11 +211,14 @@ export default function PlanningAgent() { messages: [], reasoning: {}, chatting: false, + historySessions: getStoredHistory(), }); const [input, setInput] = useState(""); const [files, setFiles] = useState<{ id: string; name: string }[]>([]); const [uploading, setUploading] = useState(false); + const [isHistoryOpen, setIsHistoryOpen] = useState(false); + const [loadingHistory, setLoadingHistory] = useState(false); const abortRef = useRef(null); const bottomRef = useRef(null); @@ -191,6 +230,32 @@ export default function PlanningAgent() { return () => abortRef.current?.abort(); }, []); + /* Load session */ + const loadHistorySession = async (sessionId: string) => { + if (loadingHistory) return; + setLoadingHistory(true); + try { + const res = await fetch(`${API.planning}/history?session_id=${sessionId}&limit=100`); + if (!res.ok) throw new Error(`Load history failed: ${res.status}`); + const data = await res.json(); + + const messages: ChatMessage[] = data.map((item: any) => ({ + id: item.ID.toString(), + role: item.Role.toLowerCase(), + content: item.Content, + ts: new Date(item.CreatedAt).getTime(), + status: "sent" + })); + + dispatch({ type: "load-session", sessionId, messages }); + setIsHistoryOpen(false); + } catch (err) { + dispatch({ type: "set-error", v: `加载历史失败: ${(err as Error).message}` }); + } finally { + setLoadingHistory(false); + } + }; + /* Send message */ const send = useCallback(async () => { const text = input.trim(); @@ -279,6 +344,23 @@ export default function PlanningAgent() { /* Reset */ const handleReset = () => { + if (state.messages.length > 0) { + const lastMsg = state.messages[state.messages.length - 1]; + const summary = lastMsg.role === "assistant" + ? lastMsg.content.slice(0, 50) + (lastMsg.content.length > 50 ? "..." : "") + : (state.messages[state.messages.length - 2]?.content.slice(0, 50) || "Empty session") + "..."; + + const newHistorySession: HistorySession = { + id: uid(), + sessionId: state.sessionId, + summary: summary || "新会话", + ts: Date.now(), + }; + + const updatedHistory = [newHistorySession, ...state.historySessions.filter(s => s.sessionId !== state.sessionId)]; + dispatch({ type: "set-history", sessions: updatedHistory }); + } + abortRef.current?.abort(); const fresh = { sessionId: makeId("sess"), userId: makeId("user") }; dispatch({ type: "reset", ...fresh }); @@ -286,10 +368,85 @@ export default function PlanningAgent() { }; return ( -
+
+ {/* ─── Sidebar Overlay ─── */} + {isHistoryOpen && ( +
setIsHistoryOpen(false)} + /> + )} + +
+
+
+

历史对话

+ +
+
+ {state.historySessions.length === 0 ? ( +
+ 暂无历史记录 +
+ ) : ( +
+ {state.historySessions.map((session) => ( +
loadHistorySession(session.sessionId)} + className={`p-4 rounded-xl border border-border cursor-pointer transition-all hover:bg-surface-muted hover:border-magenta group ${ + state.sessionId === session.sessionId ? 'bg-magenta/5 border-magenta ring-1 ring-magenta' : 'bg-white' + }`} + > +
+ + ID: {session.sessionId.slice(0, 8)}... + + + {new Date(session.ts).toLocaleDateString()} + +
+

+ {session.summary} +

+
+ ))} +
+ )} + {loadingHistory && ( +
+
+
+ )} +
+
+
+ {/* ─── Main Chat ─── */}
+ {/* History Toggle Button */} + +
{state.messages.length === 0 && (
diff --git a/temp_output.txt b/temp_output.txt new file mode 100644 index 0000000..f226c9b --- /dev/null +++ b/temp_output.txt @@ -0,0 +1,198 @@ + abortRef.current?.abort(); + const fresh = { sessionId: makeId("sess"), userId: makeId("user") }; + dispatch({ type: "reset", ...fresh }); + setFiles([]); + }; + + return ( +
+ {/* ─── Sidebar Overlay ─── */} + {isHistoryOpen && ( +
setIsHistoryOpen(false)} + /> + )} + +
+
+
+

历史对话

+ +
+
+ {state.historySessions.length === 0 ? ( +
+ 暂无历史记录 +
+ ) : ( +
+ {state.historySessions.map((session) => ( +
loadHistorySession(session.sessionId)} + className={`p-4 rounded-xl border border-border cursor-pointer transition-all hover:bg-surface-muted hover:border-magenta group ${ + state.sessionId === session.sessionId ? 'bg-magenta/5 border-magenta ring-1 ring-magenta' : 'bg-white' + }`} + > +
+ + ID: {session.sessionId.slice(0, 8)}... + + + {new Date(session.ts).toLocaleDateString()} + +
+

+ {session.summary} +

+
+ ))} +
+ )} + {loadingHistory && ( +
+
+
+ )} +
+
+
+ + {/* ─── Main Chat ─── */} +
+
+ {/* History Toggle Button */} + + +
+ {state.messages.length === 0 && ( +
+

规划委员会代理

+

+ 提供一个高层 Epic,代理将从产品、架构与 RTE 视角进行规划。 +

+
+ )} + + {state.messages.map((msg) => ( +
+ {msg.role === "assistant" && ( + 系统 / 迭代闭环 + )} + {msg.role === "assistant" && state.reasoning[msg.id] && ( +
+ + + 查看思考过程 + +
+ {state.reasoning[msg.id]} +
+
+ )} + {msg.role === "assistant" ? ( +
+ + {msg.content || (state.chatting ? "思考中..." : "")} + +
+ ) : ( + msg.content || "Thinking..." + )} + {msg.status === "failed" && ( + 发送失败 + )} +
+ ))} +
+
+
+ + {/* Error */} + {state.error && ( +
+ {state.error} + +
+ )} + + {/* Input area */} +
+ {/* File upload row */} +
+ + {files.map((f) => ( + {f.name} + ))} + +
+ +
+