2025-02-26 18:06:35 +08:00
|
|
|
import {
|
|
|
|
|
forwardRef,
|
|
|
|
|
memo,
|
|
|
|
|
useCallback,
|
|
|
|
|
useEffect,
|
|
|
|
|
useImperativeHandle,
|
|
|
|
|
useRef,
|
|
|
|
|
useState,
|
|
|
|
|
} from "react";
|
|
|
|
|
|
|
|
|
|
import { useChatStore } from "@/stores/chatStore";
|
|
|
|
|
import { useConnectStore } from "@/stores/connectStore";
|
|
|
|
|
import { useSearchStore } from "@/stores/searchStore";
|
2025-03-11 11:02:30 +08:00
|
|
|
import { useWindows } from "@/hooks/useWindows";
|
2025-03-06 20:32:57 +08:00
|
|
|
import useMessageChunkData from "@/hooks/useMessageChunkData";
|
|
|
|
|
import useWebSocket from "@/hooks/useWebSocket";
|
2025-03-11 11:02:30 +08:00
|
|
|
import { useChatActions } from "@/hooks/useChatActions";
|
|
|
|
|
import { useMessageHandler } from "@/hooks/useMessageHandler";
|
|
|
|
|
import { ChatSidebar } from "./ChatSidebar";
|
|
|
|
|
import { ChatHeader } from "./ChatHeader";
|
|
|
|
|
import { ChatContent } from "./ChatContent";
|
|
|
|
|
import ConnectPrompt from "./ConnectPrompt";
|
|
|
|
|
import type { Chat } from "./types";
|
2025-02-21 18:57:32 +08:00
|
|
|
|
2024-11-20 10:08:08 +08:00
|
|
|
interface ChatAIProps {
|
2025-02-26 18:06:35 +08:00
|
|
|
isTransitioned: boolean;
|
|
|
|
|
isSearchActive?: boolean;
|
|
|
|
|
isDeepThinkActive?: boolean;
|
|
|
|
|
activeChatProp?: Chat;
|
|
|
|
|
changeInput?: (val: string) => void;
|
|
|
|
|
setIsSidebarOpen?: (value: boolean) => void;
|
|
|
|
|
isSidebarOpen?: boolean;
|
|
|
|
|
clearChatPage?: () => void;
|
2025-03-04 15:49:04 +08:00
|
|
|
isChatPage?: boolean;
|
2025-03-17 16:24:18 +08:00
|
|
|
getFileUrl: (path: string) => string;
|
2024-11-20 10:08:08 +08:00
|
|
|
}
|
|
|
|
|
|
2024-11-24 19:25:47 +08:00
|
|
|
export interface ChatAIRef {
|
2025-02-26 18:06:35 +08:00
|
|
|
init: (value: string) => void;
|
|
|
|
|
cancelChat: () => void;
|
|
|
|
|
reconnect: () => void;
|
|
|
|
|
clearChat: () => void;
|
2024-11-24 19:25:47 +08:00
|
|
|
}
|
2024-11-20 10:08:08 +08:00
|
|
|
|
2025-02-18 09:40:00 +08:00
|
|
|
const ChatAI = memo(
|
2025-02-26 18:06:35 +08:00
|
|
|
forwardRef<ChatAIRef, ChatAIProps>(
|
|
|
|
|
(
|
|
|
|
|
{
|
|
|
|
|
isTransitioned,
|
|
|
|
|
changeInput,
|
|
|
|
|
isSearchActive,
|
|
|
|
|
isDeepThinkActive,
|
|
|
|
|
activeChatProp,
|
|
|
|
|
setIsSidebarOpen,
|
|
|
|
|
isSidebarOpen = false,
|
|
|
|
|
clearChatPage,
|
2025-03-04 15:49:04 +08:00
|
|
|
isChatPage = false,
|
2025-03-17 16:24:18 +08:00
|
|
|
getFileUrl,
|
2025-02-26 18:06:35 +08:00
|
|
|
},
|
|
|
|
|
ref
|
|
|
|
|
) => {
|
|
|
|
|
if (!isTransitioned) return null;
|
|
|
|
|
|
|
|
|
|
useImperativeHandle(ref, () => ({
|
|
|
|
|
init: init,
|
2025-03-11 11:02:30 +08:00
|
|
|
cancelChat: () => cancelChat(activeChat),
|
2025-02-26 18:06:35 +08:00
|
|
|
reconnect: reconnect,
|
|
|
|
|
clearChat: clearChat,
|
|
|
|
|
}));
|
|
|
|
|
|
2025-03-02 21:44:21 +08:00
|
|
|
const { curChatEnd, setCurChatEnd, connected, setConnected } =
|
|
|
|
|
useChatStore();
|
|
|
|
|
|
2025-02-26 18:06:35 +08:00
|
|
|
const currentService = useConnectStore((state) => state.currentService);
|
|
|
|
|
|
|
|
|
|
const [activeChat, setActiveChat] = useState<Chat>();
|
|
|
|
|
const [timedoutShow, setTimedoutShow] = useState(false);
|
2025-03-11 11:02:30 +08:00
|
|
|
const [isLogin, setIsLogin] = useState(true);
|
2025-02-26 18:06:35 +08:00
|
|
|
|
|
|
|
|
const curIdRef = useRef("");
|
|
|
|
|
|
|
|
|
|
const [isSidebarOpenChat, setIsSidebarOpenChat] = useState(isSidebarOpen);
|
|
|
|
|
const [chats, setChats] = useState<Chat[]>([]);
|
|
|
|
|
const sourceDataIds = useSearchStore((state) => state.sourceDataIds);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
activeChatProp && setActiveChat(activeChatProp);
|
|
|
|
|
}, [activeChatProp]);
|
|
|
|
|
|
2025-03-02 21:44:21 +08:00
|
|
|
const [Question, setQuestion] = useState<string>("");
|
|
|
|
|
|
2025-03-30 19:33:49 +08:00
|
|
|
const [websocketSessionId, setWebsocketSessionId] = useState('');
|
|
|
|
|
|
|
|
|
|
const onWebsocketSessionId = useCallback((sessionId: string) => {
|
|
|
|
|
setWebsocketSessionId(sessionId);
|
|
|
|
|
}, []);
|
|
|
|
|
|
2025-03-06 20:32:57 +08:00
|
|
|
const {
|
|
|
|
|
data: {
|
|
|
|
|
query_intent,
|
|
|
|
|
fetch_source,
|
|
|
|
|
pick_source,
|
|
|
|
|
deep_read,
|
|
|
|
|
think,
|
|
|
|
|
response,
|
|
|
|
|
},
|
|
|
|
|
handlers,
|
|
|
|
|
clearAllChunkData,
|
|
|
|
|
} = useMessageChunkData();
|
|
|
|
|
|
|
|
|
|
const [loadingStep, setLoadingStep] = useState<Record<string, boolean>>({
|
|
|
|
|
query_intent: false,
|
|
|
|
|
fetch_source: false,
|
|
|
|
|
pick_source: false,
|
|
|
|
|
deep_read: false,
|
|
|
|
|
think: false,
|
|
|
|
|
response: false,
|
|
|
|
|
});
|
2025-03-02 21:44:21 +08:00
|
|
|
|
2025-03-11 11:02:30 +08:00
|
|
|
const dealMsgRef = useRef<((msg: string) => void) | null>(null);
|
2025-02-26 18:06:35 +08:00
|
|
|
|
2025-03-30 19:33:49 +08:00
|
|
|
const clientId = isChatPage ? "standalone" : "popup"
|
|
|
|
|
const { errorShow, setErrorShow, reconnect, disconnectWS, updateDealMsg } =
|
2025-03-11 11:02:30 +08:00
|
|
|
useWebSocket({
|
2025-03-30 19:33:49 +08:00
|
|
|
clientId,
|
2025-03-11 11:02:30 +08:00
|
|
|
connected,
|
|
|
|
|
setConnected,
|
|
|
|
|
currentService,
|
|
|
|
|
dealMsgRef,
|
2025-03-30 19:33:49 +08:00
|
|
|
onWebsocketSessionId,
|
2025-03-11 11:02:30 +08:00
|
|
|
});
|
2025-03-02 21:44:21 +08:00
|
|
|
|
2025-03-11 11:02:30 +08:00
|
|
|
const {
|
|
|
|
|
chatClose,
|
|
|
|
|
cancelChat,
|
|
|
|
|
chatHistory,
|
|
|
|
|
createNewChat,
|
|
|
|
|
handleSendMessage,
|
|
|
|
|
openSessionChat,
|
|
|
|
|
getChatHistory,
|
|
|
|
|
createChatWindow,
|
|
|
|
|
} = useChatActions(
|
|
|
|
|
currentService?.id,
|
|
|
|
|
setActiveChat,
|
|
|
|
|
setCurChatEnd,
|
|
|
|
|
setErrorShow,
|
|
|
|
|
setTimedoutShow,
|
|
|
|
|
clearAllChunkData,
|
|
|
|
|
setQuestion,
|
|
|
|
|
curIdRef,
|
|
|
|
|
isSearchActive,
|
|
|
|
|
isDeepThinkActive,
|
|
|
|
|
sourceDataIds,
|
2025-03-30 19:33:49 +08:00
|
|
|
changeInput,
|
|
|
|
|
websocketSessionId
|
2025-02-26 18:06:35 +08:00
|
|
|
);
|
|
|
|
|
|
2025-03-11 11:02:30 +08:00
|
|
|
const { dealMsg, messageTimeoutRef } = useMessageHandler(
|
|
|
|
|
curIdRef,
|
|
|
|
|
setCurChatEnd,
|
|
|
|
|
setTimedoutShow,
|
|
|
|
|
(chat) => cancelChat(chat || activeChat),
|
|
|
|
|
setLoadingStep,
|
2025-03-30 19:33:49 +08:00
|
|
|
handlers,
|
2025-02-26 18:06:35 +08:00
|
|
|
);
|
2025-02-24 10:26:39 +08:00
|
|
|
|
2025-02-26 18:06:35 +08:00
|
|
|
useEffect(() => {
|
2025-03-11 11:02:30 +08:00
|
|
|
if (dealMsg) {
|
|
|
|
|
dealMsgRef.current = dealMsg;
|
|
|
|
|
updateDealMsg && updateDealMsg(dealMsg);
|
|
|
|
|
}
|
|
|
|
|
}, [dealMsg, updateDealMsg]);
|
2025-02-26 18:06:35 +08:00
|
|
|
|
2025-03-11 11:02:30 +08:00
|
|
|
const clearChat = useCallback(() => {
|
2025-02-26 18:06:35 +08:00
|
|
|
console.log("clearChat");
|
2025-03-11 11:02:30 +08:00
|
|
|
setTimedoutShow(false);
|
|
|
|
|
setErrorShow(false);
|
|
|
|
|
chatClose(activeChat);
|
2025-02-26 18:06:35 +08:00
|
|
|
setActiveChat(undefined);
|
|
|
|
|
setCurChatEnd(true);
|
|
|
|
|
clearChatPage && clearChatPage();
|
2025-03-11 11:02:30 +08:00
|
|
|
}, [
|
|
|
|
|
activeChat,
|
|
|
|
|
chatClose,
|
|
|
|
|
clearChatPage,
|
|
|
|
|
setCurChatEnd,
|
|
|
|
|
setErrorShow,
|
|
|
|
|
setTimedoutShow,
|
|
|
|
|
]);
|
2025-02-26 18:06:35 +08:00
|
|
|
|
2025-03-11 11:02:30 +08:00
|
|
|
const init = useCallback(
|
|
|
|
|
(value: string) => {
|
|
|
|
|
if (!isLogin) return;
|
|
|
|
|
if (!curChatEnd) return;
|
|
|
|
|
if (!activeChat?._id) {
|
2025-03-30 19:33:49 +08:00
|
|
|
createNewChat(value, activeChat, websocketSessionId);
|
2025-03-11 11:02:30 +08:00
|
|
|
} else {
|
2025-03-30 19:33:49 +08:00
|
|
|
handleSendMessage(value, activeChat, websocketSessionId);
|
2025-02-26 18:06:35 +08:00
|
|
|
}
|
|
|
|
|
},
|
2025-03-30 19:33:49 +08:00
|
|
|
[isLogin, curChatEnd, activeChat, createNewChat, handleSendMessage, websocketSessionId]
|
2025-02-26 18:06:35 +08:00
|
|
|
);
|
|
|
|
|
|
2025-03-11 11:02:30 +08:00
|
|
|
const { createWin } = useWindows();
|
|
|
|
|
const openChatAI = useCallback(() => {
|
|
|
|
|
createChatWindow(createWin);
|
|
|
|
|
}, [createChatWindow, createWin]);
|
2025-02-26 18:06:35 +08:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
return () => {
|
|
|
|
|
if (messageTimeoutRef.current) {
|
|
|
|
|
clearTimeout(messageTimeoutRef.current);
|
|
|
|
|
}
|
2025-03-11 11:02:30 +08:00
|
|
|
chatClose(activeChat);
|
2025-02-26 18:06:35 +08:00
|
|
|
setActiveChat(undefined);
|
|
|
|
|
setCurChatEnd(true);
|
2025-03-30 19:33:49 +08:00
|
|
|
disconnectWS();
|
2025-02-26 18:06:35 +08:00
|
|
|
};
|
2025-03-11 11:02:30 +08:00
|
|
|
}, [chatClose, setCurChatEnd]);
|
2025-02-26 18:06:35 +08:00
|
|
|
|
2025-03-11 11:02:30 +08:00
|
|
|
const onSelectChat = useCallback(
|
|
|
|
|
async (chat: Chat) => {
|
2025-03-12 14:24:24 +08:00
|
|
|
setTimedoutShow(false);
|
|
|
|
|
setErrorShow(false);
|
2025-03-11 11:02:30 +08:00
|
|
|
clearAllChunkData();
|
|
|
|
|
await cancelChat(activeChat);
|
|
|
|
|
await chatClose(activeChat);
|
|
|
|
|
const response = await openSessionChat(chat);
|
|
|
|
|
if (response) {
|
|
|
|
|
chatHistory(response);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[
|
|
|
|
|
clearAllChunkData,
|
|
|
|
|
cancelChat,
|
|
|
|
|
activeChat,
|
|
|
|
|
chatClose,
|
|
|
|
|
openSessionChat,
|
|
|
|
|
chatHistory,
|
|
|
|
|
]
|
|
|
|
|
);
|
2025-02-26 18:06:35 +08:00
|
|
|
|
2025-03-11 11:02:30 +08:00
|
|
|
const deleteChat = useCallback((chatId: string) => {
|
2025-02-26 18:06:35 +08:00
|
|
|
setChats((prev) => prev.filter((chat) => chat._id !== chatId));
|
|
|
|
|
if (activeChat?._id === chatId) {
|
|
|
|
|
const remainingChats = chats.filter((chat) => chat._id !== chatId);
|
|
|
|
|
if (remainingChats.length > 0) {
|
|
|
|
|
setActiveChat(remainingChats[0]);
|
|
|
|
|
} else {
|
|
|
|
|
init("");
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-03-11 11:02:30 +08:00
|
|
|
}, [activeChat, chats, init, setActiveChat]);
|
2025-02-26 18:06:35 +08:00
|
|
|
|
|
|
|
|
const handleOutsideClick = useCallback((e: MouseEvent) => {
|
|
|
|
|
const sidebar = document.querySelector("[data-sidebar]");
|
|
|
|
|
const button = document.querySelector("[data-sidebar-button]");
|
|
|
|
|
if (
|
|
|
|
|
sidebar &&
|
|
|
|
|
!sidebar.contains(e.target as Node) &&
|
|
|
|
|
button &&
|
|
|
|
|
!button.contains(e.target as Node)
|
|
|
|
|
) {
|
|
|
|
|
setIsSidebarOpenChat(false);
|
|
|
|
|
}
|
|
|
|
|
}, []);
|
2025-02-26 17:48:43 +08:00
|
|
|
|
2025-02-26 18:06:35 +08:00
|
|
|
useEffect(() => {
|
|
|
|
|
if (isSidebarOpenChat) {
|
|
|
|
|
document.addEventListener("click", handleOutsideClick);
|
|
|
|
|
}
|
|
|
|
|
return () => {
|
|
|
|
|
document.removeEventListener("click", handleOutsideClick);
|
|
|
|
|
};
|
|
|
|
|
}, [isSidebarOpenChat, handleOutsideClick]);
|
|
|
|
|
|
2025-03-11 11:02:30 +08:00
|
|
|
const fetchChatHistory = useCallback(async () => {
|
|
|
|
|
const hits = await getChatHistory();
|
|
|
|
|
setChats(hits);
|
|
|
|
|
}, [getChatHistory]);
|
2025-02-26 18:06:35 +08:00
|
|
|
|
2025-03-07 12:11:29 +08:00
|
|
|
const setIsLoginChat = useCallback(
|
|
|
|
|
(value: boolean) => {
|
|
|
|
|
setIsLogin(value);
|
2025-03-11 11:02:30 +08:00
|
|
|
value && currentService && !setIsSidebarOpen && fetchChatHistory();
|
2025-03-07 12:11:29 +08:00
|
|
|
!value && setChats([]);
|
|
|
|
|
},
|
2025-03-11 11:02:30 +08:00
|
|
|
[currentService, setIsSidebarOpen, fetchChatHistory]
|
2025-03-07 12:11:29 +08:00
|
|
|
);
|
2025-02-26 18:06:35 +08:00
|
|
|
|
2025-03-11 11:02:30 +08:00
|
|
|
const toggleSidebar = useCallback(() => {
|
|
|
|
|
setIsSidebarOpenChat(!isSidebarOpenChat);
|
|
|
|
|
setIsSidebarOpen && setIsSidebarOpen(!isSidebarOpenChat);
|
|
|
|
|
!isSidebarOpenChat && fetchChatHistory();
|
|
|
|
|
}, [isSidebarOpenChat, setIsSidebarOpen, fetchChatHistory]);
|
|
|
|
|
|
2025-02-26 18:06:35 +08:00
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
data-tauri-drag-region
|
|
|
|
|
className={`h-full flex flex-col rounded-xl overflow-hidden`}
|
|
|
|
|
>
|
2025-03-11 11:02:30 +08:00
|
|
|
{!setIsSidebarOpen && (
|
|
|
|
|
<ChatSidebar
|
|
|
|
|
isSidebarOpen={isSidebarOpenChat}
|
|
|
|
|
chats={chats}
|
|
|
|
|
activeChat={activeChat}
|
|
|
|
|
onNewChat={clearChat}
|
|
|
|
|
onSelectChat={onSelectChat}
|
|
|
|
|
onDeleteChat={deleteChat}
|
|
|
|
|
fetchChatHistory={fetchChatHistory}
|
|
|
|
|
/>
|
2025-02-26 18:06:35 +08:00
|
|
|
)}
|
2025-03-11 11:02:30 +08:00
|
|
|
|
2025-02-26 18:06:35 +08:00
|
|
|
<ChatHeader
|
|
|
|
|
onCreateNewChat={clearChat}
|
|
|
|
|
onOpenChatAI={openChatAI}
|
2025-03-11 11:02:30 +08:00
|
|
|
setIsSidebarOpen={toggleSidebar}
|
2025-02-26 18:06:35 +08:00
|
|
|
isSidebarOpen={isSidebarOpenChat}
|
|
|
|
|
activeChat={activeChat}
|
2025-03-02 21:44:21 +08:00
|
|
|
reconnect={reconnect}
|
2025-03-04 15:49:04 +08:00
|
|
|
isChatPage={isChatPage}
|
2025-03-06 21:47:09 +08:00
|
|
|
setIsLogin={setIsLoginChat}
|
2025-02-26 18:06:35 +08:00
|
|
|
/>
|
2025-03-11 11:02:30 +08:00
|
|
|
{isLogin ? (
|
|
|
|
|
<ChatContent
|
|
|
|
|
activeChat={activeChat}
|
|
|
|
|
curChatEnd={curChatEnd}
|
|
|
|
|
query_intent={query_intent}
|
|
|
|
|
fetch_source={fetch_source}
|
|
|
|
|
pick_source={pick_source}
|
|
|
|
|
deep_read={deep_read}
|
|
|
|
|
think={think}
|
|
|
|
|
response={response}
|
|
|
|
|
loadingStep={loadingStep}
|
|
|
|
|
timedoutShow={timedoutShow}
|
|
|
|
|
errorShow={errorShow}
|
|
|
|
|
Question={Question}
|
2025-03-12 14:24:24 +08:00
|
|
|
handleSendMessage={(value) => handleSendMessage(value, activeChat)}
|
2025-03-17 16:24:18 +08:00
|
|
|
getFileUrl={getFileUrl}
|
2025-03-11 11:02:30 +08:00
|
|
|
/>
|
2025-03-05 21:36:05 +08:00
|
|
|
) : (
|
|
|
|
|
<ConnectPrompt />
|
|
|
|
|
)}
|
2025-02-26 18:06:35 +08:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
)
|
2025-02-18 09:40:00 +08:00
|
|
|
);
|
2024-11-20 10:08:08 +08:00
|
|
|
|
2024-11-24 19:25:47 +08:00
|
|
|
export default ChatAI;
|