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>
</head>
<body>
<body class="coco-container">
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>

View File

@@ -149,6 +149,9 @@ const ChatAI = memo(
openSessionChat,
getChatHistory,
createChatWindow,
handleSearch,
handleRename,
handleDelete,
} = useChatActions(
currentService?.id,
setActiveChat,
@@ -158,6 +161,7 @@ const ChatAI = memo(
clearAllChunkData,
setQuestion,
curIdRef,
setChats,
isSearchActive,
isDeepThinkActive,
sourceDataIds,
@@ -210,7 +214,7 @@ const ChatAI = memo(
await handleSendMessage(value, activeChat, websocketSessionId);
}
} 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(
(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("");
}
}
},
[activeChat, chats, init, setActiveChat]
);
// const deleteChat = useCallback(
// (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("");
// }
// }
// },
// [activeChat, chats, init, setActiveChat]
// );
const handleOutsideClick = useCallback((e: MouseEvent) => {
const sidebar = document.querySelector("[data-sidebar]");
@@ -302,25 +306,25 @@ const ChatAI = memo(
};
}, [isSidebarOpenChat, handleOutsideClick]);
const fetchChatHistory = useCallback(async () => {
const hits = await getChatHistory();
setChats(hits);
}, [getChatHistory]);
// const fetchChatHistory = useCallback(async () => {
// const hits = await getChatHistory();
// setChats(hits);
// }, [getChatHistory]);
const setIsLoginChat = useCallback(
(value: boolean) => {
setIsLogin(value);
value && currentService && !setIsSidebarOpen && fetchChatHistory();
value && currentService && !setIsSidebarOpen && getChatHistory();
!value && setChats([]);
},
[currentService, setIsSidebarOpen, fetchChatHistory]
[currentService, setIsSidebarOpen, getChatHistory]
);
const toggleSidebar = useCallback(() => {
setIsSidebarOpenChat(!isSidebarOpenChat);
setIsSidebarOpen && setIsSidebarOpen(!isSidebarOpenChat);
!isSidebarOpenChat && fetchChatHistory();
}, [isSidebarOpenChat, setIsSidebarOpen, fetchChatHistory]);
!isSidebarOpenChat && getChatHistory();
}, [isSidebarOpenChat, setIsSidebarOpen, getChatHistory]);
return (
<div
@@ -332,10 +336,12 @@ const ChatAI = memo(
isSidebarOpen={isSidebarOpenChat}
chats={chats}
activeChat={activeChat}
onNewChat={clearChat}
// onNewChat={clearChat}
onSelectChat={onSelectChat}
onDeleteChat={deleteChat}
fetchChatHistory={fetchChatHistory}
onDeleteChat={handleDelete}
fetchChatHistory={getChatHistory}
onSearch={handleSearch}
onRename={handleRename}
/>
)}

View File

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

View File

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

View File

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

View File

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