mirror of
https://github.com/infinilabs/coco-app.git
synced 2025-12-16 11:37:47 +01:00
* style: modify the style * style: adjust page style * style: web style * docs: update notes
380 lines
11 KiB
TypeScript
380 lines
11 KiB
TypeScript
import {
|
|
useEffect,
|
|
useRef,
|
|
useCallback,
|
|
useReducer,
|
|
Suspense,
|
|
memo,
|
|
useState,
|
|
} from "react";
|
|
import clsx from "clsx";
|
|
import { useMount } from "ahooks";
|
|
|
|
import Search from "@/components/Search/Search";
|
|
import InputBox from "@/components/Search/InputBox";
|
|
import ChatAI, { ChatAIRef } from "@/components/Assistant/Chat";
|
|
import UpdateApp from "@/components/UpdateApp";
|
|
import { isLinux, isWin } from "@/utils/platform";
|
|
import { appReducer, initialAppState } from "@/reducers/appReducer";
|
|
import { useWindowEvents } from "@/hooks/useWindowEvents";
|
|
import { useAppStore } from "@/stores/appStore";
|
|
import { useAuthStore } from "@/stores/authStore";
|
|
import platformAdapter from "@/utils/platformAdapter";
|
|
import { useStartupStore } from "@/stores/startupStore";
|
|
import { DataSource } from "@/types/commands";
|
|
import { useThemeStore } from "@/stores/themeStore";
|
|
import { Get } from "@/api/axiosRequest";
|
|
|
|
interface SearchChatProps {
|
|
isTauri?: boolean;
|
|
hasModules?: string[];
|
|
defaultModule?: "search" | "chat";
|
|
|
|
hasFeature?: string[];
|
|
showChatHistory?: boolean;
|
|
|
|
theme?: "auto" | "light" | "dark";
|
|
searchPlaceholder?: string;
|
|
chatPlaceholder?: string;
|
|
|
|
hideCoco?: () => void;
|
|
setIsPinned?: (value: boolean) => void;
|
|
querySearch: (input: string) => Promise<any>;
|
|
queryDocuments: (
|
|
from: number,
|
|
size: number,
|
|
queryStrings: any
|
|
) => Promise<any>;
|
|
onModeChange?: (isChatMode: boolean) => void;
|
|
}
|
|
|
|
function SearchChat({
|
|
isTauri = true,
|
|
hasModules = ["search", "chat"],
|
|
defaultModule = "search",
|
|
hasFeature = ["think", "search", "think_active", "search_active"],
|
|
theme,
|
|
hideCoco,
|
|
querySearch,
|
|
queryDocuments,
|
|
searchPlaceholder,
|
|
chatPlaceholder,
|
|
showChatHistory = true,
|
|
setIsPinned,
|
|
onModeChange,
|
|
}: SearchChatProps) {
|
|
const customInitialState = {
|
|
...initialAppState,
|
|
isDeepThinkActive: hasFeature.includes("think_active"),
|
|
isSearchActive: hasFeature.includes("search_active"),
|
|
};
|
|
|
|
const [state, dispatch] = useReducer(appReducer, customInitialState);
|
|
const {
|
|
isChatMode,
|
|
input,
|
|
isTransitioned,
|
|
isSearchActive,
|
|
isDeepThinkActive,
|
|
isTyping,
|
|
} = state;
|
|
const [isWin10, setIsWin10] = useState(false);
|
|
|
|
useWindowEvents();
|
|
|
|
const initializeListeners = useAppStore((state) => state.initializeListeners);
|
|
const initializeListeners_auth = useAuthStore(
|
|
(state) => state.initializeListeners
|
|
);
|
|
|
|
const setTheme = useThemeStore((state) => state.setTheme);
|
|
|
|
useMount(async () => {
|
|
const isWin10 = await platformAdapter.isWindows10();
|
|
|
|
setIsWin10(isWin10);
|
|
});
|
|
|
|
useEffect(() => {
|
|
let mounted = true;
|
|
|
|
const init = async () => {
|
|
if (!mounted) return;
|
|
await initializeListeners();
|
|
await initializeListeners_auth();
|
|
await platformAdapter.invokeBackend("get_app_search_source");
|
|
if (theme && mounted) {
|
|
setTheme(theme);
|
|
}
|
|
};
|
|
|
|
init();
|
|
|
|
return () => {
|
|
mounted = false;
|
|
};
|
|
}, []);
|
|
|
|
const chatAIRef = useRef<ChatAIRef>(null);
|
|
|
|
const changeMode = useCallback(async (value: boolean) => {
|
|
dispatch({ type: "SET_CHAT_MODE", payload: value });
|
|
onModeChange?.(value);
|
|
}, []);
|
|
|
|
const handleSendMessage = useCallback(
|
|
async (value: string) => {
|
|
dispatch({ type: "SET_INPUT", payload: value });
|
|
if (isChatMode) {
|
|
chatAIRef.current?.init(value);
|
|
}
|
|
},
|
|
[isChatMode]
|
|
);
|
|
|
|
const cancelChat = useCallback(() => {
|
|
chatAIRef.current?.cancelChat();
|
|
}, []);
|
|
|
|
const reconnect = useCallback(() => {
|
|
chatAIRef.current?.reconnect();
|
|
}, []);
|
|
|
|
const setInput = useCallback((value: string) => {
|
|
dispatch({ type: "SET_INPUT", payload: value });
|
|
}, []);
|
|
|
|
const toggleSearchActive = useCallback(() => {
|
|
dispatch({ type: "TOGGLE_SEARCH_ACTIVE" });
|
|
}, []);
|
|
|
|
const toggleDeepThinkActive = useCallback(() => {
|
|
dispatch({ type: "TOGGLE_DEEP_THINK_ACTIVE" });
|
|
}, []);
|
|
|
|
const LoadingFallback = () => (
|
|
<div className="flex items-center justify-center h-full">loading...</div>
|
|
);
|
|
|
|
const getFileUrl = useCallback((path: string) => {
|
|
return platformAdapter.convertFileSrc(path);
|
|
}, []);
|
|
|
|
const openSetting = useCallback(() => {
|
|
return platformAdapter.emitEvent("open_settings", "");
|
|
}, []);
|
|
|
|
const setWindowAlwaysOnTop = useCallback(async (isPinned: boolean) => {
|
|
setIsPinned && setIsPinned(isPinned);
|
|
return platformAdapter.setAlwaysOnTop(isPinned);
|
|
}, []);
|
|
|
|
const getDataSourcesByServer = useCallback(
|
|
async (
|
|
serverId: string,
|
|
options?: {
|
|
from?: number;
|
|
size?: number;
|
|
query?: string;
|
|
}
|
|
): Promise<DataSource[]> => {
|
|
if (isTauri) {
|
|
return platformAdapter.invokeBackend("get_datasources_by_server", {
|
|
id: serverId,
|
|
options,
|
|
});
|
|
} else {
|
|
const [error, response]: any = await Get("/datasource/_search");
|
|
if (error) {
|
|
console.error("_search", error);
|
|
return [];
|
|
}
|
|
const res = response?.hits?.hits?.map((item: any) => {
|
|
return {
|
|
...item,
|
|
id: item._source.id,
|
|
name: item._source.name,
|
|
};
|
|
});
|
|
return res || [];
|
|
}
|
|
},
|
|
[]
|
|
);
|
|
|
|
const setupWindowFocusListener = useCallback(async (callback: () => void) => {
|
|
return platformAdapter.listenEvent("tauri://focus", callback);
|
|
}, []);
|
|
|
|
const checkScreenPermission = useCallback(async () => {
|
|
return platformAdapter.checkScreenRecordingPermission();
|
|
}, []);
|
|
|
|
const requestScreenPermission = useCallback(() => {
|
|
return platformAdapter.requestScreenRecordingPermission();
|
|
}, []);
|
|
|
|
const getScreenMonitors = useCallback(async () => {
|
|
return platformAdapter.getScreenshotableMonitors();
|
|
}, []);
|
|
|
|
const getScreenWindows = useCallback(async () => {
|
|
return platformAdapter.getScreenshotableWindows();
|
|
}, []);
|
|
|
|
const captureMonitorScreenshot = useCallback(async (id: number) => {
|
|
return platformAdapter.captureMonitorScreenshot(id);
|
|
}, []);
|
|
|
|
const captureWindowScreenshot = useCallback(async (id: number) => {
|
|
return platformAdapter.captureWindowScreenshot(id);
|
|
}, []);
|
|
|
|
const openFileDialog = useCallback(async (options: { multiple: boolean }) => {
|
|
return platformAdapter.openFileDialog(options);
|
|
}, []);
|
|
|
|
const getFileMetadata = useCallback(async (path: string) => {
|
|
return platformAdapter.getFileMetadata(path);
|
|
}, []);
|
|
|
|
const getFileIcon = useCallback(async (path: string, size: number) => {
|
|
return platformAdapter.getFileIcon(path, size);
|
|
}, []);
|
|
|
|
const checkUpdate = useCallback(async () => {
|
|
return platformAdapter.checkUpdate();
|
|
}, []);
|
|
|
|
const relaunchApp = useCallback(async () => {
|
|
return platformAdapter.relaunchApp();
|
|
}, []);
|
|
|
|
const defaultStartupWindow = useStartupStore((state) => {
|
|
return state.defaultStartupWindow;
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (platformAdapter.isTauri()) {
|
|
changeMode(defaultStartupWindow === "chatMode");
|
|
} else {
|
|
if (hasModules?.length === 1 && hasModules?.includes("chat")) {
|
|
changeMode(true);
|
|
} else {
|
|
changeMode(defaultModule === "chat");
|
|
}
|
|
}
|
|
}, []);
|
|
|
|
return (
|
|
<div
|
|
data-tauri-drag-region={isTauri}
|
|
className={clsx(
|
|
"m-auto overflow-hidden relative bg-no-repeat bg-cover bg-center bg-white dark:bg-black",
|
|
[
|
|
isTransitioned
|
|
? "bg-chat_bg_light dark:bg-chat_bg_dark"
|
|
: "bg-search_bg_light dark:bg-search_bg_dark",
|
|
],
|
|
{
|
|
"size-full": !isTauri,
|
|
"w-screen h-screen": isTauri,
|
|
"rounded-md": !isWin,
|
|
"border border-[#E6E6E6] dark:border-[#272626]": isTauri && isLinux,
|
|
"border-t border-t-[#999] dark:border-t-[#333]": isTauri && isWin10,
|
|
}
|
|
)}
|
|
>
|
|
<div
|
|
data-tauri-drag-region={isTauri}
|
|
className={`p-2 absolute w-full flex justify-center transition-all duration-500 ${
|
|
isTransitioned
|
|
? "top-[calc(100%-84px)] h-[84px] border-t"
|
|
: "top-0 h-[84px] border-b"
|
|
} border-[#E6E6E6] dark:border-[#272626]`}
|
|
>
|
|
<InputBox
|
|
isChatMode={isChatMode}
|
|
inputValue={input}
|
|
onSend={handleSendMessage}
|
|
disabled={isTyping}
|
|
disabledChange={cancelChat}
|
|
changeMode={changeMode}
|
|
changeInput={setInput}
|
|
reconnect={reconnect}
|
|
isSearchActive={isSearchActive}
|
|
setIsSearchActive={toggleSearchActive}
|
|
isDeepThinkActive={isDeepThinkActive}
|
|
setIsDeepThinkActive={toggleDeepThinkActive}
|
|
hasFeature={hasFeature}
|
|
getDataSourcesByServer={getDataSourcesByServer}
|
|
setupWindowFocusListener={setupWindowFocusListener}
|
|
checkScreenPermission={checkScreenPermission}
|
|
requestScreenPermission={requestScreenPermission}
|
|
getScreenMonitors={getScreenMonitors}
|
|
getScreenWindows={getScreenWindows}
|
|
captureMonitorScreenshot={captureMonitorScreenshot}
|
|
captureWindowScreenshot={captureWindowScreenshot}
|
|
openFileDialog={openFileDialog}
|
|
getFileMetadata={getFileMetadata}
|
|
getFileIcon={getFileIcon}
|
|
hasModules={hasModules}
|
|
searchPlaceholder={searchPlaceholder}
|
|
chatPlaceholder={chatPlaceholder}
|
|
hideCoco={hideCoco}
|
|
/>
|
|
</div>
|
|
|
|
<div
|
|
data-tauri-drag-region={isTauri}
|
|
className={`absolute w-full transition-opacity duration-500 ${
|
|
isTransitioned ? "opacity-0 pointer-events-none" : "opacity-100"
|
|
} bottom-0 h-[calc(100%-84px)] `}
|
|
>
|
|
<Suspense fallback={<LoadingFallback />}>
|
|
<Search
|
|
key="Search"
|
|
isTauri={isTauri}
|
|
input={input}
|
|
isChatMode={isChatMode}
|
|
changeInput={setInput}
|
|
querySearch={querySearch}
|
|
queryDocuments={queryDocuments}
|
|
hideCoco={hideCoco}
|
|
openSetting={openSetting}
|
|
setWindowAlwaysOnTop={setWindowAlwaysOnTop}
|
|
/>
|
|
</Suspense>
|
|
</div>
|
|
|
|
<div
|
|
data-tauri-drag-region={isTauri}
|
|
className={`absolute w-full transition-all duration-500 select-auto ${
|
|
isTransitioned
|
|
? "top-0 opacity-100 pointer-events-auto"
|
|
: "-top-[506px] opacity-0 pointer-events-none"
|
|
} h-[calc(100%-90px)]`}
|
|
>
|
|
{isTransitioned && isChatMode ? (
|
|
<Suspense fallback={<LoadingFallback />}>
|
|
<ChatAI
|
|
ref={chatAIRef}
|
|
key="ChatAI"
|
|
isTransitioned={isTransitioned}
|
|
changeInput={setInput}
|
|
isSearchActive={isSearchActive}
|
|
isDeepThinkActive={isDeepThinkActive}
|
|
getFileUrl={getFileUrl}
|
|
showChatHistory={showChatHistory}
|
|
/>
|
|
</Suspense>
|
|
) : null}
|
|
</div>
|
|
|
|
<UpdateApp checkUpdate={checkUpdate} relaunchApp={relaunchApp} />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default memo(SearchChat);
|