refactor: replace the history list in the main window (#341)

This commit is contained in:
ayangweb
2025-04-08 21:43:41 +08:00
committed by GitHub
parent b602121cd3
commit 72e5224e39
7 changed files with 327 additions and 212 deletions

View File

@@ -7,7 +7,7 @@
<title>Coco</title> <title>Coco</title>
</head> </head>
<body> <body class="coco-container">
<div id="root"></div> <div id="root"></div>
<script type="module" src="/src/main.tsx"></script> <script type="module" src="/src/main.tsx"></script>
</body> </body>

View File

@@ -149,6 +149,9 @@ const ChatAI = memo(
openSessionChat, openSessionChat,
getChatHistory, getChatHistory,
createChatWindow, createChatWindow,
handleSearch,
handleRename,
handleDelete,
} = useChatActions( } = useChatActions(
currentService?.id, currentService?.id,
setActiveChat, setActiveChat,
@@ -158,6 +161,7 @@ const ChatAI = memo(
clearAllChunkData, clearAllChunkData,
setQuestion, setQuestion,
curIdRef, curIdRef,
setChats,
isSearchActive, isSearchActive,
isDeepThinkActive, isDeepThinkActive,
sourceDataIds, sourceDataIds,
@@ -210,7 +214,7 @@ const ChatAI = memo(
await handleSendMessage(value, activeChat, websocketSessionId); await handleSendMessage(value, activeChat, websocketSessionId);
} }
} catch (error) { } catch (error) {
console.error('Failed to initialize chat:', error); console.error("Failed to initialize chat:", error);
} }
}, },
[ [
@@ -265,20 +269,20 @@ const ChatAI = memo(
] ]
); );
const deleteChat = useCallback( // const deleteChat = useCallback(
(chatId: string) => { // (chatId: string) => {
setChats((prev) => prev.filter((chat) => chat._id !== chatId)); // setChats((prev) => prev.filter((chat) => chat._id !== chatId));
if (activeChat?._id === chatId) { // if (activeChat?._id === chatId) {
const remainingChats = chats.filter((chat) => chat._id !== chatId); // const remainingChats = chats.filter((chat) => chat._id !== chatId);
if (remainingChats.length > 0) { // if (remainingChats.length > 0) {
setActiveChat(remainingChats[0]); // setActiveChat(remainingChats[0]);
} else { // } else {
init(""); // init("");
} // }
} // }
}, // },
[activeChat, chats, init, setActiveChat] // [activeChat, chats, init, setActiveChat]
); // );
const handleOutsideClick = useCallback((e: MouseEvent) => { const handleOutsideClick = useCallback((e: MouseEvent) => {
const sidebar = document.querySelector("[data-sidebar]"); const sidebar = document.querySelector("[data-sidebar]");
@@ -302,25 +306,25 @@ const ChatAI = memo(
}; };
}, [isSidebarOpenChat, handleOutsideClick]); }, [isSidebarOpenChat, handleOutsideClick]);
const fetchChatHistory = useCallback(async () => { // const fetchChatHistory = useCallback(async () => {
const hits = await getChatHistory(); // const hits = await getChatHistory();
setChats(hits); // setChats(hits);
}, [getChatHistory]); // }, [getChatHistory]);
const setIsLoginChat = useCallback( const setIsLoginChat = useCallback(
(value: boolean) => { (value: boolean) => {
setIsLogin(value); setIsLogin(value);
value && currentService && !setIsSidebarOpen && fetchChatHistory(); value && currentService && !setIsSidebarOpen && getChatHistory();
!value && setChats([]); !value && setChats([]);
}, },
[currentService, setIsSidebarOpen, fetchChatHistory] [currentService, setIsSidebarOpen, getChatHistory]
); );
const toggleSidebar = useCallback(() => { const toggleSidebar = useCallback(() => {
setIsSidebarOpenChat(!isSidebarOpenChat); setIsSidebarOpenChat(!isSidebarOpenChat);
setIsSidebarOpen && setIsSidebarOpen(!isSidebarOpenChat); setIsSidebarOpen && setIsSidebarOpen(!isSidebarOpenChat);
!isSidebarOpenChat && fetchChatHistory(); !isSidebarOpenChat && getChatHistory();
}, [isSidebarOpenChat, setIsSidebarOpen, fetchChatHistory]); }, [isSidebarOpenChat, setIsSidebarOpen, getChatHistory]);
return ( return (
<div <div
@@ -332,10 +336,12 @@ const ChatAI = memo(
isSidebarOpen={isSidebarOpenChat} isSidebarOpen={isSidebarOpenChat}
chats={chats} chats={chats}
activeChat={activeChat} activeChat={activeChat}
onNewChat={clearChat} // onNewChat={clearChat}
onSelectChat={onSelectChat} onSelectChat={onSelectChat}
onDeleteChat={deleteChat} onDeleteChat={handleDelete}
fetchChatHistory={fetchChatHistory} fetchChatHistory={getChatHistory}
onSearch={handleSearch}
onRename={handleRename}
/> />
)} )}

View File

@@ -1,26 +1,31 @@
import React from "react"; import React from "react";
import { Sidebar } from "@/components/Assistant/Sidebar"; // import { Sidebar } from "@/components/Assistant/Sidebar";
import type { Chat } from "./types"; import type { Chat } from "./types";
import HistoryList from "../Common/HistoryList";
interface ChatSidebarProps { interface ChatSidebarProps {
isSidebarOpen: boolean; isSidebarOpen: boolean;
chats: Chat[]; chats: Chat[];
activeChat?: Chat; activeChat?: Chat;
onNewChat: () => void; // onNewChat: () => void;
onSelectChat: (chat: any) => void; onSelectChat: (chat: any) => void;
onDeleteChat: (chatId: string) => void; onDeleteChat: (chatId: string) => void;
fetchChatHistory: () => void; fetchChatHistory: () => void;
onSearch: (keyword: string) => void;
onRename: (chat: any, title: string) => void;
} }
export const ChatSidebar: React.FC<ChatSidebarProps> = ({ export const ChatSidebar: React.FC<ChatSidebarProps> = ({
isSidebarOpen, isSidebarOpen,
chats, chats,
activeChat, activeChat,
onNewChat, // onNewChat,
onSelectChat, onSelectChat,
onDeleteChat, onDeleteChat,
fetchChatHistory, fetchChatHistory,
onSearch,
onRename,
}) => { }) => {
return ( return (
<div <div
@@ -34,14 +39,23 @@ export const ChatSidebar: React.FC<ChatSidebarProps> = ({
overflow-hidden overflow-hidden
`} `}
> >
<Sidebar <HistoryList
list={chats}
active={activeChat}
onSearch={onSearch}
onRefresh={fetchChatHistory}
onSelect={onSelectChat}
onRename={onRename}
onRemove={onDeleteChat}
/>
{/* <Sidebar
chats={chats} chats={chats}
activeChat={activeChat} activeChat={activeChat}
onNewChat={onNewChat} onNewChat={onNewChat}
onSelectChat={onSelectChat} onSelectChat={onSelectChat}
onDeleteChat={onDeleteChat} onDeleteChat={onDeleteChat}
fetchChatHistory={fetchChatHistory} fetchChatHistory={fetchChatHistory}
/> /> */}
</div> </div>
); );
}; };

View File

@@ -1,7 +1,17 @@
import { useCallback } from "react"; import { useCallback, useEffect, useState } from "react";
import type { Chat } from "@/components/Assistant/types"; import type { Chat } from "@/components/Assistant/types";
import { close_session_chat, cancel_session_chat, session_chat_history, new_chat, send_message, open_session_chat, chat_history } from "@/commands" import {
close_session_chat,
cancel_session_chat,
session_chat_history,
new_chat,
send_message,
open_session_chat,
chat_history,
update_session_chat,
delete_session_chat,
} from "@/commands";
import { useAppStore } from "@/stores/appStore"; import { useAppStore } from "@/stores/appStore";
import { Get, Post } from "@/api/axiosRequest"; import { Get, Post } from "@/api/axiosRequest";
@@ -14,104 +24,118 @@ export function useChatActions(
clearAllChunkData: () => void, clearAllChunkData: () => void,
setQuestion: (value: string) => void, setQuestion: (value: string) => void,
curIdRef: React.MutableRefObject<string>, curIdRef: React.MutableRefObject<string>,
setChats: (chats: Chat[]) => void,
isSearchActive?: boolean, isSearchActive?: boolean,
isDeepThinkActive?: boolean, isDeepThinkActive?: boolean,
sourceDataIds?: string[], sourceDataIds?: string[],
changeInput?: (val: string) => void, changeInput?: (val: string) => void,
websocketSessionId?: string, websocketSessionId?: string
) { ) {
const isTauri = useAppStore((state) => state.isTauri); const isTauri = useAppStore((state) => state.isTauri);
const [keyword, setKeyword] = useState("");
const chatClose = useCallback(async (activeChat?: Chat) => { const chatClose = useCallback(
if (!activeChat?._id) return; async (activeChat?: Chat) => {
try { if (!activeChat?._id) return;
let response: any try {
if (isTauri) { let response: any;
if (!currentServiceId) return; if (isTauri) {
response = await close_session_chat({ if (!currentServiceId) return;
serverId: currentServiceId, response = await close_session_chat({
sessionId: activeChat?._id, serverId: currentServiceId,
}); sessionId: activeChat?._id,
response = JSON.parse(response || ""); });
} else { response = JSON.parse(response || "");
const [error, res] = await Post(`/chat/${activeChat?._id}/_close`, {}) } else {
if (error) { const [error, res] = await Post(
console.error('_close', error); `/chat/${activeChat?._id}/_close`,
return {}
);
if (error) {
console.error("_close", error);
return;
}
response = res;
} }
response = res console.log("_close", response);
} catch (error) {
console.error("chatClose:", error);
} }
console.log("_close", response); },
} catch (error) { [currentServiceId]
console.error("chatClose:", error); );
}
}, [currentServiceId]);
const cancelChat = useCallback(async (activeChat?: Chat) => { const cancelChat = useCallback(
setCurChatEnd(true); async (activeChat?: Chat) => {
if (!activeChat?._id) return; setCurChatEnd(true);
try { if (!activeChat?._id) return;
let response: any try {
if (isTauri) { let response: any;
if (!currentServiceId) return; if (isTauri) {
response = await cancel_session_chat({ if (!currentServiceId) return;
serverId: currentServiceId, response = await cancel_session_chat({
sessionId: activeChat?._id, serverId: currentServiceId,
}); sessionId: activeChat?._id,
response = JSON.parse(response || ""); });
} else { response = JSON.parse(response || "");
const [error, res] = await Post(`/chat/${activeChat?._id}/_cancel`, {}) } else {
if (error) { const [error, res] = await Post(
console.error('_cancel', error); `/chat/${activeChat?._id}/_cancel`,
return {}
);
if (error) {
console.error("_cancel", error);
return;
}
response = res;
} }
response = res console.log("_cancel", response);
} catch (error) {
console.error("cancelChat:", error);
} }
console.log("_cancel", response); },
} catch (error) { [currentServiceId, setCurChatEnd]
console.error("cancelChat:", error); );
}
}, [currentServiceId, setCurChatEnd]);
const chatHistory = useCallback(async ( const chatHistory = useCallback(
chat: Chat, async (chat: Chat, callback?: (chat: Chat) => void) => {
callback?: (chat: Chat) => void if (!chat?._id) return;
) => { try {
if (!chat?._id) return; let response: any;
try { if (isTauri) {
let response: any if (!currentServiceId) return;
if (isTauri) { response = await session_chat_history({
if (!currentServiceId) return; serverId: currentServiceId,
response = await session_chat_history({ sessionId: chat?._id,
serverId: currentServiceId, from: 0,
sessionId: chat?._id, size: 20,
from: 0, });
size: 20, response = JSON.parse(response || "");
}); } else {
response = JSON.parse(response || ""); const [error, res] = await Get(`/chat/${chat?._id}/_history`, {
} else { from: 0,
const [error, res] = await Get(`/chat/${chat?._id}/_history`, { size: 20,
from: 0, });
size: 20, if (error) {
}) console.error("_cancel", error);
if (error) { return;
console.error('_cancel', error); }
return response = res;
} }
response = res const hits = response?.hits?.hits || [];
const updatedChat: Chat = {
...chat,
messages: hits,
};
console.log("id_history", response, updatedChat);
setActiveChat(updatedChat);
callback && callback(updatedChat);
} catch (error) {
console.error("chatHistory:", error);
} }
const hits = response?.hits?.hits || []; },
const updatedChat: Chat = { [currentServiceId, setActiveChat]
...chat, );
messages: hits,
};
console.log("id_history", response, updatedChat);
setActiveChat(updatedChat);
callback && callback(updatedChat);
} catch (error) {
console.error("chatHistory:", error);
}
}, [currentServiceId, setActiveChat]);
const createNewChat = useCallback( const createNewChat = useCallback(
async (value: string = "", activeChat?: Chat, id?: string) => { async (value: string = "", activeChat?: Chat, id?: string) => {
@@ -120,14 +144,14 @@ export function useChatActions(
setErrorShow(false); setErrorShow(false);
await chatClose(activeChat); await chatClose(activeChat);
clearAllChunkData(); clearAllChunkData();
setQuestion(value); setQuestion(value);
if (!(websocketSessionId || id)) { if (!(websocketSessionId || id)) {
setErrorShow(true); setErrorShow(true);
console.error("websocketSessionId", websocketSessionId, id); console.error("websocketSessionId", websocketSessionId, id);
return; return;
} }
console.log("sourceDataIds", sourceDataIds, websocketSessionId, id); console.log("sourceDataIds", sourceDataIds, websocketSessionId, id);
let response: any let response: any;
if (isTauri) { if (isTauri) {
if (!currentServiceId) return; if (!currentServiceId) return;
response = await new_chat({ response = await new_chat({
@@ -140,24 +164,28 @@ export function useChatActions(
datasource: sourceDataIds?.join(",") || "", datasource: sourceDataIds?.join(",") || "",
}, },
}); });
} else { } else {
console.log('websocketSessionId', websocketSessionId, id) console.log("websocketSessionId", websocketSessionId, id);
const [error, res] = await Post('/chat/_new', { const [error, res] = await Post(
message: value, "/chat/_new",
}, { {
search: isSearchActive, message: value,
deep_thinking: isDeepThinkActive, },
datasource: sourceDataIds?.join(",") || "", {
}, { search: isSearchActive,
"WEBSOCKET-SESSION-ID": websocketSessionId || id, deep_thinking: isDeepThinkActive,
}) datasource: sourceDataIds?.join(",") || "",
},
{
"WEBSOCKET-SESSION-ID": websocketSessionId || id,
}
);
if (error) { if (error) {
setErrorShow(true); setErrorShow(true);
console.error('_new', error); console.error("_new", error);
return return;
} }
response = res response = res;
} }
console.log("_new", response); console.log("_new", response);
const newChat: Chat = response; const newChat: Chat = response;
@@ -179,7 +207,14 @@ export function useChatActions(
console.error("createNewChat:", error); console.error("createNewChat:", error);
} }
}, },
[currentServiceId, sourceDataIds, isSearchActive, isDeepThinkActive, curIdRef, websocketSessionId] [
currentServiceId,
sourceDataIds,
isSearchActive,
isDeepThinkActive,
curIdRef,
websocketSessionId,
]
); );
const sendMessage = useCallback( const sendMessage = useCallback(
@@ -193,7 +228,7 @@ export function useChatActions(
console.error("websocketSessionId", websocketSessionId, id); console.error("websocketSessionId", websocketSessionId, id);
return; return;
} }
let response: any let response: any;
if (isTauri) { if (isTauri) {
if (!currentServiceId) return; if (!currentServiceId) return;
response = await send_message({ response = await send_message({
@@ -209,23 +244,28 @@ export function useChatActions(
}); });
response = JSON.parse(response || ""); response = JSON.parse(response || "");
} else { } else {
console.log('websocketSessionId', websocketSessionId, id) console.log("websocketSessionId", websocketSessionId, id);
const [error, res] = await Post(`/chat/${newChat?._id}/_send`, { const [error, res] = await Post(
message: content `/chat/${newChat?._id}/_send`,
}, { {
search: isSearchActive, message: content,
deep_thinking: isDeepThinkActive, },
datasource: sourceDataIds?.join(",") || "", {
}, { search: isSearchActive,
"WEBSOCKET-SESSION-ID": websocketSessionId || id, deep_thinking: isDeepThinkActive,
}) datasource: sourceDataIds?.join(",") || "",
},
{
"WEBSOCKET-SESSION-ID": websocketSessionId || id,
}
);
if (error) { if (error) {
setErrorShow(true); setErrorShow(true);
console.error('_cancel', error); console.error("_cancel", error);
return return;
} }
response = res response = res;
} }
console.log("_send", response); console.log("_send", response);
curIdRef.current = response[0]?._id; curIdRef.current = response[0]?._id;
@@ -243,7 +283,18 @@ export function useChatActions(
console.error("sendMessage:", error); console.error("sendMessage:", error);
} }
}, },
[currentServiceId, sourceDataIds, isSearchActive, isDeepThinkActive, curIdRef, setActiveChat, setCurChatEnd, setErrorShow, changeInput, websocketSessionId] [
currentServiceId,
sourceDataIds,
isSearchActive,
isDeepThinkActive,
curIdRef,
setActiveChat,
setCurChatEnd,
setErrorShow,
changeInput,
websocketSessionId,
]
); );
const handleSendMessage = useCallback( const handleSendMessage = useCallback(
@@ -256,88 +307,130 @@ export function useChatActions(
await chatHistory(activeChat, (chat) => sendMessage(content, chat, id)); await chatHistory(activeChat, (chat) => sendMessage(content, chat, id));
}, },
[chatHistory, sendMessage, setQuestion, setTimedoutShow, setErrorShow, clearAllChunkData] [
chatHistory,
sendMessage,
setQuestion,
setTimedoutShow,
setErrorShow,
clearAllChunkData,
]
); );
const openSessionChat = useCallback(async (chat: Chat) => { const openSessionChat = useCallback(
if (!chat?._id) return; async (chat: Chat) => {
try { if (!chat?._id) return;
let response: any try {
if (isTauri) { let response: any;
if (!currentServiceId) return; if (isTauri) {
response = await open_session_chat({ if (!currentServiceId) return;
serverId: currentServiceId, response = await open_session_chat({
sessionId: chat?._id, serverId: currentServiceId,
}); sessionId: chat?._id,
response = JSON.parse(response || ""); });
} else { response = JSON.parse(response || "");
const [error, res] = await Post(`/chat/${chat?._id}/_open`, {}) } else {
if (error) { const [error, res] = await Post(`/chat/${chat?._id}/_open`, {});
console.error('_open', error); if (error) {
return null console.error("_open", error);
return null;
}
response = res;
} }
response = res
}
console.log("_open", response); console.log("_open", response);
return response; return response;
} catch (error) { } catch (error) {
console.error("open_session_chat:", error); console.error("open_session_chat:", error);
return null; return null;
} }
}, [currentServiceId]); },
[currentServiceId]
);
const getChatHistory = useCallback(async () => { const getChatHistory = useCallback(async () => {
try { try {
let response: any let response: any;
if (isTauri) { if (isTauri) {
if (!currentServiceId) return []; if (!currentServiceId) return [];
response = await chat_history({ response = await chat_history({
serverId: currentServiceId, serverId: currentServiceId,
from: 0, from: 0,
size: 20, size: 20,
query: keyword,
}); });
response = JSON.parse(response || ""); response = JSON.parse(response || "");
} else { } else {
const [error, res] = await Get(`/chat/_history`, { const [error, res] = await Get(`/chat/_history`, {
from: 0, from: 0,
size: 20, size: 20,
}) });
if (error) { if (error) {
console.error('_history', error); console.error("_history", error);
return [] return [];
} }
response = res response = res;
} }
console.log("_history", response); console.log("_history", response);
const hits = response?.hits?.hits || []; const hits = response?.hits?.hits || [];
setChats(hits);
return hits; return hits;
} catch (error) { } catch (error) {
console.error("chat_history:", error); console.error("chat_history:", error);
return []; return [];
} }
}, [currentServiceId]); }, [currentServiceId, keyword]);
useEffect(() => {
getChatHistory();
}, [keyword]);
const createChatWindow = useCallback(async (createWin: any) => { const createChatWindow = useCallback(async (createWin: any) => {
if (isTauri) { if (isTauri) {
createWin && createWin({ createWin &&
label: "chat", createWin({
title: "Coco Chat", label: "chat",
dragDropEnabled: true, title: "Coco Chat",
center: true, dragDropEnabled: true,
width: 1000, center: true,
height: 800, width: 1000,
minWidth: 1000, height: 800,
minHeight: 800, minWidth: 1000,
alwaysOnTop: false, minHeight: 800,
skipTaskbar: false, alwaysOnTop: false,
decorations: true, skipTaskbar: false,
closable: true, decorations: true,
url: "/ui/chat", closable: true,
}); url: "/ui/chat",
});
} }
}, []); }, []);
const handleSearch = (keyword: string) => {
setKeyword(keyword);
};
const handleRename = async (chat: Chat, title: string) => {
if (!currentServiceId) return;
await update_session_chat({
serverId: currentServiceId,
sessionId: chat?._id,
title,
});
getChatHistory();
};
const handleDelete = async (id: string) => {
if (!currentServiceId) return;
await delete_session_chat(currentServiceId, id);
getChatHistory();
};
return { return {
chatClose, chatClose,
cancelChat, cancelChat,
@@ -347,6 +440,9 @@ export function useChatActions(
handleSendMessage, handleSendMessage,
openSessionChat, openSessionChat,
getChatHistory, getChatHistory,
createChatWindow createChatWindow,
handleSearch,
handleRename,
handleDelete,
}; };
} }

View File

@@ -214,9 +214,6 @@ export default function Chat({}: ChatProps) {
const handleRename = async (chat: typeChat, title: string) => { const handleRename = async (chat: typeChat, title: string) => {
if (!currentService?.id) return; if (!currentService?.id) return;
console.log("chat", chat);
console.log("title", title);
await update_session_chat({ await update_session_chat({
serverId: currentService.id, serverId: currentService.id,
sessionId: chat?._id, sessionId: chat?._id,

View File

@@ -20,7 +20,7 @@ export default function Layout() {
function updateBodyClass(path: string) { function updateBodyClass(path: string) {
const body = document.body; const body = document.body;
body.className = ""; body.classList.remove("input-body");
if (path === "/ui") { if (path === "/ui") {
body.classList.add("input-body"); body.classList.add("input-body");
@@ -28,9 +28,11 @@ export default function Layout() {
} }
useMount(async () => { useMount(async () => {
const unlistenTheme = await platformAdapter.listenThemeChanged((theme: AppTheme) => { const unlistenTheme = await platformAdapter.listenThemeChanged(
setTheme(theme); (theme: AppTheme) => {
}); setTheme(theme);
}
);
platformAdapter.onThemeChanged(({ payload }) => { platformAdapter.onThemeChanged(({ payload }) => {
if (activeTheme !== "auto") return; if (activeTheme !== "auto") return;
@@ -100,9 +102,5 @@ export default function Layout() {
event.preventDefault(); event.preventDefault();
}); });
return ( return <Outlet />;
<div className="coco-container">
<Outlet />
</div>
);
} }

View File

@@ -1,7 +1,11 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
export default { export default {
content: ["./index.html", "./src/**/*.{html,js,jsx,ts,tsx}", "./src/**/*.css"], content: [
important: '.coco-container', "./index.html",
"./src/**/*.{html,js,jsx,ts,tsx}",
"./src/**/*.css",
],
important: ".coco-container",
theme: { theme: {
extend: { extend: {
backgroundColor: { backgroundColor: {