From afa15f75a162b2a0b6517499b9de3ff6e1a3e6d2 Mon Sep 17 00:00:00 2001 From: Medcl Date: Wed, 26 Feb 2025 12:48:54 +0800 Subject: [PATCH] refactor: remove websocket_session_id from message request (#206) * refactor: remove websocket_session_id from message request * chore: update relese notes --- docs/content.en/docs/release-notes/_index.md | 2 + src-tauri/src/assistant/mod.rs | 9 +- src-tauri/src/common/datasource.rs | 7 +- src/components/Assistant/Chat.tsx | 1122 +++++++++--------- 4 files changed, 565 insertions(+), 575 deletions(-) diff --git a/docs/content.en/docs/release-notes/_index.md b/docs/content.en/docs/release-notes/_index.md index 0671365a..2981b401 100644 --- a/docs/content.en/docs/release-notes/_index.md +++ b/docs/content.en/docs/release-notes/_index.md @@ -23,6 +23,8 @@ Information about release notes of Coco Server is provided here. - Init icons in background during start #176 - Refactoring health api #187 - Refactoring assistant api #195 +- Refactor: remove websocket_session_id from message request #206 + diff --git a/src-tauri/src/assistant/mod.rs b/src-tauri/src/assistant/mod.rs index 88096e51..d42b2062 100644 --- a/src-tauri/src/assistant/mod.rs +++ b/src-tauri/src/assistant/mod.rs @@ -157,7 +157,6 @@ pub async fn send_message( app_handle: AppHandle, server_id: String, session_id: String, - websocket_id: String, message: String, query_params: Option>, //search,deep_thinking ) -> Result { @@ -165,19 +164,17 @@ pub async fn send_message( let msg = ChatRequestMessage { message: Some(message), }; - let mut headers = HashMap::new(); - headers.insert("WEBSOCKET-SESSION-ID".to_string(), websocket_id); let body = reqwest::Body::from(serde_json::to_string(&msg).unwrap()); let response = HttpClient::advanced_post( &server_id, path.as_str(), - Some(headers), + None, query_params, Some(body), ) - .await - .map_err(|e| format!("Error cancel session: {}", e))?; + .await + .map_err(|e| format!("Error cancel session: {}", e))?; handle_raw_response(response).await? } diff --git a/src-tauri/src/common/datasource.rs b/src-tauri/src/common/datasource.rs index efd36a9f..60ddc5cb 100644 --- a/src-tauri/src/common/datasource.rs +++ b/src-tauri/src/common/datasource.rs @@ -1,10 +1,11 @@ -use serde::{Deserialize, Serialize}; use crate::common::connector::Connector; +use serde::{Deserialize, Serialize}; // The DataSource struct representing a datasource, which includes the Connector -#[derive(Debug,Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct DataSource { pub id: String, + pub icon: Option, pub created: Option, pub updated: Option, pub r#type: Option, // Using 'r#type' to escape the reserved keyword 'type' @@ -13,7 +14,7 @@ pub struct DataSource { pub connector_info: Option, } -#[derive(Debug,Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct ConnectorConfig { pub id: Option, pub config: Option, // Using serde_json::Value to handle any type of config diff --git a/src/components/Assistant/Chat.tsx b/src/components/Assistant/Chat.tsx index 7fd93018..a9dd5f47 100644 --- a/src/components/Assistant/Chat.tsx +++ b/src/components/Assistant/Chat.tsx @@ -1,615 +1,605 @@ -import { - forwardRef, - memo, - useCallback, - useEffect, - useImperativeHandle, - useRef, - useState, - useMemo, -} from "react"; -import { isTauri } from "@tauri-apps/api/core"; -import { useTranslation } from "react-i18next"; -import { debounce } from "lodash-es"; -import { listen } from "@tauri-apps/api/event"; -import { invoke } from "@tauri-apps/api/core"; +import {forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState,} from "react"; +import {invoke, isTauri} from "@tauri-apps/api/core"; +import {useTranslation} from "react-i18next"; +import {debounce} from "lodash-es"; +import {listen} from "@tauri-apps/api/event"; -import { ChatMessage } from "./ChatMessage"; -import type { Chat } from "./types"; -import { useChatStore } from "@/stores/chatStore"; -import { useWindows } from "@/hooks/useWindows"; -import { ChatHeader } from "./ChatHeader"; -import { Sidebar } from "@/components/Assistant/Sidebar"; -import { useConnectStore } from "@/stores/connectStore"; +import {ChatMessage} from "./ChatMessage"; +import type {Chat} from "./types"; +import {useChatStore} from "@/stores/chatStore"; +import {useWindows} from "@/hooks/useWindows"; +import {ChatHeader} from "./ChatHeader"; +import {Sidebar} from "@/components/Assistant/Sidebar"; +import {useConnectStore} from "@/stores/connectStore"; interface ChatAIProps { - isTransitioned: boolean; - isSearchActive?: boolean; - isDeepThinkActive?: boolean; - activeChatProp?: Chat; - changeInput?: (val: string) => void; - setIsSidebarOpen?: (value: boolean) => void; - isSidebarOpen?: boolean; - clearChatPage?: () => void; + isTransitioned: boolean; + isSearchActive?: boolean; + isDeepThinkActive?: boolean; + activeChatProp?: Chat; + changeInput?: (val: string) => void; + setIsSidebarOpen?: (value: boolean) => void; + isSidebarOpen?: boolean; + clearChatPage?: () => void; } export interface ChatAIRef { - init: (value: string) => void; - cancelChat: () => void; - connected: boolean; - reconnect: () => void; - handleSendMessage: (value: string) => void; - clearChat: () => void; + init: (value: string) => void; + cancelChat: () => void; + connected: boolean; + reconnect: () => void; + handleSendMessage: (value: string) => void; + clearChat: () => void; } const ChatAI = memo( - forwardRef( - ( - { - isTransitioned, - changeInput, - isSearchActive, - isDeepThinkActive, - activeChatProp, - setIsSidebarOpen, - isSidebarOpen = false, - clearChatPage, - }, - ref - ) => { - if (!isTransitioned) return null; + forwardRef( + ( + { + isTransitioned, + changeInput, + isSearchActive, + isDeepThinkActive, + activeChatProp, + setIsSidebarOpen, + isSidebarOpen = false, + clearChatPage, + }, + ref + ) => { + if (!isTransitioned) return null; - const { t } = useTranslation(); + const {t} = useTranslation(); - useImperativeHandle(ref, () => ({ - init: init, - cancelChat: cancelChat, - connected: connected, - reconnect: reconnect, - handleSendMessage: handleSendMessage, - clearChat: clearChat, - })); + useImperativeHandle(ref, () => ({ + init: init, + cancelChat: cancelChat, + connected: connected, + reconnect: reconnect, + handleSendMessage: handleSendMessage, + clearChat: clearChat, + })); - const { createWin } = useWindows(); + const {createWin} = useWindows(); - const { - curChatEnd, - setCurChatEnd, - connected, - setConnected, - messages, - setMessages, - } = useChatStore(); - const currentService = useConnectStore((state) => state.currentService); - - const [activeChat, setActiveChat] = useState(); - const [isTyping, setIsTyping] = useState(false); - const [timedoutShow, setTimedoutShow] = useState(false); - const messagesEndRef = useRef(null); - - const [curMessage, setCurMessage] = useState(""); - - const websocketIdRef = useRef(""); - - const curChatEndRef = useRef(curChatEnd); - curChatEndRef.current = curChatEnd; - - const curIdRef = useRef(""); - - const [isSidebarOpenChat, setIsSidebarOpenChat] = useState(isSidebarOpen); - const [chats, setChats] = useState([]); - - useEffect(() => { - activeChatProp && setActiveChat(activeChatProp); - }, [activeChatProp]); - - const handleMessageChunk = useCallback((chunk: string) => { - setCurMessage((prev) => prev + chunk); - }, []); - - const reconnect = async () => { - if (!currentService?.id) return; - try { - await invoke("connect_to_server", { id: currentService?.id }); - setConnected(true); - } catch (error) { - console.error("Failed to connect:", error); - } - }; - - const messageTimeoutRef = useRef(); - - const dealMsg = useCallback( - (msg: string) => { - // console.log("msg:", msg); - if (messageTimeoutRef.current) { - clearTimeout(messageTimeoutRef.current); - } - - if (msg.includes("websocket-session-id")) { - const array = msg.split(" "); - websocketIdRef.current = array[2]; - return ""; - } else if (msg.includes("PRIVATE")) { - messageTimeoutRef.current = setTimeout(() => { - if (!curChatEnd && isTyping) { - console.log("AI response timeout"); - setTimedoutShow(true); - cancelChat(); - } - }, 30000); - - if (msg.includes("assistant finished output")) { - if (messageTimeoutRef.current) { - clearTimeout(messageTimeoutRef.current); - } - console.log("AI finished output"); - setCurChatEnd(true); - } else { - const cleanedData = msg.replace(/^PRIVATE /, ""); - try { - // console.log("cleanedData", cleanedData); - const chunkData = JSON.parse(cleanedData); - if (chunkData.reply_to_message === curIdRef.current) { - handleMessageChunk(chunkData.message_chunk); - return chunkData.message_chunk; - } - } catch (error) { - console.error("parse error:", error); - } - } - } - }, - [curChatEnd, isTyping] - ); - - useEffect(() => { - const unlisten = listen("ws-message", (event) => { - const data = dealMsg(String(event.payload)); - if (data) { - setMessages((prev) => prev + data); - } - }); - - return () => { - unlisten.then((fn) => fn()); - }; - }, []); - - const assistantMessage = useMemo(() => { - if (!activeChat?._id || (!curMessage && !messages)) return null; - return { - _id: activeChat._id, - _source: { - type: "assistant", - message: curMessage || messages, - }, - }; - }, [activeChat?._id, curMessage, messages]); - - const updatedChat = useMemo(() => { - if (!activeChat?._id || !assistantMessage) return null; - return { - ...activeChat, - messages: [...(activeChat.messages || []), assistantMessage], - }; - }, [activeChat, assistantMessage]); - - const simulateAssistantResponse = useCallback(() => { - if (!updatedChat) return; - - console.log("updatedChat:", updatedChat); - setActiveChat(updatedChat); - setMessages(""); - setCurMessage(""); - setIsTyping(false); - }, [updatedChat]); - - useEffect(() => { - if (curChatEnd) { - simulateAssistantResponse(); - } - }, [curChatEnd]); - - const [userScrolling, setUserScrolling] = useState(false); - const scrollTimeoutRef = useRef(); - - const scrollToBottom = useCallback( - debounce(() => { - if (!userScrolling) { - const container = messagesEndRef.current?.parentElement; - if (container) { - container.scrollTo({ - top: container.scrollHeight, - behavior: "smooth", - }); - } - } - }, 100), - [userScrolling] - ); - - useEffect(() => { - const container = messagesEndRef.current?.parentElement; - if (!container) return; - - const handleScroll = () => { - if (scrollTimeoutRef.current) { - clearTimeout(scrollTimeoutRef.current); - } - - const { scrollTop, scrollHeight, clientHeight } = container; - const isAtBottom = - Math.abs(scrollHeight - scrollTop - clientHeight) < 10; - - setUserScrolling(!isAtBottom); - - if (isAtBottom) { - setUserScrolling(false); - } - - scrollTimeoutRef.current = setTimeout(() => { const { - scrollTop: newScrollTop, - scrollHeight: newScrollHeight, - clientHeight: newClientHeight, - } = container; - const nowAtBottom = - Math.abs(newScrollHeight - newScrollTop - newClientHeight) < 10; - if (nowAtBottom) { - setUserScrolling(false); - } - }, 500); - }; + curChatEnd, + setCurChatEnd, + connected, + setConnected, + messages, + setMessages, + } = useChatStore(); + const currentService = useConnectStore((state) => state.currentService); - container.addEventListener("scroll", handleScroll); - return () => { - container.removeEventListener("scroll", handleScroll); - if (scrollTimeoutRef.current) { - clearTimeout(scrollTimeoutRef.current); - } - }; - }, []); + const [activeChat, setActiveChat] = useState(); + const [isTyping, setIsTyping] = useState(false); + const [timedoutShow, setTimedoutShow] = useState(false); + const messagesEndRef = useRef(null); - useEffect(() => { - scrollToBottom(); - }, [activeChat?.messages, isTyping, curMessage]); + const [curMessage, setCurMessage] = useState(""); - const clearChat = () => { - chatClose(); - setActiveChat(undefined); - clearChatPage && clearChatPage(); - } - - const createNewChat = useCallback(async (value: string = "") => { - chatClose(); - try { - let response: any = await invoke("new_chat", { - serverId: currentService?.id, - message: value, - }); - console.log("_new", response); - const newChat: Chat = response; + const websocketIdRef = useRef(""); - setActiveChat(newChat); - handleSendMessage(value, newChat); - } catch (error) { - console.error("Failed to fetch user data:", error); - } - }, []); + const curChatEndRef = useRef(curChatEnd); + curChatEndRef.current = curChatEnd; - const init = (value: string) => { - if (!curChatEnd) return; - if (!activeChat?._id) { - createNewChat(value); - } else { - handleSendMessage(value); - } - }; + const curIdRef = useRef(""); - const handleSendMessage = useCallback( - async (content: string, newChat?: Chat) => { - newChat = newChat || activeChat; - if (!newChat?._id || !content) return; - setTimedoutShow(false); - try { - let response: any = await invoke("send_message", { - serverId: currentService?.id, - sessionId: newChat?._id, - websocketId: websocketIdRef.current, - query_params: { - search: isSearchActive, - deep_thinking: isDeepThinkActive, - }, - message: content, - }); - response = JSON.parse(response || "") - console.log("_send", response, websocketIdRef.current); - curIdRef.current = response[0]?._id; + const [isSidebarOpenChat, setIsSidebarOpenChat] = useState(isSidebarOpen); + const [chats, setChats] = useState([]); - const updatedChat: Chat = { - ...newChat, - messages: [ - ...(newChat?.messages || []), - ...(response || []), - ], + useEffect(() => { + activeChatProp && setActiveChat(activeChatProp); + }, [activeChatProp]); + + const handleMessageChunk = useCallback((chunk: string) => { + setCurMessage((prev) => prev + chunk); + }, []); + + const reconnect = async () => { + if (!currentService?.id) return; + try { + await invoke("connect_to_server", {id: currentService?.id}); + setConnected(true); + } catch (error) { + console.error("Failed to connect:", error); + } }; - changeInput && changeInput(""); - console.log("updatedChat2", updatedChat); - setActiveChat(updatedChat); - setIsTyping(true); - setCurChatEnd(false); - } catch (error) { - console.error("Failed to fetch user data:", error); - } - }, - [activeChat, isSearchActive, isDeepThinkActive] - ); + const messageTimeoutRef = useRef(); - const chatClose = async () => { - if (!activeChat?._id) return; - try { - let response: any = await invoke("close_session_chat", { - serverId: currentService?.id, - sessionId: activeChat?._id, - }); - response = JSON.parse(response || "") - console.log("_close", response); - } catch (error) { - console.error("Failed to fetch user data:", error); - } - }; + const dealMsg = useCallback( + (msg: string) => { + // console.log("msg:", msg); + if (messageTimeoutRef.current) { + clearTimeout(messageTimeoutRef.current); + } - const cancelChat = async () => { - if (curMessage || messages) { - simulateAssistantResponse(); - } + if (msg.includes("websocket-session-id")) { + const array = msg.split(" "); + websocketIdRef.current = array[2]; + return ""; + } else if (msg.includes("PRIVATE")) { + messageTimeoutRef.current = setTimeout(() => { + if (!curChatEnd && isTyping) { + console.log("AI response timeout"); + setTimedoutShow(true); + cancelChat(); + } + }, 30000); - setCurChatEnd(true); - setIsTyping(false); - if (!activeChat?._id) return; - try { - let response: any = await invoke("cancel_session_chat", { - serverId: currentService?.id, - sessionId: activeChat?._id, - }); - response = JSON.parse(response || "") - console.log("_cancel", response); - } catch (error) { - console.error("Failed to fetch user data:", error); - } - }; + if (msg.includes("assistant finished output")) { + if (messageTimeoutRef.current) { + clearTimeout(messageTimeoutRef.current); + } + console.log("AI finished output"); + setCurChatEnd(true); + } else { + const cleanedData = msg.replace(/^PRIVATE /, ""); + try { + // console.log("cleanedData", cleanedData); + const chunkData = JSON.parse(cleanedData); + if (chunkData.reply_to_message === curIdRef.current) { + handleMessageChunk(chunkData.message_chunk); + return chunkData.message_chunk; + } + } catch (error) { + console.error("parse error:", error); + } + } + } + }, + [curChatEnd, isTyping] + ); - async function openChatAI() { - if (isTauri()) { - createWin && - createWin({ - label: "chat", - title: "Coco Chat", - dragDropEnabled: true, - center: true, - width: 1000, - height: 800, - alwaysOnTop: false, - skipTaskbar: false, - decorations: true, - closable: true, - url: "/ui/chat", - }); - } - } + useEffect(() => { + const unlisten = listen("ws-message", (event) => { + const data = dealMsg(String(event.payload)); + if (data) { + setMessages((prev) => prev + data); + } + }); - useEffect(() => { - return () => { - if (messageTimeoutRef.current) { - clearTimeout(messageTimeoutRef.current); - } - chatClose(); - setMessages(""); - setCurMessage(""); - setActiveChat(undefined); - setIsTyping(false); - setCurChatEnd(true); - scrollToBottom.cancel(); - }; - }, []); + return () => { + unlisten.then((fn) => fn()); + }; + }, []); - const chatHistory = async (chat: Chat) => { - try { - let response: any = await invoke("session_chat_history", { - serverId: currentService?.id, - sessionId: chat?._id, - from: 0, - size: 20, - }); - response = JSON.parse(response || "") - console.log("id_history", response); - const hits = response?.hits?.hits || []; - const updatedChat: Chat = { - ...chat, - messages: hits, - }; - setActiveChat(updatedChat); - } catch (error) { - console.error("Failed to fetch user data:", error); - } - }; + const assistantMessage = useMemo(() => { + if (!activeChat?._id || (!curMessage && !messages)) return null; + return { + _id: activeChat._id, + _source: { + type: "assistant", + message: curMessage || messages, + }, + }; + }, [activeChat?._id, curMessage, messages]); - const onSelectChat = async (chat: any) => { - chatClose(); - try { - let response: any = await invoke("open_session_chat", { - serverId: currentService?.id, - sessionId: chat?._id, - }); - response = JSON.parse(response || "") - console.log("_open", response); - chatHistory(response); - } catch (error) { - console.error("Failed to fetch user data:", error); - } - }; + const updatedChat = useMemo(() => { + if (!activeChat?._id || !assistantMessage) return null; + return { + ...activeChat, + messages: [...(activeChat.messages || []), assistantMessage], + }; + }, [activeChat, assistantMessage]); - const deleteChat = (chatId: string) => { - 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(""); - } - } - }; + const simulateAssistantResponse = useCallback(() => { + if (!updatedChat) return; - 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); - } - }, []); + console.log("updatedChat:", updatedChat); + setActiveChat(updatedChat); + setMessages(""); + setCurMessage(""); + setIsTyping(false); + }, [updatedChat]); - useEffect(() => { - if (isSidebarOpenChat) { - document.addEventListener("click", handleOutsideClick); - } - return () => { - document.removeEventListener("click", handleOutsideClick); - }; - }, [isSidebarOpenChat, handleOutsideClick]); + useEffect(() => { + if (curChatEnd) { + simulateAssistantResponse(); + } + }, [curChatEnd]); - const getChatHistory = async () => { - if (!currentService?.id) return; - try { - let response: any = await invoke("chat_history", { - serverId: currentService?.id, - from: 0, - size: 20, - }); - response = JSON.parse(response || "") - console.log("_history", response); - const hits = response?.hits?.hits || []; - setChats(hits); - if (hits[0]) { - onSelectChat(hits[0]); - } else { - init(""); - } - } catch (error) { - console.error("Failed to fetch user data:", error); - } - }; + const [userScrolling, setUserScrolling] = useState(false); + const scrollTimeoutRef = useRef(); - useEffect(() => { - currentService && !setIsSidebarOpen && getChatHistory(); - }, [currentService]); + const scrollToBottom = useCallback( + debounce(() => { + if (!userScrolling) { + const container = messagesEndRef.current?.parentElement; + if (container) { + container.scrollTo({ + top: container.scrollHeight, + behavior: "smooth", + }); + } + } + }, 100), + [userScrolling] + ); - return ( -
- {setIsSidebarOpen ? null : ( -
{ + const container = messagesEndRef.current?.parentElement; + if (!container) return; + + const handleScroll = () => { + if (scrollTimeoutRef.current) { + clearTimeout(scrollTimeoutRef.current); + } + + const {scrollTop, scrollHeight, clientHeight} = container; + const isAtBottom = + Math.abs(scrollHeight - scrollTop - clientHeight) < 10; + + setUserScrolling(!isAtBottom); + + if (isAtBottom) { + setUserScrolling(false); + } + + scrollTimeoutRef.current = setTimeout(() => { + const { + scrollTop: newScrollTop, + scrollHeight: newScrollHeight, + clientHeight: newClientHeight, + } = container; + const nowAtBottom = + Math.abs(newScrollHeight - newScrollTop - newClientHeight) < 10; + if (nowAtBottom) { + setUserScrolling(false); + } + }, 500); + }; + + container.addEventListener("scroll", handleScroll); + return () => { + container.removeEventListener("scroll", handleScroll); + if (scrollTimeoutRef.current) { + clearTimeout(scrollTimeoutRef.current); + } + }; + }, []); + + useEffect(() => { + scrollToBottom(); + }, [activeChat?.messages, isTyping, curMessage]); + + const clearChat = () => { + chatClose(); + setActiveChat(undefined); + clearChatPage && clearChatPage(); + } + + const createNewChat = useCallback(async (value: string = "") => { + chatClose(); + try { + let response: any = await invoke("new_chat", { + serverId: currentService?.id, + message: value, + }); + console.log("_new", response); + const newChat: Chat = response; + + setActiveChat(newChat); + handleSendMessage(value, newChat); + } catch (error) { + console.error("Failed to fetch user data:", error); + } + }, []); + + const init = (value: string) => { + if (!curChatEnd) return; + if (!activeChat?._id) { + createNewChat(value); + } else { + handleSendMessage(value); + } + }; + + const handleSendMessage = useCallback( + async (content: string, newChat?: Chat) => { + newChat = newChat || activeChat; + if (!newChat?._id || !content) return; + setTimedoutShow(false); + try { + let response: any = await invoke("send_message", { + serverId: currentService?.id, + sessionId: newChat?._id, + query_params: { + search: isSearchActive, + deep_thinking: isDeepThinkActive, + }, + message: content, + }); + response = JSON.parse(response || "") + console.log("_send", response, websocketIdRef.current); + curIdRef.current = response[0]?._id; + + const updatedChat: Chat = { + ...newChat, + messages: [ + ...(newChat?.messages || []), + ...(response || []), + ], + }; + + changeInput && changeInput(""); + console.log("updatedChat2", updatedChat); + setActiveChat(updatedChat); + setIsTyping(true); + setCurChatEnd(false); + } catch (error) { + console.error("Failed to fetch user data:", error); + } + }, + [activeChat, isSearchActive, isDeepThinkActive] + ); + + const chatClose = async () => { + if (!activeChat?._id) return; + try { + let response: any = await invoke("close_session_chat", { + serverId: currentService?.id, + sessionId: activeChat?._id, + }); + response = JSON.parse(response || "") + console.log("_close", response); + } catch (error) { + console.error("Failed to fetch user data:", error); + } + }; + + const cancelChat = async () => { + if (curMessage || messages) { + simulateAssistantResponse(); + } + + setCurChatEnd(true); + setIsTyping(false); + if (!activeChat?._id) return; + try { + let response: any = await invoke("cancel_session_chat", { + serverId: currentService?.id, + sessionId: activeChat?._id, + }); + response = JSON.parse(response || "") + console.log("_cancel", response); + } catch (error) { + console.error("Failed to fetch user data:", error); + } + }; + + async function openChatAI() { + if (isTauri()) { + createWin && + createWin({ + label: "chat", + title: "Coco Chat", + dragDropEnabled: true, + center: true, + width: 1000, + height: 800, + alwaysOnTop: false, + skipTaskbar: false, + decorations: true, + closable: true, + url: "/ui/chat", + }); + } + } + + useEffect(() => { + return () => { + if (messageTimeoutRef.current) { + clearTimeout(messageTimeoutRef.current); + } + chatClose(); + setMessages(""); + setCurMessage(""); + setActiveChat(undefined); + setIsTyping(false); + setCurChatEnd(true); + scrollToBottom.cancel(); + }; + }, []); + + const chatHistory = async (chat: Chat) => { + try { + let response: any = await invoke("session_chat_history", { + serverId: currentService?.id, + sessionId: chat?._id, + from: 0, + size: 20, + }); + response = JSON.parse(response || "") + console.log("id_history", response); + const hits = response?.hits?.hits || []; + const updatedChat: Chat = { + ...chat, + messages: hits, + }; + setActiveChat(updatedChat); + } catch (error) { + console.error("Failed to fetch user data:", error); + } + }; + + const onSelectChat = async (chat: any) => { + chatClose(); + try { + let response: any = await invoke("open_session_chat", { + serverId: currentService?.id, + sessionId: chat?._id, + }); + response = JSON.parse(response || "") + console.log("_open", response); + chatHistory(response); + } catch (error) { + console.error("Failed to fetch user data:", error); + } + }; + + const deleteChat = (chatId: string) => { + 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(""); + } + } + }; + + 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); + } + }, []); + + useEffect(() => { + if (isSidebarOpenChat) { + document.addEventListener("click", handleOutsideClick); + } + return () => { + document.removeEventListener("click", handleOutsideClick); + }; + }, [isSidebarOpenChat, handleOutsideClick]); + + const getChatHistory = async () => { + if (!currentService?.id) return; + try { + let response: any = await invoke("chat_history", { + serverId: currentService?.id, + from: 0, + size: 20, + }); + response = JSON.parse(response || "") + console.log("_history", response); + const hits = response?.hits?.hits || []; + setChats(hits); + if (hits[0]) { + onSelectChat(hits[0]); + } else { + init(""); + } + } catch (error) { + console.error("Failed to fetch user data:", error); + } + }; + + useEffect(() => { + currentService && !setIsSidebarOpen && getChatHistory(); + }, [currentService]); + + return ( +
+ {setIsSidebarOpen ? null : ( +
- -
- )} + > + +
+ )} - { - setIsSidebarOpenChat(!isSidebarOpenChat); - setIsSidebarOpen && setIsSidebarOpen(!isSidebarOpenChat); - }} - isSidebarOpen={isSidebarOpenChat} - activeChat={activeChat} - /> + { + setIsSidebarOpenChat(!isSidebarOpenChat); + setIsSidebarOpen && setIsSidebarOpen(!isSidebarOpenChat); + }} + isSidebarOpen={isSidebarOpenChat} + activeChat={activeChat} + /> - {/* Chat messages */} -
- + {/* Chat messages */} +
+ - {activeChat?.messages?.map((message, index) => ( - - ))} + {activeChat?.messages?.map((message, index) => ( + + ))} - {!curChatEnd && activeChat?._id ? ( - - ) : null} + {!curChatEnd && activeChat?._id ? ( + + ) : null} - {timedoutShow ? ( - - ) : null} + {timedoutShow ? ( + + ) : null} -
-
-
- ); - } - ) +
+
+
+ ); + } + ) ); export default ChatAI;