2025-02-26 18:06:35 +08:00
|
|
|
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";
|
|
|
|
|
|
2025-03-02 21:44:21 +08:00
|
|
|
import { ChatMessage } from "@/components/ChatMessage";
|
|
|
|
|
import type { Chat, IChunkData } from "./types";
|
2025-02-26 18:06:35 +08:00
|
|
|
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 { useSearchStore } from "@/stores/searchStore";
|
2025-03-02 21:44:21 +08:00
|
|
|
import { IServer } from "@/stores/appStore";
|
2025-03-03 17:54:00 +08:00
|
|
|
import FileList from "../Search/FileList";
|
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;
|
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;
|
|
|
|
|
connected: boolean;
|
|
|
|
|
reconnect: () => void;
|
|
|
|
|
handleSendMessage: (value: string) => 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,
|
|
|
|
|
},
|
|
|
|
|
ref
|
|
|
|
|
) => {
|
|
|
|
|
if (!isTransitioned) return null;
|
|
|
|
|
|
|
|
|
|
const { t } = useTranslation();
|
|
|
|
|
|
|
|
|
|
useImperativeHandle(ref, () => ({
|
|
|
|
|
init: init,
|
|
|
|
|
cancelChat: cancelChat,
|
|
|
|
|
connected: connected,
|
|
|
|
|
reconnect: reconnect,
|
|
|
|
|
handleSendMessage: handleSendMessage,
|
|
|
|
|
clearChat: clearChat,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
const { createWin } = useWindows();
|
|
|
|
|
|
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-02-26 22:37:21 +08:00
|
|
|
const [errorShow, setErrorShow] = useState(false);
|
2025-02-26 18:06:35 +08:00
|
|
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
|
|
|
|
|
const curChatEndRef = useRef(curChatEnd);
|
|
|
|
|
curChatEndRef.current = curChatEnd;
|
|
|
|
|
|
|
|
|
|
const curIdRef = useRef("");
|
|
|
|
|
|
|
|
|
|
const [isSidebarOpenChat, setIsSidebarOpenChat] = useState(isSidebarOpen);
|
|
|
|
|
const [chats, setChats] = useState<Chat[]>([]);
|
|
|
|
|
const sourceDataIds = useSearchStore((state) => state.sourceDataIds);
|
2025-03-03 17:54:00 +08:00
|
|
|
const uploadFiles = useChatStore((state) => state.uploadFiles);
|
2025-02-26 18:06:35 +08:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
activeChatProp && setActiveChat(activeChatProp);
|
|
|
|
|
}, [activeChatProp]);
|
|
|
|
|
|
2025-03-02 21:44:21 +08:00
|
|
|
const reconnect = async (server?: IServer) => {
|
|
|
|
|
server = server || currentService;
|
|
|
|
|
if (!server?.id) return;
|
2025-02-26 18:06:35 +08:00
|
|
|
try {
|
2025-03-03 15:43:19 +08:00
|
|
|
console.log("reconnect", server.id);
|
2025-03-02 21:44:21 +08:00
|
|
|
await invoke("connect_to_server", { id: server.id });
|
2025-02-26 18:06:35 +08:00
|
|
|
setConnected(true);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("Failed to connect:", error);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const messageTimeoutRef = useRef<NodeJS.Timeout>();
|
|
|
|
|
|
2025-03-02 21:44:21 +08:00
|
|
|
const [Question, setQuestion] = useState<string>("");
|
|
|
|
|
|
|
|
|
|
const [query_intent, setQuery_intent] = useState<IChunkData>();
|
|
|
|
|
const deal_query_intent = useCallback((data: IChunkData) => {
|
|
|
|
|
setQuery_intent((prev: IChunkData | undefined): IChunkData => {
|
|
|
|
|
if (!prev) return data;
|
|
|
|
|
return {
|
|
|
|
|
...prev,
|
|
|
|
|
message_chunk: prev.message_chunk + data.message_chunk,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const [fetch_source, setFetch_source] = useState<IChunkData>();
|
|
|
|
|
const deal_fetch_source = useCallback((data: IChunkData) => {
|
|
|
|
|
setFetch_source(data);
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const [pick_source, setPick_source] = useState<IChunkData>();
|
|
|
|
|
const deal_pick_source = useCallback((data: IChunkData) => {
|
|
|
|
|
setPick_source((prev: IChunkData | undefined): IChunkData => {
|
|
|
|
|
if (!prev) return data;
|
|
|
|
|
return {
|
|
|
|
|
...prev,
|
|
|
|
|
message_chunk: prev.message_chunk + data.message_chunk,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const [deep_read, setDeep_read] = useState<IChunkData>();
|
|
|
|
|
const deal_deep_read = useCallback((data: IChunkData) => {
|
|
|
|
|
setDeep_read((prev: IChunkData | undefined): IChunkData => {
|
|
|
|
|
if (!prev) return data;
|
|
|
|
|
return {
|
|
|
|
|
...prev,
|
|
|
|
|
message_chunk: prev.message_chunk + "&" + data.message_chunk,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const [think, setThink] = useState<IChunkData>();
|
|
|
|
|
const deal_think = useCallback((data: IChunkData) => {
|
|
|
|
|
setThink((prev: IChunkData | undefined): IChunkData => {
|
|
|
|
|
if (!prev) return data;
|
|
|
|
|
return {
|
|
|
|
|
...prev,
|
|
|
|
|
message_chunk: prev.message_chunk + data.message_chunk,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const [response, setResponse] = useState<IChunkData>();
|
|
|
|
|
const deal_response = useCallback((data: IChunkData) => {
|
|
|
|
|
setResponse((prev: IChunkData | undefined): IChunkData => {
|
|
|
|
|
if (!prev) return data;
|
|
|
|
|
return {
|
|
|
|
|
...prev,
|
|
|
|
|
message_chunk: prev.message_chunk + data.message_chunk,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const clearCurrentChat = useCallback(() => {
|
|
|
|
|
setQuery_intent(undefined);
|
|
|
|
|
setFetch_source(undefined);
|
|
|
|
|
setPick_source(undefined);
|
|
|
|
|
setDeep_read(undefined);
|
|
|
|
|
setThink(undefined);
|
|
|
|
|
setResponse(undefined);
|
|
|
|
|
}, []);
|
|
|
|
|
|
2025-02-26 18:06:35 +08:00
|
|
|
const dealMsg = useCallback(
|
|
|
|
|
(msg: string) => {
|
|
|
|
|
if (messageTimeoutRef.current) {
|
|
|
|
|
clearTimeout(messageTimeoutRef.current);
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-02 21:44:21 +08:00
|
|
|
if (!msg.includes("PRIVATE")) return;
|
2025-02-26 18:06:35 +08:00
|
|
|
|
2025-03-02 21:44:21 +08:00
|
|
|
messageTimeoutRef.current = setTimeout(() => {
|
|
|
|
|
if (!curChatEnd) {
|
|
|
|
|
console.log("AI response timeout");
|
|
|
|
|
setTimedoutShow(true);
|
|
|
|
|
cancelChat();
|
|
|
|
|
}
|
|
|
|
|
}, 30000);
|
|
|
|
|
|
|
|
|
|
if (msg.includes("assistant finished output")) {
|
|
|
|
|
clearTimeout(messageTimeoutRef.current);
|
|
|
|
|
console.log("AI finished output");
|
|
|
|
|
setCurChatEnd(true);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const cleanedData = msg.replace(/^PRIVATE /, "");
|
|
|
|
|
try {
|
|
|
|
|
const chunkData = JSON.parse(cleanedData);
|
|
|
|
|
|
|
|
|
|
if (chunkData.reply_to_message !== curIdRef.current) return;
|
|
|
|
|
|
|
|
|
|
// ['query_intent', 'fetch_source', 'pick_source', 'deep_read', 'think', 'response'];
|
|
|
|
|
if (chunkData.chunk_type === "query_intent") {
|
|
|
|
|
deal_query_intent(chunkData);
|
|
|
|
|
} else if (chunkData.chunk_type === "fetch_source") {
|
|
|
|
|
deal_fetch_source(chunkData);
|
|
|
|
|
} else if (chunkData.chunk_type === "pick_source") {
|
|
|
|
|
deal_pick_source(chunkData);
|
|
|
|
|
} else if (chunkData.chunk_type === "deep_read") {
|
|
|
|
|
deal_deep_read(chunkData);
|
|
|
|
|
} else if (chunkData.chunk_type === "think") {
|
|
|
|
|
deal_think(chunkData);
|
|
|
|
|
} else if (chunkData.chunk_type === "response") {
|
|
|
|
|
deal_response(chunkData);
|
2025-02-26 18:06:35 +08:00
|
|
|
}
|
2025-03-02 21:44:21 +08:00
|
|
|
} catch (error) {
|
|
|
|
|
console.error("parse error:", error);
|
2025-02-26 18:06:35 +08:00
|
|
|
}
|
|
|
|
|
},
|
2025-03-02 21:44:21 +08:00
|
|
|
[curChatEnd]
|
2025-02-26 18:06:35 +08:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2025-03-02 21:44:21 +08:00
|
|
|
if (curChatEnd) {
|
|
|
|
|
simulateAssistantResponse();
|
|
|
|
|
}
|
|
|
|
|
}, [curChatEnd]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
let unlisten_error = null;
|
|
|
|
|
|
|
|
|
|
if (connected) {
|
|
|
|
|
setErrorShow(false);
|
|
|
|
|
unlisten_error = listen("ws-error", (event) => {
|
|
|
|
|
console.error("WebSocket error:", event.payload);
|
|
|
|
|
setConnected(false);
|
|
|
|
|
setErrorShow(true);
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-02-26 18:06:35 +08:00
|
|
|
|
|
|
|
|
return () => {
|
2025-03-02 21:44:21 +08:00
|
|
|
unlisten_error?.then((fn) => fn());
|
2025-02-26 18:06:35 +08:00
|
|
|
};
|
2025-03-02 21:44:21 +08:00
|
|
|
}, [connected]);
|
2025-02-26 18:06:35 +08:00
|
|
|
|
2025-03-02 21:44:21 +08:00
|
|
|
useEffect(() => {
|
|
|
|
|
let unlisten_message = null;
|
|
|
|
|
if (connected) {
|
|
|
|
|
setErrorShow(false);
|
|
|
|
|
unlisten_message = listen("ws-message", (event) => {
|
|
|
|
|
dealMsg(String(event.payload));
|
2025-03-03 20:51:45 +08:00
|
|
|
console.log("message:", event.payload);
|
2025-03-02 21:44:21 +08:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
unlisten_message?.then((fn) => fn());
|
2025-02-26 18:06:35 +08:00
|
|
|
};
|
2025-03-02 21:44:21 +08:00
|
|
|
}, [dealMsg, connected]);
|
2025-02-26 18:06:35 +08:00
|
|
|
|
|
|
|
|
const updatedChat = useMemo(() => {
|
2025-03-02 21:44:21 +08:00
|
|
|
if (!activeChat?._id) return null;
|
2025-02-26 18:06:35 +08:00
|
|
|
return {
|
|
|
|
|
...activeChat,
|
2025-03-02 21:44:21 +08:00
|
|
|
messages: [...(activeChat.messages || [])],
|
2025-02-26 18:06:35 +08:00
|
|
|
};
|
2025-03-02 21:44:21 +08:00
|
|
|
}, [activeChat]);
|
2025-02-26 18:06:35 +08:00
|
|
|
|
|
|
|
|
const simulateAssistantResponse = useCallback(() => {
|
|
|
|
|
if (!updatedChat) return;
|
|
|
|
|
|
|
|
|
|
console.log("updatedChat:", updatedChat);
|
|
|
|
|
setActiveChat(updatedChat);
|
|
|
|
|
}, [updatedChat]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (curChatEnd) {
|
|
|
|
|
simulateAssistantResponse();
|
|
|
|
|
}
|
|
|
|
|
}, [curChatEnd]);
|
|
|
|
|
|
|
|
|
|
const [userScrolling, setUserScrolling] = useState(false);
|
|
|
|
|
const scrollTimeoutRef = useRef<NodeJS.Timeout>();
|
|
|
|
|
|
|
|
|
|
const scrollToBottom = useCallback(
|
|
|
|
|
debounce(() => {
|
|
|
|
|
if (!userScrolling) {
|
|
|
|
|
const container = messagesEndRef.current?.parentElement;
|
|
|
|
|
if (container) {
|
|
|
|
|
container.scrollTo({
|
|
|
|
|
top: container.scrollHeight,
|
|
|
|
|
behavior: "smooth",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}, 100),
|
|
|
|
|
[userScrolling]
|
|
|
|
|
);
|
2025-02-24 10:26:39 +08:00
|
|
|
|
2025-02-26 18:06:35 +08:00
|
|
|
useEffect(() => {
|
|
|
|
|
const container = messagesEndRef.current?.parentElement;
|
|
|
|
|
if (!container) return;
|
2025-02-24 18:42:49 +08:00
|
|
|
|
2025-02-26 18:06:35 +08:00
|
|
|
const handleScroll = () => {
|
|
|
|
|
if (scrollTimeoutRef.current) {
|
|
|
|
|
clearTimeout(scrollTimeoutRef.current);
|
|
|
|
|
}
|
2025-02-24 10:26:39 +08:00
|
|
|
|
2025-02-26 18:06:35 +08:00
|
|
|
const { scrollTop, scrollHeight, clientHeight } = container;
|
|
|
|
|
const isAtBottom =
|
|
|
|
|
Math.abs(scrollHeight - scrollTop - clientHeight) < 10;
|
2025-02-24 10:26:39 +08:00
|
|
|
|
2025-02-26 18:06:35 +08:00
|
|
|
setUserScrolling(!isAtBottom);
|
2025-02-26 12:48:54 +08:00
|
|
|
|
2025-02-26 18:06:35 +08:00
|
|
|
if (isAtBottom) {
|
|
|
|
|
setUserScrolling(false);
|
|
|
|
|
}
|
2025-02-20 15:38:55 +08:00
|
|
|
|
2025-02-26 18:06:35 +08:00
|
|
|
scrollTimeoutRef.current = setTimeout(() => {
|
|
|
|
|
const {
|
|
|
|
|
scrollTop: newScrollTop,
|
|
|
|
|
scrollHeight: newScrollHeight,
|
|
|
|
|
clientHeight: newClientHeight,
|
|
|
|
|
} = container;
|
|
|
|
|
const nowAtBottom =
|
|
|
|
|
Math.abs(newScrollHeight - newScrollTop - newClientHeight) < 10;
|
|
|
|
|
if (nowAtBottom) {
|
|
|
|
|
setUserScrolling(false);
|
2025-02-26 15:48:49 +08:00
|
|
|
}
|
2025-02-26 18:06:35 +08:00
|
|
|
}, 500);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
container.addEventListener("scroll", handleScroll);
|
|
|
|
|
return () => {
|
|
|
|
|
container.removeEventListener("scroll", handleScroll);
|
|
|
|
|
if (scrollTimeoutRef.current) {
|
|
|
|
|
clearTimeout(scrollTimeoutRef.current);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
scrollToBottom();
|
2025-03-02 21:44:21 +08:00
|
|
|
}, [
|
|
|
|
|
activeChat?.messages,
|
|
|
|
|
query_intent?.message_chunk,
|
|
|
|
|
fetch_source?.message_chunk,
|
|
|
|
|
pick_source?.message_chunk,
|
|
|
|
|
deep_read?.message_chunk,
|
|
|
|
|
think?.message_chunk,
|
|
|
|
|
response?.message_chunk,
|
|
|
|
|
]);
|
2025-02-26 18:06:35 +08:00
|
|
|
|
|
|
|
|
const clearChat = () => {
|
|
|
|
|
console.log("clearChat");
|
|
|
|
|
chatClose();
|
|
|
|
|
setActiveChat(undefined);
|
|
|
|
|
setCurChatEnd(true);
|
|
|
|
|
clearChatPage && clearChatPage();
|
|
|
|
|
};
|
|
|
|
|
|
2025-03-02 21:44:21 +08:00
|
|
|
const createNewChat = useCallback(
|
|
|
|
|
async (value: string = "") => {
|
|
|
|
|
setTimedoutShow(false);
|
|
|
|
|
setErrorShow(false);
|
|
|
|
|
chatClose();
|
|
|
|
|
clearCurrentChat();
|
|
|
|
|
setQuestion(value);
|
|
|
|
|
try {
|
|
|
|
|
// console.log("sourceDataIds", sourceDataIds);
|
|
|
|
|
let response: any = await invoke("new_chat", {
|
|
|
|
|
serverId: currentService?.id,
|
|
|
|
|
message: value,
|
|
|
|
|
queryParams: {
|
|
|
|
|
search: isSearchActive,
|
|
|
|
|
deep_thinking: isDeepThinkActive,
|
|
|
|
|
datasource: sourceDataIds.join(","),
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
console.log("_new", response);
|
|
|
|
|
const newChat: Chat = response;
|
|
|
|
|
curIdRef.current = response?.payload?.id;
|
2025-02-26 18:06:35 +08:00
|
|
|
|
2025-03-02 21:44:21 +08:00
|
|
|
newChat._source = {
|
|
|
|
|
message: value,
|
|
|
|
|
};
|
|
|
|
|
const updatedChat: Chat = {
|
|
|
|
|
...newChat,
|
|
|
|
|
messages: [newChat],
|
|
|
|
|
};
|
2025-02-26 18:06:35 +08:00
|
|
|
|
2025-03-02 21:44:21 +08:00
|
|
|
changeInput && changeInput("");
|
|
|
|
|
console.log("updatedChat2", updatedChat);
|
|
|
|
|
setActiveChat(updatedChat);
|
|
|
|
|
setCurChatEnd(false);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
setErrorShow(true);
|
|
|
|
|
console.error("Failed to fetch user data:", error);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[isSearchActive, isDeepThinkActive]
|
|
|
|
|
);
|
2025-02-26 18:06:35 +08:00
|
|
|
|
|
|
|
|
const init = (value: string) => {
|
2025-03-03 20:51:45 +08:00
|
|
|
console.log("init", value, curChatEnd, activeChat);
|
2025-02-26 18:06:35 +08:00
|
|
|
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;
|
2025-03-02 21:44:21 +08:00
|
|
|
setQuestion(content);
|
|
|
|
|
await chatHistory(newChat);
|
|
|
|
|
|
2025-02-26 18:06:35 +08:00
|
|
|
setTimedoutShow(false);
|
2025-02-26 22:37:21 +08:00
|
|
|
setErrorShow(false);
|
2025-03-02 21:44:21 +08:00
|
|
|
clearCurrentChat();
|
2025-02-26 18:06:35 +08:00
|
|
|
try {
|
2025-03-02 21:44:21 +08:00
|
|
|
// console.log("sourceDataIds", sourceDataIds);
|
2025-02-26 18:06:35 +08:00
|
|
|
let response: any = await invoke("send_message", {
|
|
|
|
|
serverId: currentService?.id,
|
|
|
|
|
sessionId: newChat?._id,
|
|
|
|
|
queryParams: {
|
|
|
|
|
search: isSearchActive,
|
|
|
|
|
deep_thinking: isDeepThinkActive,
|
|
|
|
|
datasource: sourceDataIds.join(","),
|
|
|
|
|
},
|
|
|
|
|
message: content,
|
|
|
|
|
});
|
|
|
|
|
response = JSON.parse(response || "");
|
2025-02-26 22:37:21 +08:00
|
|
|
console.log("_send", response);
|
2025-02-26 18:06:35 +08:00
|
|
|
curIdRef.current = response[0]?._id;
|
|
|
|
|
|
|
|
|
|
const updatedChat: Chat = {
|
|
|
|
|
...newChat,
|
|
|
|
|
messages: [...(newChat?.messages || []), ...(response || [])],
|
2025-02-26 17:48:43 +08:00
|
|
|
};
|
2025-02-19 17:23:49 +08:00
|
|
|
|
2025-02-26 18:06:35 +08:00
|
|
|
changeInput && changeInput("");
|
|
|
|
|
console.log("updatedChat2", updatedChat);
|
|
|
|
|
setActiveChat(updatedChat);
|
|
|
|
|
setCurChatEnd(false);
|
|
|
|
|
} catch (error) {
|
2025-02-26 22:37:21 +08:00
|
|
|
setErrorShow(true);
|
2025-02-26 18:06:35 +08:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
};
|
2025-02-24 18:42:49 +08:00
|
|
|
|
2025-02-26 18:06:35 +08:00
|
|
|
const cancelChat = async () => {
|
|
|
|
|
setCurChatEnd(true);
|
|
|
|
|
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();
|
|
|
|
|
setActiveChat(undefined);
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}, []);
|
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]);
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("Failed to fetch user data:", error);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
currentService && !setIsSidebarOpen && getChatHistory();
|
|
|
|
|
}, [currentService]);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
data-tauri-drag-region
|
|
|
|
|
className={`h-full flex flex-col rounded-xl overflow-hidden`}
|
|
|
|
|
>
|
|
|
|
|
{setIsSidebarOpen ? null : (
|
|
|
|
|
<div
|
|
|
|
|
data-sidebar
|
|
|
|
|
className={`fixed inset-y-0 left-0 z-50 w-64 transform transition-all duration-300 ease-in-out
|
2025-02-24 18:42:49 +08:00
|
|
|
${
|
2025-02-26 18:06:35 +08:00
|
|
|
isSidebarOpenChat
|
|
|
|
|
? "translate-x-0"
|
|
|
|
|
: "-translate-x-[calc(100%)]"
|
|
|
|
|
}
|
2025-02-24 18:42:49 +08:00
|
|
|
md:relative md:translate-x-0 bg-gray-100 dark:bg-gray-800
|
|
|
|
|
border-r border-gray-200 dark:border-gray-700 rounded-tl-xl rounded-bl-xl
|
|
|
|
|
overflow-hidden`}
|
2025-02-26 18:06:35 +08:00
|
|
|
>
|
|
|
|
|
<Sidebar
|
|
|
|
|
chats={chats}
|
|
|
|
|
activeChat={activeChat}
|
|
|
|
|
onNewChat={clearChat}
|
|
|
|
|
onSelectChat={onSelectChat}
|
|
|
|
|
onDeleteChat={deleteChat}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
<ChatHeader
|
|
|
|
|
onCreateNewChat={clearChat}
|
|
|
|
|
onOpenChatAI={openChatAI}
|
|
|
|
|
setIsSidebarOpen={() => {
|
|
|
|
|
setIsSidebarOpenChat(!isSidebarOpenChat);
|
|
|
|
|
setIsSidebarOpen && setIsSidebarOpen(!isSidebarOpenChat);
|
|
|
|
|
}}
|
|
|
|
|
isSidebarOpen={isSidebarOpenChat}
|
|
|
|
|
activeChat={activeChat}
|
2025-03-02 21:44:21 +08:00
|
|
|
reconnect={reconnect}
|
2025-02-26 18:06:35 +08:00
|
|
|
/>
|
|
|
|
|
{/* Chat messages */}
|
2025-03-03 17:54:00 +08:00
|
|
|
<div className="flex flex-col h-full justify-between overflow-hidden">
|
|
|
|
|
<div className="flex-1 w-full overflow-x-hidden overflow-y-auto border-t border-[rgba(0,0,0,0.1)] dark:border-[rgba(255,255,255,0.15)] custom-scrollbar relative">
|
2025-02-26 18:06:35 +08:00
|
|
|
<ChatMessage
|
2025-03-03 17:54:00 +08:00
|
|
|
key={"greetings"}
|
2025-02-26 18:06:35 +08:00
|
|
|
message={{
|
2025-03-03 17:54:00 +08:00
|
|
|
_id: "greetings",
|
2025-02-26 18:06:35 +08:00
|
|
|
_source: {
|
|
|
|
|
type: "assistant",
|
2025-03-03 17:54:00 +08:00
|
|
|
message: t("assistant.chat.greetings"),
|
2025-02-26 18:06:35 +08:00
|
|
|
},
|
|
|
|
|
}}
|
|
|
|
|
/>
|
2025-03-03 17:54:00 +08:00
|
|
|
{activeChat?.messages?.map((message, index) => (
|
|
|
|
|
<ChatMessage
|
|
|
|
|
key={message._id + index}
|
|
|
|
|
message={message}
|
|
|
|
|
isTyping={false}
|
|
|
|
|
onResend={handleSendMessage}
|
|
|
|
|
/>
|
|
|
|
|
))}
|
|
|
|
|
{(query_intent ||
|
|
|
|
|
fetch_source ||
|
|
|
|
|
pick_source ||
|
|
|
|
|
deep_read ||
|
|
|
|
|
think ||
|
|
|
|
|
response) &&
|
|
|
|
|
activeChat?._id ? (
|
|
|
|
|
<ChatMessage
|
|
|
|
|
key={"current"}
|
|
|
|
|
message={{
|
|
|
|
|
_id: "current",
|
|
|
|
|
_source: {
|
|
|
|
|
type: "assistant",
|
|
|
|
|
message: "",
|
|
|
|
|
question: Question,
|
|
|
|
|
},
|
|
|
|
|
}}
|
|
|
|
|
onResend={handleSendMessage}
|
|
|
|
|
isTyping={!curChatEnd}
|
|
|
|
|
query_intent={query_intent}
|
|
|
|
|
fetch_source={fetch_source}
|
|
|
|
|
pick_source={pick_source}
|
|
|
|
|
deep_read={deep_read}
|
|
|
|
|
think={think}
|
|
|
|
|
response={response}
|
|
|
|
|
/>
|
|
|
|
|
) : null}
|
|
|
|
|
{timedoutShow ? (
|
|
|
|
|
<ChatMessage
|
|
|
|
|
key={"timedout"}
|
|
|
|
|
message={{
|
|
|
|
|
_id: "timedout",
|
|
|
|
|
_source: {
|
|
|
|
|
type: "assistant",
|
|
|
|
|
message: t("assistant.chat.timedout"),
|
|
|
|
|
question: Question,
|
|
|
|
|
},
|
|
|
|
|
}}
|
|
|
|
|
onResend={handleSendMessage}
|
|
|
|
|
isTyping={false}
|
|
|
|
|
/>
|
|
|
|
|
) : null}
|
|
|
|
|
{errorShow ? (
|
|
|
|
|
<ChatMessage
|
|
|
|
|
key={"error"}
|
|
|
|
|
message={{
|
|
|
|
|
_id: "error",
|
|
|
|
|
_source: {
|
|
|
|
|
type: "assistant",
|
|
|
|
|
message: t("assistant.chat.error"),
|
|
|
|
|
question: Question,
|
|
|
|
|
},
|
|
|
|
|
}}
|
|
|
|
|
onResend={handleSendMessage}
|
|
|
|
|
isTyping={false}
|
|
|
|
|
/>
|
|
|
|
|
) : null}
|
|
|
|
|
<div ref={messagesEndRef} />
|
|
|
|
|
</div>
|
2025-02-26 22:37:21 +08:00
|
|
|
|
2025-03-03 17:54:00 +08:00
|
|
|
{uploadFiles.length > 0 && (
|
2025-03-04 12:43:24 +08:00
|
|
|
<div className="max-h-[120px] overflow-auto p-2">
|
2025-03-03 17:54:00 +08:00
|
|
|
<FileList />
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2025-02-26 18:06:35 +08:00
|
|
|
</div>
|
|
|
|
|
</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;
|