-
- Quick AI access allows you to start a conversation immediately from the
- search box using the tab key.
-
+
{renderDescription()}
LinkedAssistant
@@ -147,4 +142,4 @@ const QuickAiAccess = () => {
);
};
-export default QuickAiAccess;
+export default SharedAi;
diff --git a/src/components/Settings/Extensions/components/Details/index.tsx b/src/components/Settings/Extensions/components/Details/index.tsx
index 571dc1d8..4c5a4afa 100644
--- a/src/components/Settings/Extensions/components/Details/index.tsx
+++ b/src/components/Settings/Extensions/components/Details/index.tsx
@@ -1,40 +1,65 @@
-import { useContext, useMemo } from "react";
-import { ExtensionsContext, Plugin } from "../..";
+import { useContext } from "react";
+
+import { ExtensionsContext } from "../..";
+import Applications from "./Applications";
+import Application from "./Application";
+import { useExtensionsStore } from "@/stores/extensionsStore";
+import SharedAi from "./SharedAi";
+import AiOverview from "./AiOverview";
const Details = () => {
- const { plugins, activeId } = useContext(ExtensionsContext);
+ const { rootState } = useContext(ExtensionsContext);
+ const quickAiAccessServer = useExtensionsStore((state) => {
+ return state.quickAiAccessServer;
+ });
+ const setQuickAiAccessServer = useExtensionsStore((state) => {
+ return state.setQuickAiAccessServer;
+ });
+ const quickAiAccessAssistant = useExtensionsStore((state) => {
+ return state.quickAiAccessAssistant;
+ });
+ const setQuickAiAccessAssistant = useExtensionsStore((state) => {
+ return state.setQuickAiAccessAssistant;
+ });
- const findPlugin = (plugins: Plugin[], id: string) => {
- for (const plugin of plugins) {
- const { children = [] } = plugin;
+ const renderContent = () => {
+ if (!rootState.activeExtension) return;
- if (plugin.id === id) {
- return plugin;
- }
+ const { id, type } = rootState.activeExtension;
- if (children.length > 0) {
- const matched = findPlugin(children, id) as Plugin;
+ if (id === "Applications") {
+ return
;
+ }
- if (!matched) continue;
+ if (type === "application") {
+ return
;
+ }
- return matched;
- }
+ if (id === "QuickAIAccess") {
+ return (
+
+ );
+ }
+
+ if (id === "AIOverview") {
+ return
;
}
};
- const currentPlugin = useMemo(() => {
- if (!activeId) return;
-
- return findPlugin(plugins, activeId);
- }, [activeId, plugins]);
-
return (
- {currentPlugin?.name}
+ {rootState.activeExtension?.title}
-
{currentPlugin?.detail}
+
{renderContent()}
);
};
diff --git a/src/components/Settings/Extensions/index.tsx b/src/components/Settings/Extensions/index.tsx
index 18ec8e9b..9adb33a5 100644
--- a/src/components/Settings/Extensions/index.tsx
+++ b/src/components/Settings/Extensions/index.tsx
@@ -1,210 +1,107 @@
-import {
- createContext,
- Dispatch,
- ReactElement,
- ReactNode,
- SetStateAction,
- useEffect,
- useMemo,
- useState,
-} from "react";
-import { Bot, Calculator, Folder } from "lucide-react";
-import { noop } from "lodash-es";
-import { useMount } from "ahooks";
+import { createContext, useEffect } from "react";
+import { useMount, useReactive } from "ahooks";
import { useTranslation } from "react-i18next";
+import type { LiteralUnion } from "type-fest";
-import ApplicationsDetail from "./components/Details/Applications";
-import Application from "./components/Details/Application";
import platformAdapter from "@/utils/platformAdapter";
import Content from "./components/Content";
import Details from "./components/Details";
-import QuickAiAccess from "./components/Details/QuickAiAccess";
+import { cloneDeep, sortBy } from "lodash-es";
+import { useExtensionsStore } from "@/stores/extensionsStore";
-export interface IApplication {
- path: string;
- name: string;
- iconPath: string;
- alias: string;
- hotkey: string;
- isDisabled: boolean;
+export type ExtensionId = LiteralUnion<
+ "Applications" | "Calculator" | "QuickAIAccess" | "AIOverview",
+ string
+>;
+
+type ExtensionType =
+ | "group"
+ | "extension"
+ | "application"
+ | "script"
+ | "quick_link"
+ | "setting"
+ | "calculator"
+ | "command"
+ | "ai_extension";
+
+export type ExtensionPlatform = "windows" | "macos" | "linux";
+
+interface ExtensionAction {
+ exec: string;
+ args: string[];
}
-export interface Plugin {
- id: string;
- icon: ReactElement;
- name: ReactNode;
- type?: "Group" | "Extension" | "Application";
+interface ExtensionQuickLink {
+ link: string;
+}
+
+export interface Extension {
+ id: ExtensionId;
+ type: ExtensionType;
+ icon: string;
+ title: string;
+ description: string;
alias?: string;
hotkey?: string;
- enabled?: boolean;
- detail?: ReactNode;
- children?: Plugin[];
- manualLoad?: boolean;
- loadChildren?: () => Promise
;
- onAliasChange?: (alias: string) => void;
- onHotkeyChange?: (hotkey: string) => void;
- onEnabledChange?: (enabled: boolean) => void;
+ enabled: boolean;
+ platforms?: ExtensionPlatform[];
+ action: ExtensionAction;
+ quick_link: ExtensionQuickLink;
+ commands?: Extension[];
+ scripts?: Extension[];
+ quick_links?: Extension[];
+ settings: Record;
}
-export interface ExtensionsContextType {
- plugins: Plugin[];
- setPlugins: Dispatch>;
- activeId?: string;
- setActiveId: (id: string) => void;
+interface State {
+ extensions: Extension[];
+ activeExtension?: Extension;
}
-export const ExtensionsContext = createContext({
- plugins: [],
- setPlugins: noop,
- setActiveId: noop,
+const INITIAL_STATE: State = {
+ extensions: [],
+};
+
+export const ExtensionsContext = createContext<{ rootState: State }>({
+ rootState: INITIAL_STATE,
});
-const Extensions = () => {
+export const Extensions = () => {
const { t } = useTranslation();
- const [apps, setApps] = useState([]);
- const [disabled, setDisabled] = useState([]);
- const [activeId, setActiveId] = useState();
-
- useMount(async () => {
- const disabled = await platformAdapter.invokeBackend(
- "get_disabled_local_query_sources"
- );
-
- setDisabled(disabled);
+ const state = useReactive(cloneDeep(INITIAL_STATE));
+ const setDisabledExtensions = useExtensionsStore((state) => {
+ return state.setDisabledExtensions;
});
- const loadApps = async () => {
- const apps = await platformAdapter.invokeBackend(
- "get_app_list"
+ useMount(async () => {
+ const result = await platformAdapter.invokeBackend<[boolean, Extension[]]>(
+ "list_extensions"
);
- const sortedApps = apps.sort((a, b) => {
- return a.name.localeCompare(b.name, undefined, {
- sensitivity: "base",
- });
- });
+ const extensions = result[1];
- setApps(sortedApps);
- };
+ const disabledExtensions = extensions.filter((item) => !item.enabled);
- const presetPlugins = useMemo(() => {
- const plugins: Plugin[] = [
- {
- id: "Applications",
- icon: ,
- name: t("settings.extensions.application.title"),
- type: "Group",
- detail: ,
- children: [],
- manualLoad: true,
- loadChildren: loadApps,
- },
- {
- id: "Calculator",
- icon: ,
- name: t("settings.extensions.calculator.title"),
- },
- {
- id: "QuickAiAccess",
- icon: ,
- name: "Quick AI Access",
- detail: ,
- },
- ];
+ setDisabledExtensions(disabledExtensions.map((item) => item.id));
- if (apps.length > 0) {
- for (const app of apps) {
- const { path, iconPath, isDisabled } = app;
-
- plugins[0].children?.push({
- ...app,
- id: path,
- type: "Application",
- icon: (
-
- ),
- enabled: !isDisabled,
- detail: ,
- onAliasChange(alias) {
- platformAdapter.invokeBackend("set_app_alias", {
- appPath: path,
- alias,
- });
-
- const nextApps = apps.map((item) => {
- if (item.path !== path) return item;
-
- return { ...item, alias };
- });
-
- setApps(nextApps);
- },
- onHotkeyChange(hotkey) {
- const command = `${hotkey ? "register" : "unregister"}_app_hotkey`;
-
- platformAdapter.invokeBackend(command, {
- appPath: path,
- hotkey,
- });
-
- const nextApps = apps.map((item) => {
- if (item.path !== path) return item;
-
- return { ...item, hotkey };
- });
-
- setApps(nextApps);
- },
- onEnabledChange(enabled) {
- const command = `${enabled ? "enable" : "disable"}_app_search`;
-
- platformAdapter.invokeBackend(command, {
- appPath: path,
- });
-
- const nextApps = apps.map((item) => {
- if (item.path !== path) return item;
-
- return { ...item, isDisabled: !enabled };
- });
-
- setApps(nextApps);
- },
- });
- }
- }
-
- return plugins;
- }, [apps]);
-
- const [plugins, setPlugins] = useState(presetPlugins);
+ state.extensions = sortBy(extensions, ["title"]);
+ });
useEffect(() => {
- setPlugins(presetPlugins);
- }, [presetPlugins]);
-
- useEffect(() => {
- setPlugins((prevPlugins) => {
- return prevPlugins.map((item) => {
- if (disabled.includes(item.id)) {
- return { ...item, enabled: false };
- }
-
- return item;
- });
+ const unsubscribe = useExtensionsStore.subscribe((state) => {
+ platformAdapter.emitEvent("change-extensions-store", state);
});
- }, [disabled]);
+
+ return () => {
+ unsubscribe();
+ };
+ });
return (
@@ -217,7 +114,7 @@ const Extensions = () => {
{t("settings.extensions.list.name")}
-
+
{t("settings.extensions.list.type")}
@@ -227,7 +124,7 @@ const Extensions = () => {
{t("settings.extensions.list.hotkey")}
-
+
{t("settings.extensions.list.enabled")}
diff --git a/src/components/Settings/SettingsSelectPro.tsx b/src/components/Settings/SettingsSelectPro.tsx
index 8a0168cd..4203b3f3 100644
--- a/src/components/Settings/SettingsSelectPro.tsx
+++ b/src/components/Settings/SettingsSelectPro.tsx
@@ -1,6 +1,6 @@
-// import { Select, SelectProps } from "@headlessui/react";
import { Select, SelectProps } from "@headlessui/react";
import clsx from "clsx";
+import { isArray } from "lodash-es";
import { ChevronDownIcon } from "lucide-react";
import { FC } from "react";
@@ -24,11 +24,11 @@ const SettingsSelectPro: FC
= (props) => {
} = props;
const renderOptions = () => {
- if (data) {
+ if (isArray(data)) {
return data.map((item) => {
return (
-
);
});
diff --git a/src/components/Settings/SettingsToggle.tsx b/src/components/Settings/SettingsToggle.tsx
index a3899f90..11c3f730 100644
--- a/src/components/Settings/SettingsToggle.tsx
+++ b/src/components/Settings/SettingsToggle.tsx
@@ -1,35 +1,27 @@
-import { Switch } from "@headlessui/react";
+import { Switch, SwitchProps } from "@headlessui/react";
import clsx from "clsx";
-interface SettingsToggleProps {
- checked: boolean;
- onChange: (checked: boolean) => void;
+interface SettingsToggleProps extends SwitchProps {
label: string;
className?: string;
}
-export default function SettingsToggle({
- checked,
- onChange,
- label,
- className,
-}: SettingsToggleProps) {
+export default function SettingsToggle(props: SettingsToggleProps) {
+ const { label, className, ...rest } = props;
+
return (
{label}
);
diff --git a/src/constants/index.ts b/src/constants/index.ts
index ddf7f910..756f93f2 100644
--- a/src/constants/index.ts
+++ b/src/constants/index.ts
@@ -4,6 +4,4 @@ export const HISTORY_PANEL_ID = "headlessui-popover-panel:history-panel";
export const CONTEXT_MENU_PANEL_ID = "headlessui-popover-panel:context-menu";
-export const ASK_AI_CLIENT_ID = "ask-ai-client";
-
export const COPY_BUTTON_ID = "copy-button";
diff --git a/src/hooks/useKeyboardNavigation.ts b/src/hooks/useKeyboardNavigation.ts
index 447f4d87..ff01bb08 100644
--- a/src/hooks/useKeyboardNavigation.ts
+++ b/src/hooks/useKeyboardNavigation.ts
@@ -2,8 +2,9 @@ import { useCallback, useEffect } from 'react';
import { useShortcutsStore } from "@/stores/shortcutsStore";
import { isMetaOrCtrlKey, metaOrCtrlKey } from '@/utils/keyboardUtils';
-import { copyToClipboard, OpenURLWithBrowser } from "@/utils/index";
+import { copyToClipboard } from "@/utils/index";
import type { QueryHits, SearchDocument } from "@/types/search";
+import platformAdapter from "@/utils/platformAdapter";
interface UseKeyboardNavigationProps {
suggests: QueryHits[];
@@ -67,12 +68,11 @@ export function useKeyboardNavigation({
if (
e.key === "Enter" &&
!e.shiftKey &&
- selectedIndex !== null &&
- isMetaOrCtrlKey(e)
+ selectedIndex !== null
) {
const item = globalItemIndexMap[selectedIndex];
- if (item?.url) {
- OpenURLWithBrowser(item?.url);
+ if (item?.on_opened) {
+ platformAdapter.invokeBackend("open", { onOpened: item.on_opened });
} else {
copyToClipboard(item?.payload?.result?.value);
}
@@ -85,8 +85,8 @@ export function useKeyboardNavigation({
const item = globalItemIndexMap[index];
- if (item?.url) {
- OpenURLWithBrowser(item?.url);
+ if (item?.on_opened) {
+ platformAdapter.invokeBackend("open", { onOpened: item.on_opened });
}
}
},
diff --git a/src/hooks/useScript.ts b/src/hooks/useScript.ts
index e46a8165..37be5165 100644
--- a/src/hooks/useScript.ts
+++ b/src/hooks/useScript.ts
@@ -1,4 +1,4 @@
-import { useEffect, useState } from 'react';
+import { useEffect, useState } from "react";
const useScript = (src: string, onError?: () => void) => {
useEffect(() => {
@@ -6,7 +6,7 @@ const useScript = (src: string, onError?: () => void) => {
return; // Prevent duplicate script loading
}
- const script = document.createElement('script');
+ const script = document.createElement("script");
script.src = src;
script.async = true;
@@ -25,24 +25,27 @@ const useScript = (src: string, onError?: () => void) => {
export default useScript;
-
export const useIconfontScript = () => {
const appStore = JSON.parse(localStorage.getItem("app-store") || "{}");
const [useLocalFallback, setUseLocalFallback] = useState(false);
- let baseURL = appStore.state?.endpoint_http
+ let baseURL = appStore.state?.endpoint_http;
if (!baseURL || baseURL === "undefined") {
baseURL = "";
}
if (useLocalFallback || baseURL === "") {
- useScript('/assets/fonts/icons/iconfont.js');
+ useScript("/assets/fonts/icons/iconfont.js");
return;
}
useScript(`${baseURL}/assets/fonts/icons/iconfont.js`, () => {
- console.log("Remote iconfont loading failed, falling back to local resource");
+ console.log(
+ "Remote iconfont loading failed, falling back to local resource"
+ );
setUseLocalFallback(true);
});
+
+ useScript("/assets/fonts/icons/extension.js");
};
diff --git a/src/hooks/useSearch.ts b/src/hooks/useSearch.ts
index bfb76c3e..7c349beb 100644
--- a/src/hooks/useSearch.ts
+++ b/src/hooks/useSearch.ts
@@ -1,11 +1,18 @@
-import { useState, useCallback, useMemo } from 'react';
-import { debounce } from 'lodash-es';
+import { useState, useCallback, useMemo, useRef } from "react";
+import { debounce } from "lodash-es";
-import type { QueryHits, MultiSourceQueryResponse, FailedRequest, SearchDocument } from '@/types/search';
+import type {
+ QueryHits,
+ MultiSourceQueryResponse,
+ FailedRequest,
+ SearchDocument,
+} from "@/types/search";
import platformAdapter from "@/utils/platformAdapter";
import { Get } from "@/api/axiosRequest";
import { useConnectStore } from "@/stores/connectStore";
import { useAppStore } from "@/stores/appStore";
+import { useSearchStore } from "@/stores/searchStore";
+import { useExtensionsStore } from "@/stores/extensionsStore";
interface SearchState {
isError: FailedRequest[];
@@ -21,6 +28,25 @@ interface SearchDataBySource {
export function useSearch() {
const isTauri = useAppStore((state) => state.isTauri);
+ const enabledAiOverview = useSearchStore((state) => {
+ return state.enabledAiOverview;
+ });
+ const aiOverviewServer = useExtensionsStore((state) => {
+ return state.aiOverviewServer;
+ });
+ const aiOverviewAssistant = useExtensionsStore((state) => {
+ return state.aiOverviewAssistant;
+ });
+ const timerRef = useRef(null);
+ const disabledExtensions = useExtensionsStore((state) => {
+ return state.disabledExtensions;
+ });
+ const aiOverviewCharLen = useExtensionsStore((state) => {
+ return state.aiOverviewCharLen;
+ });
+ const aiOverviewDelay = useExtensionsStore((state) => {
+ return state.aiOverviewDelay;
+ });
const { querySourceTimeout } = useConnectStore();
@@ -29,22 +55,28 @@ export function useSearch() {
suggests: [],
searchData: {},
isSearchComplete: false,
- globalItemIndexMap: {}
+ globalItemIndexMap: {},
});
- const handleSearchResponse = (response: MultiSourceQueryResponse) => {
+ const handleSearchResponse = (
+ response: MultiSourceQueryResponse,
+ searchInput: string
+ ) => {
const data = response?.hits || [];
- const searchData = data.reduce((acc: SearchDataBySource, item: QueryHits) => {
- const name = item?.document?.source?.name;
- if (name) {
- if (!acc[name]) {
- acc[name] = [];
+ const searchData = data.reduce(
+ (acc: SearchDataBySource, item: QueryHits) => {
+ const name = item?.document?.source?.name;
+ if (name) {
+ if (!acc[name]) {
+ acc[name] = [];
+ }
+ acc[name].push(item);
}
- acc[name].push(item);
- }
- return acc;
- }, {});
+ return acc;
+ },
+ {}
+ );
// Update indices and map
//console.log("_search response", data, searchData);
@@ -54,10 +86,65 @@ export function useSearch() {
searchData[sourceName].map((item: QueryHits) => {
item.document.querySource = item?.source;
const index = globalIndex++;
- item.document.index = index
+ item.document.index = index;
globalItemIndexMap[index] = item.document;
return item;
- })
+ });
+ }
+
+ const filteredData = data.filter((item: any) => {
+ return (
+ item?.document?.type !== "AI Assistant" &&
+ item?.document?.category !== "Calculator" &&
+ item?.document?.category !== "Application"
+ );
+ });
+
+ console.log("aiOverviewCharLen", aiOverviewCharLen);
+ console.log("aiOverviewDelay", aiOverviewDelay);
+
+ if (
+ searchInput.length >= aiOverviewCharLen &&
+ isTauri &&
+ enabledAiOverview &&
+ aiOverviewServer &&
+ aiOverviewAssistant &&
+ filteredData.length > 5 &&
+ !disabledExtensions.includes("AIOverview")
+ ) {
+ timerRef.current = setTimeout(() => {
+ const id = "AI Overview";
+
+ const payload = {
+ source: {
+ id,
+ type: id,
+ },
+ document: {
+ index: 1000000,
+ id,
+ category: id,
+ payload: {
+ message: JSON.stringify({
+ query: searchInput,
+ result: filteredData,
+ }),
+ },
+ source: {
+ icon: "font_a-AIOverview",
+ },
+ },
+ };
+
+ setSearchState((prev) => ({
+ ...prev,
+ suggests: prev.suggests.concat(payload as any),
+ searchData: {
+ [id]: [payload as any],
+ ...prev.searchData,
+ },
+ }));
+ }, aiOverviewDelay * 1000);
}
setSearchState({
@@ -69,47 +156,65 @@ export function useSearch() {
});
};
- const performSearch = useCallback(async (searchInput: string) => {
- if (!searchInput) {
- setSearchState(prev => ({ ...prev, suggests: [] }));
- return;
- }
-
- let response: MultiSourceQueryResponse;
- if (isTauri) {
- response = await platformAdapter.commands("query_coco_fusion", {
- from: 0,
- size: 10,
- queryStrings: { query: searchInput },
- queryTimeout: querySourceTimeout,
- });
- } else {
- const [error, res]: any = await Get(`/query/_search?query=${searchInput}`);
- if (error) {
- console.error("_search", error);
- response = { failed: [], hits: [], total_hits: 0 };
- } else {
- const hits =
- res?.hits?.hits?.map((hit: any) => ({
- document: {
- ...hit._source,
- },
- score: hit._score || 0,
- source: hit._source.source || null,
- })) || [];
- const total = res?.hits?.total?.value || 0;
- response = {
- failed: [],
- hits: hits,
- total_hits: total,
- };
+ const performSearch = useCallback(
+ async (searchInput: string) => {
+ if (!searchInput) {
+ setSearchState((prev) => ({ ...prev, suggests: [] }));
+ return;
}
- }
- console.log("_suggest", searchInput, response);
+ let response: MultiSourceQueryResponse;
+ if (isTauri) {
+ response = await platformAdapter.commands("query_coco_fusion", {
+ from: 0,
+ size: 10,
+ queryStrings: { query: searchInput },
+ queryTimeout: querySourceTimeout,
+ });
+ } else {
+ const [error, res]: any = await Get(
+ `/query/_search?query=${searchInput}`
+ );
+ if (error) {
+ console.error("_search", error);
+ response = { failed: [], hits: [], total_hits: 0 };
+ } else {
+ const hits =
+ res?.hits?.hits?.map((hit: any) => ({
+ document: {
+ ...hit._source,
+ },
+ score: hit._score || 0,
+ source: hit._source.source || null,
+ })) || [];
+ const total = res?.hits?.total?.value || 0;
+ response = {
+ failed: [],
+ hits: hits,
+ total_hits: total,
+ };
+ }
+ }
- handleSearchResponse(response);
- }, [querySourceTimeout, isTauri]);
+ console.log("_suggest", searchInput, response);
+
+ if (timerRef.current) {
+ clearTimeout(timerRef.current);
+ }
+
+ handleSearchResponse(response, searchInput);
+ },
+ [
+ querySourceTimeout,
+ isTauri,
+ enabledAiOverview,
+ aiOverviewServer,
+ aiOverviewAssistant,
+ disabledExtensions,
+ aiOverviewCharLen,
+ aiOverviewDelay,
+ ]
+ );
const debouncedSearch = useMemo(
() => debounce(performSearch, 300),
@@ -118,6 +223,6 @@ export function useSearch() {
return {
...searchState,
- performSearch: debouncedSearch
+ performSearch: debouncedSearch,
};
-}
\ No newline at end of file
+}
diff --git a/src/hooks/useStreamChat.ts b/src/hooks/useStreamChat.ts
new file mode 100644
index 00000000..4a031adb
--- /dev/null
+++ b/src/hooks/useStreamChat.ts
@@ -0,0 +1,135 @@
+import { useAppStore } from "@/stores/appStore";
+import { EventPayloads } from "@/types/platform";
+import platformAdapter from "@/utils/platformAdapter";
+import { useAsyncEffect, useMount, useReactive, useUnmount } from "ahooks";
+import { noop } from "lodash-es";
+import { useRef, useState } from "react";
+import useMessageChunkData from "./useMessageChunkData";
+
+interface Options {
+ message: string;
+ clientId: keyof EventPayloads;
+ server?: any;
+ assistant?: any;
+ setVisible: (visible: boolean) => void;
+}
+
+interface State {
+ sessionId?: string;
+ isTyping?: boolean;
+}
+
+export const useStreamChat = (options: Options) => {
+ const { message, clientId, server, assistant, setVisible } = options;
+
+ const unlistenRef = useRef<() => void>(noop);
+ const addError = useAppStore((state) => state.addError);
+ const state = useReactive({
+ isTyping: true,
+ });
+ const [loadingStep, setLoadingStep] = useState>({
+ query_intent: false,
+ tools: false,
+ fetch_source: false,
+ pick_source: false,
+ deep_read: false,
+ think: false,
+ response: false,
+ });
+
+ const {
+ data: chunkData,
+ handlers,
+ clearAllChunkData,
+ } = useMessageChunkData();
+
+ useMount(async () => {
+ try {
+ unlistenRef.current = await platformAdapter.listenEvent(
+ clientId,
+ ({ payload }) => {
+ console.log(clientId, JSON.parse(payload));
+
+ const chunkData = JSON.parse(payload);
+
+ if (chunkData?._id) {
+ state.sessionId = chunkData._id;
+
+ return;
+ }
+
+ if (state.sessionId !== chunkData.session_id) {
+ return;
+ }
+
+ // If the chunk data does not contain a message_chunk, we ignore it
+ if (chunkData.message_chunk) {
+ setVisible(true);
+ }
+
+ state.isTyping = true;
+
+ setLoadingStep(() => ({
+ query_intent: false,
+ tools: false,
+ fetch_source: false,
+ pick_source: false,
+ deep_read: false,
+ think: false,
+ response: false,
+ [chunkData.chunk_type]: true,
+ }));
+
+ if (chunkData.chunk_type === "query_intent") {
+ handlers.deal_query_intent(chunkData);
+ } else if (chunkData.chunk_type === "tools") {
+ handlers.deal_tools(chunkData);
+ } else if (chunkData.chunk_type === "fetch_source") {
+ handlers.deal_fetch_source(chunkData);
+ } else if (chunkData.chunk_type === "pick_source") {
+ handlers.deal_pick_source(chunkData);
+ } else if (chunkData.chunk_type === "deep_read") {
+ handlers.deal_deep_read(chunkData);
+ } else if (chunkData.chunk_type === "think") {
+ handlers.deal_think(chunkData);
+ } else if (chunkData.chunk_type === "response") {
+ handlers.deal_response(chunkData);
+ } else if (chunkData.chunk_type === "reply_end") {
+ console.log("AI finished output");
+ state.isTyping = false;
+ return;
+ }
+ }
+ );
+ } catch (error) {
+ addError(String(error));
+ }
+ });
+
+ useAsyncEffect(async () => {
+ if (!message || !server || !assistant) return;
+
+ clearAllChunkData();
+
+ try {
+ await platformAdapter.invokeBackend("ask_ai", {
+ message,
+ clientId,
+ serverId: server.id,
+ assistantId: assistant.id,
+ });
+ } catch (error) {
+ addError(String(error));
+ }
+ }, [message, server, assistant]);
+
+ useUnmount(() => {
+ unlistenRef.current();
+ });
+
+ return {
+ ...state,
+ chunkData,
+ loadingStep,
+ };
+};
diff --git a/src/hooks/useSyncStore.ts b/src/hooks/useSyncStore.ts
index 50701be8..a9aca2b0 100644
--- a/src/hooks/useSyncStore.ts
+++ b/src/hooks/useSyncStore.ts
@@ -93,6 +93,21 @@ export const useSyncStore = () => {
const setQuickAiAccessAssistant = useExtensionsStore((state) => {
return state.setQuickAiAccessAssistant;
});
+ const setAiOverviewServer = useExtensionsStore((state) => {
+ return state.setAiOverviewServer;
+ });
+ const setAiOverviewAssistant = useExtensionsStore((state) => {
+ return state.setAiOverviewAssistant;
+ });
+ const setDisabledExtensions = useExtensionsStore((state) => {
+ return state.setDisabledExtensions;
+ });
+ const setAiOverviewCharLen = useExtensionsStore((state) => {
+ return state.setAiOverviewCharLen;
+ });
+ const setAiOverviewDelay = useExtensionsStore((state) => {
+ return state.setAiOverviewDelay;
+ });
useEffect(() => {
if (!resetFixedWindow) {
@@ -174,10 +189,23 @@ export const useSyncStore = () => {
}),
platformAdapter.listenEvent("change-extensions-store", ({ payload }) => {
- const { quickAiAccessServer, quickAiAccessAssistant } = payload;
+ const {
+ quickAiAccessServer,
+ quickAiAccessAssistant,
+ aiOverviewServer,
+ aiOverviewAssistant,
+ disabledExtensions,
+ aiOverviewCharLen,
+ aiOverviewDelay,
+ } = payload;
setQuickAiAccessServer(quickAiAccessServer);
setQuickAiAccessAssistant(quickAiAccessAssistant);
+ setAiOverviewServer(aiOverviewServer);
+ setAiOverviewAssistant(aiOverviewAssistant);
+ setDisabledExtensions(disabledExtensions);
+ setAiOverviewCharLen(aiOverviewCharLen);
+ setAiOverviewDelay(aiOverviewDelay);
}),
]);
diff --git a/src/main.css b/src/main.css
index 36e1b630..05b9c668 100644
--- a/src/main.css
+++ b/src/main.css
@@ -241,7 +241,7 @@
-ms-user-select: none;
user-select: none;
}
-
+
.user-select-text {
-webkit-touch-callout: text;
-webkit-user-select: text;
@@ -250,4 +250,14 @@
-ms-user-select: text;
user-select: text;
}
+
+ .hide-scrollbar {
+ overflow: auto;
+ scrollbar-width: none; /* Firefox */
+ -ms-overflow-style: none; /* IE/Edge */
+ }
+
+ .hide-scrollbar::-webkit-scrollbar {
+ display: none; /* Chrome/Safari */
+ }
}
diff --git a/src/routes/layout.tsx b/src/routes/layout.tsx
index 655ba9ec..99823515 100644
--- a/src/routes/layout.tsx
+++ b/src/routes/layout.tsx
@@ -14,6 +14,8 @@ import { AppTheme } from "@/types/index";
import ErrorNotification from "@/components/Common/ErrorNotification";
import { useModifierKeyPress } from "@/hooks/useModifierKeyPress";
import { useIconfontScript } from "@/hooks/useScript";
+import { Extension } from "@/components/Settings/Extensions";
+import { useExtensionsStore } from "@/stores/extensionsStore";
export default function Layout() {
const location = useLocation();
@@ -119,6 +121,20 @@ export default function Layout() {
useIconfontScript();
+ const setDisabledExtensions = useExtensionsStore((state) => {
+ return state.setDisabledExtensions;
+ });
+
+ useMount(async () => {
+ const result = await platformAdapter.invokeBackend<[boolean, Extension[]]>(
+ "list_extensions"
+ );
+
+ const disabledExtensions = result[1].filter((item) => !item.enabled);
+
+ setDisabledExtensions(disabledExtensions.map((item) => item.id));
+ });
+
return (
<>
diff --git a/src/stores/extensionsStore.ts b/src/stores/extensionsStore.ts
index 1ce5247c..e80c30c3 100644
--- a/src/stores/extensionsStore.ts
+++ b/src/stores/extensionsStore.ts
@@ -1,3 +1,4 @@
+import { ExtensionId } from "@/components/Settings/Extensions";
import { create } from "zustand";
import { persist, subscribeWithSelector } from "zustand/middleware";
@@ -6,6 +7,16 @@ export type IExtensionsStore = {
setQuickAiAccessServer: (quickAiAccessServer?: any) => void;
quickAiAccessAssistant?: any;
setQuickAiAccessAssistant: (quickAiAccessAssistant?: any) => void;
+ aiOverviewServer?: any;
+ setAiOverviewServer: (aiOverviewServer?: any) => void;
+ aiOverviewAssistant?: any;
+ setAiOverviewAssistant: (aiOverviewAssistant?: any) => void;
+ disabledExtensions: ExtensionId[];
+ setDisabledExtensions: (disabledExtensions?: string[]) => void;
+ aiOverviewCharLen: number;
+ setAiOverviewCharLen: (aiOverviewCharLen: number) => void;
+ aiOverviewDelay: number;
+ setAiOverviewDelay: (aiOverviewDelay: number) => void;
};
export const useExtensionsStore = create()(
@@ -18,12 +29,34 @@ export const useExtensionsStore = create()(
setQuickAiAccessAssistant(quickAiAccessAssistant) {
return set({ quickAiAccessAssistant });
},
+ setAiOverviewServer(aiOverviewServer) {
+ return set({ aiOverviewServer });
+ },
+ setAiOverviewAssistant(aiOverviewAssistant) {
+ return set({ aiOverviewAssistant });
+ },
+ disabledExtensions: [],
+ setDisabledExtensions(disabledExtensions) {
+ return set({ disabledExtensions });
+ },
+ aiOverviewCharLen: 10,
+ setAiOverviewCharLen(aiOverviewCharLen) {
+ return set({ aiOverviewCharLen });
+ },
+ aiOverviewDelay: 2,
+ setAiOverviewDelay(aiOverviewDelay) {
+ return set({ aiOverviewDelay });
+ },
}),
{
name: "extensions-store",
partialize: (state) => ({
quickAiAccessServer: state.quickAiAccessServer,
quickAiAccessAssistant: state.quickAiAccessAssistant,
+ aiOverviewServer: state.aiOverviewServer,
+ aiOverviewAssistant: state.aiOverviewAssistant,
+ aiOverviewCharLen: state.aiOverviewCharLen,
+ aiOverviewDelay: state.aiOverviewDelay,
}),
}
)
diff --git a/src/stores/searchStore.ts b/src/stores/searchStore.ts
index 4eeff01c..2a73387c 100644
--- a/src/stores/searchStore.ts
+++ b/src/stores/searchStore.ts
@@ -24,6 +24,10 @@ export type ISearchStore = {
setSelectedAssistant: (selectedAssistant?: any) => void;
askAiServerId?: string;
setAskAiServerId: (askAiServerId?: string) => void;
+ enabledAiOverview: boolean;
+ setEnabledAiOverview: (enabledAiOverview: boolean) => void;
+ askAiAssistantId?: string;
+ setAskAiAssistantId: (askAiAssistantId?: string) => void;
};
export const useSearchStore = create()(
@@ -59,6 +63,13 @@ export const useSearchStore = create()(
setAskAiServerId: (askAiServerId) => {
return set({ askAiServerId });
},
+ enabledAiOverview: false,
+ setEnabledAiOverview: (enabledAiOverview) => {
+ return set({ enabledAiOverview });
+ },
+ setAskAiAssistantId: (askAiAssistantId) => {
+ return set({ askAiAssistantId });
+ },
}),
{
name: "search-store",
diff --git a/src/types/platform.ts b/src/types/platform.ts
index d1eaa21b..1950da23 100644
--- a/src/types/platform.ts
+++ b/src/types/platform.ts
@@ -1,4 +1,3 @@
-import { ASK_AI_CLIENT_ID } from "@/constants";
import { IAppearanceStore } from "@/stores/appearanceStore";
import { IConnectStore } from "@/stores/connectStore";
import { IExtensionsStore } from "@/stores/extensionsStore";
@@ -38,9 +37,10 @@ export interface EventPayloads {
"change-shortcuts-store": IShortcutsStore;
"change-connect-store": IConnectStore;
"change-appearance-store": IAppearanceStore;
- [ASK_AI_CLIENT_ID]: any;
"toggle-to-chat-mode": void;
"change-extensions-store": IExtensionsStore;
+ "quick-ai-access-client-id": any;
+ "ai-overview-client-id": any;
}
// Window operation interface
diff --git a/src/types/search.ts b/src/types/search.ts
index 7beff078..01a2f6f4 100644
--- a/src/types/search.ts
+++ b/src/types/search.ts
@@ -6,7 +6,7 @@ export interface QueryHits {
export interface QuerySource {
type: string; // coco-server/local/ etc.
- id: string; // coco server's id
+ id: string; // coco server's id
name: string; // coco server's name, local computer name, etc.
}
@@ -37,6 +37,7 @@ export interface SearchDocument {
querySource?: QuerySource;
index?: number; // Index in the current search result
globalIndex?: number;
+ on_opened?: any;
}
export interface RichLabel {
@@ -74,4 +75,4 @@ export interface MultiSourceQueryResponse {
failed: FailedRequest[];
hits: QueryHits[];
total_hits: number;
-}
\ No newline at end of file
+}
diff --git a/src/utils/index.ts b/src/utils/index.ts
index 2af5709e..eb8c20a2 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -113,3 +113,7 @@ export const closeHistoryPanel = () => {
button.click();
}
};
+
+// export const sortByFirstLetter = (list: T[], key: keyof T) => {
+// return list.sort((a, b) => {});
+// };
diff --git a/src/utils/platform.ts b/src/utils/platform.ts
index 7576dafc..1672d4df 100644
--- a/src/utils/platform.ts
+++ b/src/utils/platform.ts
@@ -13,6 +13,18 @@ console.log("isMac", isMac);
console.log("isWin", isWin);
console.log("isLinux", isLinux);
+export function platform() {
+ if (isWin) {
+ return "windows";
+ } else if (isMac) {
+ return "macos";
+ } else if (isLinux) {
+ return "linux";
+ }
+
+ return void 0;
+}
+
export function family() {
if (isWeb) {
const ua = navigator.userAgent.toLowerCase();
diff --git a/src/utils/tauriAdapter.ts b/src/utils/tauriAdapter.ts
index 4138672c..936a63a5 100644
--- a/src/utils/tauriAdapter.ts
+++ b/src/utils/tauriAdapter.ts
@@ -207,8 +207,9 @@ export const createTauriAdapter = (): TauriPlatformAdapter => {
},
async openExternal(url) {
- const { invoke } = await import("@tauri-apps/api/core");
- return invoke("open", { path: url });
+ const { open } = await import("@tauri-apps/plugin-shell");
+
+ open(url);
},
isWindows10,
diff --git a/tailwind.config.js b/tailwind.config.js
index 8333624b..527bdcf7 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -56,7 +56,7 @@ export default {
2000: "2000",
},
screens: {
- 'mobile': {'max': '679px'},
+ mobile: { max: "679px" },
},
},
},
diff --git a/tsup.config.ts b/tsup.config.ts
index 4e227911..800fb8aa 100644
--- a/tsup.config.ts
+++ b/tsup.config.ts
@@ -67,7 +67,7 @@ export default defineConfig({
const packageJson = {
name: "@infinilabs/search-chat",
- version: "1.2.5",
+ version: "1.2.8",
main: "index.js",
module: "index.js",
type: "module",