重构聊天会话管理,支持会话历史和存储

This commit is contained in:
2026-04-30 15:02:08 +08:00
parent c5b0f43a0d
commit e0e78cd95a
11 changed files with 1247 additions and 221 deletions
+109 -47
View File
@@ -5,6 +5,7 @@ import { Box, Drawer, alpha, useTheme } from "@mui/material";
import { AgentComposer } from "./AgentComposer";
import { AgentHeader } from "./AgentHeader";
import { AgentHistoryPanel } from "./AgentHistoryPanel";
import { AgentWorkspace } from "./AgentWorkspace";
import { Blob } from "./GlobalChatbox.parts";
import type { Props } from "./GlobalChatbox.types";
@@ -17,7 +18,7 @@ export const GlobalChatbox: React.FC<Props> = ({ open, onClose }) => {
const [input, setInput] = useState("");
const [width, setWidth] = useState(520);
const [isResizing, setIsResizing] = useState(false);
const [headerMenuAnchorEl, setHeaderMenuAnchorEl] = useState<HTMLElement | null>(null);
const [isHistoryOpen, setIsHistoryOpen] = useState(false);
const bottomRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement | null>(null);
@@ -47,15 +48,20 @@ export const GlobalChatbox: React.FC<Props> = ({ open, onClose }) => {
const handleToolCall = useAgentToolActions();
const {
messages,
chatSessions,
activeStorageSessionId,
branchGroups,
branchTransition,
isHydrating,
isStreaming,
sendPrompt,
regenerate,
editAndResubmit,
cycleBranch,
abort,
reset,
createSession,
removeSession,
switchSession,
} = useAgentChatSession({
onToolCall: handleToolCall,
onBeforeSend: stopListening,
@@ -88,24 +94,34 @@ export const GlobalChatbox: React.FC<Props> = ({ open, onClose }) => {
}, 0);
}, []);
const handleHeaderMenuOpen = useCallback((event: React.MouseEvent<HTMLElement>) => {
setHeaderMenuAnchorEl(event.currentTarget);
}, []);
const handleHeaderMenuClose = useCallback(() => {
setHeaderMenuAnchorEl(null);
}, []);
const handleNewConversation = useCallback(() => {
handleStopSpeech();
stopListening();
reset();
void createSession();
setInput("");
handleHeaderMenuClose();
window.setTimeout(() => {
inputRef.current?.focus();
}, 0);
}, [handleHeaderMenuClose, handleStopSpeech, reset, stopListening]);
}, [createSession, handleStopSpeech, stopListening]);
const handleHistoryToggle = useCallback(() => {
setIsHistoryOpen((prev) => !prev);
}, []);
const handleSelectSession = useCallback(
(storageSessionId: string) => {
setInput("");
void switchSession(storageSessionId);
},
[switchSession],
);
const handleDeleteSession = useCallback(
(storageSessionId: string) => {
void removeSession(storageSessionId);
},
[removeSession],
);
const handleMouseDown = useCallback((event: React.MouseEvent) => {
event.preventDefault();
@@ -198,45 +214,91 @@ export const GlobalChatbox: React.FC<Props> = ({ open, onClose }) => {
<AgentHeader
isStreaming={isStreaming}
menuAnchorEl={headerMenuAnchorEl}
onMenuOpen={handleHeaderMenuOpen}
onMenuClose={handleHeaderMenuClose}
isHistoryOpen={isHistoryOpen}
onHistoryToggle={handleHistoryToggle}
onNewConversation={handleNewConversation}
onClose={onClose}
/>
<AgentWorkspace
messages={messages}
branchGroups={branchGroups}
branchTransition={branchTransition}
isStreaming={isStreaming}
bottomRef={bottomRef}
speakingMessageId={speakingMessageId}
speechState={speechState}
onSpeak={handleSpeak}
onPauseSpeech={handlePauseSpeech}
onResumeSpeech={handleResumeSpeech}
onStopSpeech={handleStopSpeech}
isTtsSupported={isTtsSupported}
onRegenerate={regenerate}
onEditResubmit={editAndResubmit}
onCycleBranch={cycleBranch}
/>
<Box sx={{ flex: 1, display: "flex", minHeight: 0, position: "relative", overflow: "hidden" }}>
<Box
onClick={() => setIsHistoryOpen(false)}
sx={{
position: "absolute",
inset: 0,
bgcolor: alpha("#000", 0.05),
backdropFilter: "blur(2px)",
opacity: isHistoryOpen ? 1 : 0,
pointerEvents: isHistoryOpen ? "auto" : "none",
transition: "opacity 0.3s ease",
zIndex: 10,
}}
/>
<Box
sx={{
position: "absolute",
top: 0,
bottom: 0,
left: 0,
width: 268,
zIndex: 20,
transform: isHistoryOpen ? "translateX(0)" : "translateX(-100%)",
transition: "transform 0.3s cubic-bezier(0.2, 0.8, 0.2, 1)",
boxShadow: isHistoryOpen ? `4px 0 24px ${alpha("#000", 0.08)}` : "none",
}}
>
<AgentHistoryPanel
sessions={chatSessions}
activeSessionId={activeStorageSessionId}
isHydrating={isHydrating}
onNewSession={() => {
handleNewConversation();
setIsHistoryOpen(false);
}}
onSelectSession={(id) => {
handleSelectSession(id);
setIsHistoryOpen(false);
}}
onDeleteSession={handleDeleteSession}
/>
</Box>
<AgentComposer
input={input}
inputRef={inputRef}
isStreaming={isStreaming}
isListening={isListening}
isSttSupported={isSttSupported}
presets={PRESET_PROMPTS}
onInputChange={setInput}
onSend={handleSend}
onAbort={abort}
onStartListening={startListening}
onStopListening={stopListening}
onPresetSelect={handlePresetPromptSelect}
/>
<Box sx={{ flex: 1, display: "flex", minWidth: 0, flexDirection: "column" }}>
<AgentWorkspace
messages={messages}
branchGroups={branchGroups}
branchTransition={branchTransition}
isStreaming={isStreaming}
bottomRef={bottomRef}
speakingMessageId={speakingMessageId}
speechState={speechState}
onSpeak={handleSpeak}
onPauseSpeech={handlePauseSpeech}
onResumeSpeech={handleResumeSpeech}
onStopSpeech={handleStopSpeech}
isTtsSupported={isTtsSupported}
onRegenerate={regenerate}
onEditResubmit={editAndResubmit}
onCycleBranch={cycleBranch}
/>
<AgentComposer
input={input}
inputRef={inputRef}
isHydrating={isHydrating}
isStreaming={isStreaming}
isListening={isListening}
isSttSupported={isSttSupported}
presets={PRESET_PROMPTS}
onInputChange={setInput}
onSend={handleSend}
onAbort={abort}
onStartListening={startListening}
onStopListening={stopListening}
onPresetSelect={handlePresetPromptSelect}
/>
</Box>
</Box>
</Box>
</Drawer>
);