diff --git a/docs/content.en/docs/release-notes/_index.md b/docs/content.en/docs/release-notes/_index.md index f4135e8c..f3daa03b 100644 --- a/docs/content.en/docs/release-notes/_index.md +++ b/docs/content.en/docs/release-notes/_index.md @@ -29,17 +29,21 @@ Information about release notes of Coco Server is provided here. ### 🐛 Bug fix +- fix: solve the problem of modifying the assistant in the chat #476 - fix: several issues around search #502 - fix: fixed the newly created session has no title when it is deleted #511 - fix: loading chat history for potential empty attachments - fix: datasource & MCP list synchronization update #521 +- fix: app icon & category icon #529 - fix: show only enabled datasource & MCP list +- fix: server image loading failure #534 - fix: panic when fetching app metadata on Windows #538 +- fix: service switching error #539 +- fix: switch server assistant and session session unchanged #540 ### ✈️ Improvements - chore: adjust list error message #475 -- fix: solve the problem of modifying the assistant in the chat #476 - chore: refine wording on search failure - chore:search and MCP show hidden logic #494 - chore: greetings show hidden logic #496 @@ -54,12 +58,9 @@ Information about release notes of Coco Server is provided here. - refactor: optimizing list styles in markdown content #520 - feat: add a component for text reading aloud #522 - style: history component styles #528 -- fix: app icon & category icon #529 - style: search error styles #533 -- fix: server image loading failure #534 - chore: skip register server that not logged in #536 - refactor: service info related components #537 -- fix: service switching error #539 - chore: chat content can be copied #539 ## 0.4.0 (2025-04-27) diff --git a/src/components/Assistant/AssistantFetcher.tsx b/src/components/Assistant/AssistantFetcher.tsx new file mode 100644 index 00000000..815809f0 --- /dev/null +++ b/src/components/Assistant/AssistantFetcher.tsx @@ -0,0 +1,118 @@ +import { useRef } from "react"; + +import { Post } from "@/api/axiosRequest"; +import platformAdapter from "@/utils/platformAdapter"; +import { useConnectStore } from "@/stores/connectStore"; +import { useAppStore } from "@/stores/appStore"; + +interface AssistantFetcherProps { + debounceKeyword?: string; + assistantIDs?: string[]; +} + +export const AssistantFetcher = ({ + debounceKeyword = "", + assistantIDs = [], +}: AssistantFetcherProps) => { + const isTauri = useAppStore((state) => state.isTauri); + + const currentService = useConnectStore((state) => state.currentService); + + const currentAssistant = useConnectStore((state) => state.currentAssistant); + const setCurrentAssistant = useConnectStore((state) => { + return state.setCurrentAssistant; + }); + + const lastServerId = useRef(null); + + const fetchAssistant = async (params: { + current: number; + pageSize: number; + }) => { + try { + const { pageSize, current } = params; + + const from = (current - 1) * pageSize; + const size = pageSize; + + let response: any; + + const body: Record = { + serverId: currentService?.id, + from, + size, + }; + + body.query = { + bool: { + must: [{ term: { enabled: true } }], + }, + }; + + if (debounceKeyword) { + body.query.bool.must.push({ + query_string: { + fields: ["combined_fulltext"], + query: debounceKeyword, + fuzziness: "AUTO", + fuzzy_prefix_length: 2, + fuzzy_max_expansions: 10, + fuzzy_transpositions: true, + allow_leading_wildcard: false, + }, + }); + } + if (assistantIDs.length > 0) { + body.query.bool.must.push({ + terms: { + id: assistantIDs.map((id) => id), + }, + }); + } + + if (isTauri) { + if (!currentService?.id) { + throw new Error("currentService is undefined"); + } + + response = await platformAdapter.commands("assistant_search", body); + } else { + const [error, res] = await Post(`/assistant/_search`, body); + + if (error) { + throw new Error(error); + } + + response = res; + } + + let assistantList = response?.hits?.hits ?? []; + + console.log("assistantList", assistantList); + + if ( + !currentAssistant?._id || + currentService?.id !== lastServerId.current + ) { + setCurrentAssistant(assistantList[0]); + } + lastServerId.current = currentService?.id; + + return { + total: response.hits.total.value, + list: assistantList, + }; + } catch (error) { + setCurrentAssistant(null); + + console.error("assistant_search", error); + + return { + total: 0, + list: [], + }; + } + }; + + return { fetchAssistant }; +}; diff --git a/src/components/Assistant/AssistantList.tsx b/src/components/Assistant/AssistantList.tsx index 3514b06e..5cf1f40d 100644 --- a/src/components/Assistant/AssistantList.tsx +++ b/src/components/Assistant/AssistantList.tsx @@ -1,4 +1,4 @@ -import { useState, useRef, useCallback, useMemo } from "react"; +import { useState, useRef, useCallback } from "react"; import { ChevronDownIcon, RefreshCw, @@ -10,40 +10,28 @@ import { useTranslation } from "react-i18next"; import { isNil } from "lodash-es"; import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react"; import { - useAsyncEffect, useDebounce, useKeyPress, usePagination, - useReactive, } from "ahooks"; import clsx from "clsx"; -import { useAppStore } from "@/stores/appStore"; import logoImg from "@/assets/icon.svg"; -import platformAdapter from "@/utils/platformAdapter"; import VisibleKey from "@/components/Common/VisibleKey"; import { useConnectStore } from "@/stores/connectStore"; import FontIcon from "@/components/Common/Icons/FontIcon"; -import { useChatStore } from "@/stores/chatStore"; import { useShortcutsStore } from "@/stores/shortcutsStore"; -import { Post } from "@/api/axiosRequest"; -import NoDataImage from "../Common/NoDataImage"; -import PopoverInput from "../Common/PopoverInput"; +import NoDataImage from "@/components/Common/NoDataImage"; +import PopoverInput from "@/components/Common/PopoverInput"; +import { AssistantFetcher } from "./AssistantFetcher"; interface AssistantListProps { assistantIDs?: string[]; } -interface State { - allAssistants: any[]; -} - export function AssistantList({ assistantIDs = [] }: AssistantListProps) { const { t } = useTranslation(); - const { connected } = useChatStore(); - const isTauri = useAppStore((state) => state.isTauri); - const setAssistantList = useConnectStore((state) => state.setAssistantList); const currentService = useConnectStore((state) => state.currentService); const currentAssistant = useConnectStore((state) => state.currentAssistant); const setCurrentAssistant = useConnectStore((state) => { @@ -57,130 +45,15 @@ export function AssistantList({ assistantIDs = [] }: AssistantListProps) { const searchInputRef = useRef(null); const [keyword, setKeyword] = useState(""); const debounceKeyword = useDebounce(keyword, { wait: 500 }); - const state = useReactive({ - allAssistants: [], + + const { fetchAssistant } = AssistantFetcher({ + debounceKeyword, + assistantIDs, }); - const currentServiceId = useMemo(() => { - return currentService?.id; - }, [connected, currentService?.id]); - - const fetchAssistant = async (params: { - current: number; - pageSize: number; - }) => { - try { - const { pageSize, current } = params; - - const from = (current - 1) * pageSize; - const size = pageSize; - - let response: any; - - const body: Record = { - serverId: currentServiceId, - from, - size, - }; - - body.query = { - bool: { - must: [{ term: { enabled: true } }], - }, - }; - - if (debounceKeyword) { - body.query.bool.must.push({ - query_string: { - fields: ["combined_fulltext"], - query: debounceKeyword, - fuzziness: "AUTO", - fuzzy_prefix_length: 2, - fuzzy_max_expansions: 10, - fuzzy_transpositions: true, - allow_leading_wildcard: false, - }, - }); - } - if (assistantIDs.length > 0) { - body.query.bool.must.push({ - terms: { - id: assistantIDs.map((id) => id), - }, - }); - } - - if (isTauri) { - if (!currentServiceId) { - throw new Error("currentServiceId is undefined"); - } - - response = await platformAdapter.commands("assistant_search", body); - } else { - const [error, res] = await Post(`/assistant/_search`, body); - - if (error) { - throw new Error(error); - } - - response = res; - } - - let assistantList = response?.hits?.hits ?? []; - - console.log("assistantList", assistantList); - - for (const item of assistantList) { - const index = state.allAssistants.findIndex((allItem: any) => { - return item._id === allItem._id; - }); - - if (index === -1) { - state.allAssistants.push(item); - } else { - state.allAssistants[index] = item; - } - } - - //console.log("state.allAssistants", state.allAssistants); - - const matched = state.allAssistants.find((item: any) => { - return item._id === currentAssistant?._id; - }); - - //console.log("matched", matched); - - if (matched) { - setCurrentAssistant(matched); - } else { - setCurrentAssistant(assistantList[0]); - } - - return { - total: response.hits.total.value, - list: assistantList, - }; - } catch (error) { - setCurrentAssistant(null); - - console.error("assistant_search", error); - - return { - total: 0, - list: [], - }; - } - }; - - useAsyncEffect(async () => { - const data = await fetchAssistant({ current: 1, pageSize: 1000 }); - - setAssistantList(data.list); - }, [currentServiceId]); - const { pagination, runAsync } = usePagination(fetchAssistant, { defaultPageSize: 5, - refreshDeps: [currentServiceId, debounceKeyword], + refreshDeps: [currentService?.id, debounceKeyword], onSuccess(data) { setAssistants(data.list); }, diff --git a/src/components/Assistant/Chat.tsx b/src/components/Assistant/Chat.tsx index 6405781e..8a529996 100644 --- a/src/components/Assistant/Chat.tsx +++ b/src/components/Assistant/Chat.tsx @@ -75,7 +75,6 @@ const ChatAI = memo( const { curChatEnd, setCurChatEnd, connected, setConnected } = useChatStore(); - const currentService = useConnectStore((state) => state.currentService); const visibleStartPage = useConnectStore((state) => { return state.visibleStartPage; }); @@ -151,7 +150,6 @@ const ChatAI = memo( handleRename, handleDelete, } = useChatActions( - currentService?.id, setActiveChat, setCurChatEnd, setTimedoutShow, @@ -366,6 +364,7 @@ const ChatAI = memo( loadingStep={loadingStep} timedoutShow={timedoutShow} Question={Question} + assistantIDs={assistantIDs} handleSendMessage={(value) => handleSendMessage(value, activeChat) } diff --git a/src/components/Assistant/ChatContent.tsx b/src/components/Assistant/ChatContent.tsx index 4a7bb486..63e17188 100644 --- a/src/components/Assistant/ChatContent.tsx +++ b/src/components/Assistant/ChatContent.tsx @@ -1,7 +1,5 @@ import { useRef, useEffect, UIEvent, useState } from "react"; import { useTranslation } from "react-i18next"; -import { ArrowDown } from "lucide-react"; -import clsx from "clsx"; import { ChatMessage } from "@/components/ChatMessage"; import { Greetings } from "./Greetings"; @@ -9,10 +7,10 @@ import FileList from "@/components/Assistant/FileList"; import { useChatScroll } from "@/hooks/useChatScroll"; import { useChatStore } from "@/stores/chatStore"; import type { Chat, IChunkData } from "@/types/chat"; -// import SessionFile from "./SessionFile"; import { useConnectStore } from "@/stores/connectStore"; import SessionFile from "./SessionFile"; import Splash from "./Splash"; +import ScrollToBottom from "@/components/Common/ScrollToBottom"; interface ChatContentProps { activeChat?: Chat; @@ -27,6 +25,7 @@ interface ChatContentProps { loadingStep?: Record; timedoutShow: boolean; Question: string; + assistantIDs?: string[]; handleSendMessage: (content: string, newChat?: Chat) => void; getFileUrl: (path: string) => string; } @@ -44,6 +43,7 @@ export const ChatContent = ({ loadingStep, timedoutShow, Question, + assistantIDs, handleSendMessage, getFileUrl, }: ChatContentProps) => { @@ -173,24 +173,9 @@ export const ChatContent = ({ {sessionId && } - + - + ); }; diff --git a/src/components/Assistant/ChatHeader.tsx b/src/components/Assistant/ChatHeader.tsx index 4b6eddd4..d1dcd685 100644 --- a/src/components/Assistant/ChatHeader.tsx +++ b/src/components/Assistant/ChatHeader.tsx @@ -114,6 +114,7 @@ export function ChatHeader({ activeChat?._source?.message || activeChat?._id} + {isTauri ? (
+ ); +}; + +export default ScrollToBottom; diff --git a/src/hooks/useChatActions.ts b/src/hooks/useChatActions.ts index f67d3f3c..db21c803 100644 --- a/src/hooks/useChatActions.ts +++ b/src/hooks/useChatActions.ts @@ -9,7 +9,6 @@ import { useChatStore } from "@/stores/chatStore"; import { useSearchStore } from "@/stores/searchStore"; export function useChatActions( - currentServiceId: string | undefined, setActiveChat: (chat: Chat | undefined) => void, setCurChatEnd: (value: boolean) => void, setTimedoutShow: (value: boolean) => void, @@ -34,6 +33,7 @@ export function useChatActions( const setVisibleStartPage = useConnectStore((state) => { return state.setVisibleStartPage; }); + const currentService = useConnectStore((state) => state.currentService); const [keyword, setKeyword] = useState(""); @@ -43,9 +43,9 @@ export function useChatActions( let response: any; if (isTauri) { - if (!currentServiceId) return; + if (!currentService?.id) return; response = await platformAdapter.commands("close_session_chat", { - serverId: currentServiceId, + serverId: currentService?.id, sessionId: activeChat?._id, }); response = response ? JSON.parse(response) : null; @@ -59,7 +59,7 @@ export function useChatActions( console.log("_close", response); }, - [currentServiceId, isTauri] + [currentService?.id, isTauri] ); const cancelChat = useCallback( @@ -68,9 +68,9 @@ export function useChatActions( if (!activeChat?._id) return; let response: any; if (isTauri) { - if (!currentServiceId) return; + if (!currentService?.id) return; response = await platformAdapter.commands("cancel_session_chat", { - serverId: currentServiceId, + serverId: currentService?.id, sessionId: activeChat?._id, }); response = response ? JSON.parse(response) : null; @@ -83,7 +83,7 @@ export function useChatActions( } console.log("_cancel", response); }, - [currentServiceId, isTauri] + [currentService?.id, isTauri] ); const chatHistory = useCallback( @@ -92,9 +92,9 @@ export function useChatActions( let response: any; if (isTauri) { - if (!currentServiceId) return; + if (!currentService?.id) return; response = await platformAdapter.commands("session_chat_history", { - serverId: currentServiceId, + serverId: currentService?.id, sessionId: chat?._id, from: 0, size: 100, @@ -118,7 +118,7 @@ export function useChatActions( callback && callback(updatedChat); setVisibleStartPage(false); }, - [currentServiceId, isTauri] + [currentService?.id, isTauri] ); const createNewChat = useCallback( @@ -146,9 +146,9 @@ export function useChatActions( }; let response: any; if (isTauri) { - if (!currentServiceId) return; + if (!currentService?.id) return; response = await platformAdapter.commands("new_chat", { - serverId: currentServiceId, + serverId: currentService?.id, websocketId: sessionId, message: value, queryParams, @@ -186,7 +186,7 @@ export function useChatActions( }, [ isTauri, - currentServiceId, + currentService?.id, sourceDataIds, MCPIds, isSearchActive, @@ -222,9 +222,9 @@ export function useChatActions( } let response: any; if (isTauri) { - if (!currentServiceId) return; + if (!currentService?.id) return; response = await platformAdapter.commands("send_message", { - serverId: currentServiceId, + serverId: currentService?.id, websocketId: sessionId, sessionId: newChat?._id, queryParams, @@ -260,7 +260,7 @@ export function useChatActions( }, [ isTauri, - currentServiceId, + currentService?.id, sourceDataIds, MCPIds, isSearchActive, @@ -295,9 +295,9 @@ export function useChatActions( let response: any; if (isTauri) { - if (!currentServiceId) return; + if (!currentService?.id) return; response = await platformAdapter.commands("open_session_chat", { - serverId: currentServiceId, + serverId: currentService?.id, sessionId: chat?._id, }); response = response ? JSON.parse(response) : null; @@ -310,19 +310,19 @@ export function useChatActions( return response; }, - [currentServiceId, isTauri] + [currentService?.id, isTauri] ); const getChatHistory = useCallback(async () => { let response: any; if (isTauri) { - if (!currentServiceId || !isLogin) { + if (!currentService?.id || !isLogin) { setChats([]); return } response = await platformAdapter.commands("chat_history", { - serverId: currentServiceId, + serverId: currentService?.id, from: 0, size: 100, query: keyword, @@ -336,14 +336,15 @@ export function useChatActions( }); response = res; } + console.log("_history", response); const hits = response?.hits?.hits || []; setChats(hits); - }, [currentServiceId, keyword, isTauri]); + }, [currentService?.id, keyword, isTauri]); useEffect(() => { showChatHistory && connected && getChatHistory(); - }, [showChatHistory, connected, getChatHistory]); + }, [showChatHistory, connected, getChatHistory, currentService?.id]); const createChatWindow = useCallback(async (createWin: any) => { if (isTauri) { @@ -371,20 +372,20 @@ export function useChatActions( }; const handleRename = useCallback(async (chatId: string, title: string) => { - if (!currentServiceId) return; + if (!currentService?.id) return; await platformAdapter.commands("update_session_chat", { - serverId: currentServiceId, + serverId: currentService?.id, sessionId: chatId, title, }); - }, [currentServiceId]); + }, [currentService?.id]); const handleDelete = useCallback(async (chatId: string) => { - if (!currentServiceId) return; + if (!currentService?.id) return; - await platformAdapter.commands("delete_session_chat", currentServiceId, chatId); - }, [currentServiceId]); + await platformAdapter.commands("delete_session_chat", currentService?.id, chatId); + }, [currentService?.id]); return { chatClose,