"use client"; import React, { useCallback, useEffect, useRef, useState } from "react"; import { Box, Drawer, alpha, useTheme } from "@mui/material"; import type { AgentModel } from "@/lib/chatStream"; 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"; import { PRESET_PROMPTS } from "./GlobalChatbox.utils"; import { useSpeechRecognition, useSpeechSynthesis } from "./GlobalChatbox.voice"; import { useAgentChatSession } from "./hooks/useAgentChatSession"; import { useAgentToolActions } from "./hooks/useAgentToolActions"; export const GlobalChatbox: React.FC = ({ open, onClose }) => { const [input, setInput] = useState(""); const [width, setWidth] = useState(520); const [isResizing, setIsResizing] = useState(false); const [isHistoryOpen, setIsHistoryOpen] = useState(false); const [selectedModel, setSelectedModel] = useState( "deepseek/deepseek-v4-pro", ); const bottomRef = useRef(null); const inputRef = useRef(null); const theme = useTheme(); const { speechState, speakingMessageId, speak: handleSpeak, pause: handlePauseSpeech, resume: handleResumeSpeech, stop: handleStopSpeech, isSupported: isTtsSupported, } = useSpeechSynthesis(); const handleSpeechResult = useCallback((text: string) => { setInput((prev) => prev + text); }, []); const { isListening, start: startListening, stop: stopListening, isSupported: isSttSupported, } = useSpeechRecognition(handleSpeechResult); const handleToolCall = useAgentToolActions(); const { messages, chatSessions, activeStorageSessionId, branchGroups, branchTransition, isHydrating, isStreaming, sendPrompt, regenerate, editAndResubmit, cycleBranch, abort, createSession, removeSession, switchSession, } = useAgentChatSession({ onToolCall: handleToolCall, onBeforeSend: stopListening, getModel: () => selectedModel, }); useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages, isStreaming]); useEffect(() => { if (!open) return; const timer = window.setTimeout(() => { inputRef.current?.focus(); bottomRef.current?.scrollIntoView({ behavior: "auto" }); }, 0); return () => window.clearTimeout(timer); }, [open]); const handleSend = useCallback(() => { const prompt = input.trim(); if (!prompt || isStreaming) return; setInput(""); void sendPrompt(prompt); }, [input, isStreaming, sendPrompt]); const handlePresetPromptSelect = useCallback((prompt: string) => { setInput(prompt); window.setTimeout(() => { inputRef.current?.focus(); }, 0); }, []); const handleNewConversation = useCallback(() => { handleStopSpeech(); stopListening(); void createSession(); setInput(""); window.setTimeout(() => { inputRef.current?.focus(); }, 0); }, [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(); setIsResizing(true); }, []); useEffect(() => { const handleMouseMove = (event: MouseEvent) => { if (!isResizing) return; const newWidth = window.innerWidth - event.clientX; if (newWidth > 360 && newWidth < 1240) { setWidth(newWidth); } }; const handleMouseUp = () => { setIsResizing(false); }; if (isResizing) { window.addEventListener("mousemove", handleMouseMove); window.addEventListener("mouseup", handleMouseUp); } return () => { window.removeEventListener("mousemove", handleMouseMove); window.removeEventListener("mouseup", handleMouseUp); }; }, [isResizing]); return ( muiTheme.zIndex.modal + 100 }} PaperProps={{ sx: { width: { xs: "100%", sm: width }, background: "transparent", boxShadow: "none", overflow: open ? "visible" : "hidden", zIndex: (muiTheme) => muiTheme.zIndex.modal + 100, transition: isResizing ? "none" : undefined, }, }} > 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, }} /> { handleNewConversation(); setIsHistoryOpen(false); }} onSelectSession={(id) => { handleSelectSession(id); setIsHistoryOpen(false); }} onDeleteSession={handleDeleteSession} /> ); };