"use client"; import { useCallback, useEffect, useRef, useState } from "react"; import { Maximize2Icon, Minimize2Icon, MinusIcon, RotateCwIcon, ShieldCheckIcon, XIcon, } from "lucide-react"; import { useShallow } from "zustand/react/shallow"; import { useChatStore } from "@/lib/store/chat-store"; import { closeConversation } from "@/lib/services/conversation"; import { bindHostBridge, requestHostClose, requestHostMinimize, requestHostToggleMaximize, } from "@/lib/widget/host-bridge"; import { ConnectionStatus } from "@/components/im/connection-status"; import { MessageEditor } from "@/components/im/message-editor"; import { MessageList, type MessageListHandle, } from "@/components/im/message-list"; export function ChatShell() { const messageListRef = useRef(null); const [isMaximized, setIsMaximized] = useState(false); const [isCloseDialogOpen, setIsCloseDialogOpen] = useState(false); const [isClosingConversation, setIsClosingConversation] = useState(false); const { title, subtitle, themeColor, conversation, messages, messagesHasMore, messagesLoadingMore, loadOlderMessages, status, error, isOpen, isVisible, setIsOpen, setIsVisible, bootstrap, handleSendMessage, uploadMessageImage, sendAttachment, retry, disconnectSocket, markConversationRead, } = useChatStore( useShallow((state) => ({ title: state.title, subtitle: state.subtitle, themeColor: state.themeColor, conversation: state.conversation, messages: state.messages, messagesHasMore: state.messagesHasMore, messagesLoadingMore: state.messagesLoadingMore, loadOlderMessages: state.loadOlderMessages, status: state.status, error: state.error, isOpen: state.isOpen, isVisible: state.isVisible, setIsOpen: state.setIsOpen, setIsVisible: state.setIsVisible, bootstrap: state.bootstrap, handleSendMessage: state.handleSendMessage, uploadMessageImage: state.uploadMessageImage, sendAttachment: state.sendAttachment, retry: state.retry, disconnectSocket: state.disconnectSocket, markConversationRead: state.markConversationRead, })), ); const maybeMarkConversationRead = useCallback(() => { if (!isVisible || !conversation || typeof document === "undefined") { return; } if (document.visibilityState !== "visible") { return; } void markConversationRead().catch((error) => { console.error("Failed to mark widget conversation read", error); }); }, [conversation, isVisible, markConversationRead]); useEffect(() => { return bindHostBridge({ onOpen: () => { setIsOpen(true); setIsVisible(true); }, onMinimize: () => { setIsVisible(false); }, onMaximizedChange: (nextIsMaximized) => { setIsMaximized(nextIsMaximized); }, }); }, [setIsOpen, setIsVisible]); useEffect(() => { bootstrap(); return () => { if (!isOpen) { disconnectSocket(); } }; }, [isOpen, bootstrap, disconnectSocket]); useEffect(() => { maybeMarkConversationRead(); }, [maybeMarkConversationRead, messages.length]); useEffect(() => { const handleVisibilityChange = () => { if (document.visibilityState === "visible") { maybeMarkConversationRead(); } }; const handleFocus = () => { maybeMarkConversationRead(); }; document.addEventListener("visibilitychange", handleVisibilityChange); window.addEventListener("focus", handleFocus); return () => { document.removeEventListener("visibilitychange", handleVisibilityChange); window.removeEventListener("focus", handleFocus); }; }, [maybeMarkConversationRead]); async function handleSend(content: string) { await handleSendMessage(content); messageListRef.current?.scrollToBottom(); } function handleMinimize() { setIsVisible(false); requestHostMinimize(); } function handleToggleMaximize() { requestHostToggleMaximize(); } async function confirmCloseConversation() { if (isClosingConversation) { return; } setIsClosingConversation(true); try { if (conversation?.id) { await closeConversation(conversation.id); } setIsCloseDialogOpen(false); requestHostClose(); } catch (closeError) { window.alert( closeError instanceof Error ? closeError.message : "关闭会话失败", ); } finally { setIsClosingConversation(false); } } useEffect(() => { if (!isCloseDialogOpen) { return; } const handleKeyDown = (event: KeyboardEvent) => { if (event.key === "Escape" && !isClosingConversation) { setIsCloseDialogOpen(false); } }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [isCloseDialogOpen, isClosingConversation]); return (
{title}
{subtitle}
会话加密传输
{error ? (
{error}
) : null}
{isCloseDialogOpen ? (
{/*
*/}
结束当前对话?
结束会话,客服将无法再查看您的消息记录,如需再次联系请重新发起对话。
) : null}
); }