mirror of
https://github.com/infinilabs/coco-app.git
synced 2025-12-16 19:47:43 +01:00
feat: add connect services (#111)
* feat: add connect services * chore: adjust auth and userInfo * chore: add responsiveness to auth and userInfo * chore: add dark css * chore: handle /, to join the baseURL and url. * chore: use http:// or https:// rather http * chore: handle /, to join the baseURL and url. * chore: active current service * chore: connect * chore: data source & connect data * feat: add handleKeyDown open settings * chore: settings name
This commit is contained in:
@@ -27,7 +27,7 @@
|
|||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"i18next": "^23.16.2",
|
"i18next": "^23.16.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"lucide-react": "^0.453.0",
|
"lucide-react": "^0.461.0",
|
||||||
"mermaid": "^11.4.0",
|
"mermaid": "^11.4.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
@@ -55,6 +55,7 @@
|
|||||||
"@types/react-katex": "^3.0.4",
|
"@types/react-katex": "^3.0.4",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
|
"immer": "^10.1.1",
|
||||||
"postcss": "^8.4.47",
|
"postcss": "^8.4.47",
|
||||||
"tailwindcss": "^3.4.14",
|
"tailwindcss": "^3.4.14",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
|
|||||||
23
pnpm-lock.yaml
generated
23
pnpm-lock.yaml
generated
@@ -60,8 +60,8 @@ importers:
|
|||||||
specifier: ^4.17.21
|
specifier: ^4.17.21
|
||||||
version: 4.17.21
|
version: 4.17.21
|
||||||
lucide-react:
|
lucide-react:
|
||||||
specifier: ^0.453.0
|
specifier: ^0.461.0
|
||||||
version: 0.453.0(react@18.3.1)
|
version: 0.461.0(react@18.3.1)
|
||||||
mermaid:
|
mermaid:
|
||||||
specifier: ^11.4.0
|
specifier: ^11.4.0
|
||||||
version: 11.4.0
|
version: 11.4.0
|
||||||
@@ -106,7 +106,7 @@ importers:
|
|||||||
version: 11.0.3
|
version: 11.0.3
|
||||||
zustand:
|
zustand:
|
||||||
specifier: ^5.0.0
|
specifier: ^5.0.0
|
||||||
version: 5.0.0(@types/react@18.3.11)(react@18.3.1)
|
version: 5.0.0(@types/react@18.3.11)(immer@10.1.1)(react@18.3.1)
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@tauri-apps/cli':
|
'@tauri-apps/cli':
|
||||||
specifier: '>=2.0.0'
|
specifier: '>=2.0.0'
|
||||||
@@ -138,6 +138,9 @@ importers:
|
|||||||
autoprefixer:
|
autoprefixer:
|
||||||
specifier: ^10.4.20
|
specifier: ^10.4.20
|
||||||
version: 10.4.20(postcss@8.4.47)
|
version: 10.4.20(postcss@8.4.47)
|
||||||
|
immer:
|
||||||
|
specifier: ^10.1.1
|
||||||
|
version: 10.1.1
|
||||||
postcss:
|
postcss:
|
||||||
specifier: ^8.4.47
|
specifier: ^8.4.47
|
||||||
version: 8.4.47
|
version: 8.4.47
|
||||||
@@ -1440,6 +1443,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
immer@10.1.1:
|
||||||
|
resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==}
|
||||||
|
|
||||||
inline-style-parser@0.2.4:
|
inline-style-parser@0.2.4:
|
||||||
resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==}
|
resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==}
|
||||||
|
|
||||||
@@ -1577,8 +1583,8 @@ packages:
|
|||||||
lru-cache@5.1.1:
|
lru-cache@5.1.1:
|
||||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
||||||
|
|
||||||
lucide-react@0.453.0:
|
lucide-react@0.461.0:
|
||||||
resolution: {integrity: sha512-kL+RGZCcJi9BvJtzg2kshO192Ddy9hv3ij+cPrVPWSRzgCWCVazoQJxOjAwgK53NomL07HB7GPHW120FimjNhQ==}
|
resolution: {integrity: sha512-Scpw3D/dV1bgVRC5Kh774RCm99z0iZpPv75M6kg7QL1lLvkQ1rmI1Sjjic1aGp1ULBwd7FokV6ry0g+d6pMB+w==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc
|
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc
|
||||||
|
|
||||||
@@ -3616,6 +3622,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
safer-buffer: 2.1.2
|
safer-buffer: 2.1.2
|
||||||
|
|
||||||
|
immer@10.1.1: {}
|
||||||
|
|
||||||
inline-style-parser@0.2.4: {}
|
inline-style-parser@0.2.4: {}
|
||||||
|
|
||||||
internmap@1.0.1: {}
|
internmap@1.0.1: {}
|
||||||
@@ -3726,7 +3734,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
yallist: 3.1.1
|
yallist: 3.1.1
|
||||||
|
|
||||||
lucide-react@0.453.0(react@18.3.1):
|
lucide-react@0.461.0(react@18.3.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
|
|
||||||
@@ -4708,9 +4716,10 @@ snapshots:
|
|||||||
|
|
||||||
yaml@2.6.0: {}
|
yaml@2.6.0: {}
|
||||||
|
|
||||||
zustand@5.0.0(@types/react@18.3.11)(react@18.3.1):
|
zustand@5.0.0(@types/react@18.3.11)(immer@10.1.1)(react@18.3.1):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 18.3.11
|
'@types/react': 18.3.11
|
||||||
|
immer: 10.1.1
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
|
|
||||||
zwitch@2.0.4: {}
|
zwitch@2.0.4: {}
|
||||||
|
|||||||
@@ -47,14 +47,20 @@ export const tauriFetch = async <T = any>({
|
|||||||
const auth = authStore?.state?.auth
|
const auth = authStore?.state?.auth
|
||||||
console.log("auth", auth)
|
console.log("auth", auth)
|
||||||
|
|
||||||
|
if (baseURL.endsWith("/")) {
|
||||||
|
baseURL = baseURL.slice(0, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
||||||
|
// If not, prepend the defaultPrefix
|
||||||
url = baseURL + url;
|
url = baseURL + url;
|
||||||
|
}
|
||||||
|
|
||||||
if (method !== "GET") {
|
if (method !== "GET") {
|
||||||
headers["Content-Type"] = "application/json";
|
headers["Content-Type"] = "application/json";
|
||||||
}
|
}
|
||||||
|
|
||||||
headers["X-API-TOKEN"] = headers["X-API-TOKEN"] || auth?.token || "";
|
headers["X-API-TOKEN"] = headers["X-API-TOKEN"] || (auth && auth[endpoint_http]?.token) || "";
|
||||||
|
|
||||||
// debug API
|
// debug API
|
||||||
const requestInfo = {
|
const requestInfo = {
|
||||||
|
|||||||
BIN
src/assets/images/coco-cloud-banner.jpeg
Normal file
BIN
src/assets/images/coco-cloud-banner.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
@@ -16,6 +16,7 @@ import source_default_img from "@/assets/images/source_default.png";
|
|||||||
import source_default_dark_img from "@/assets/images/source_default_dark.png";
|
import source_default_dark_img from "@/assets/images/source_default_dark.png";
|
||||||
import file_efault_img from "@/assets/images/file_efault.png";
|
import file_efault_img from "@/assets/images/file_efault.png";
|
||||||
import { useTheme } from "@/contexts/ThemeContext";
|
import { useTheme } from "@/contexts/ThemeContext";
|
||||||
|
import { useConnectStore } from "@/stores/connectStore";
|
||||||
|
|
||||||
type ISearchData = Record<string, any[]>;
|
type ISearchData = Record<string, any[]>;
|
||||||
|
|
||||||
@@ -46,8 +47,9 @@ function DropdownList({
|
|||||||
|
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
|
||||||
const connector_data = useAppStore((state) => state.connector_data);
|
const connector_data = useConnectStore((state) => state.connector_data);
|
||||||
const datasourceData = useAppStore((state) => state.datasourceData);
|
const datasourceData = useConnectStore((state) => state.datasourceData);
|
||||||
|
|
||||||
const endpoint_http = useAppStore((state) => state.endpoint_http);
|
const endpoint_http = useAppStore((state) => state.endpoint_http);
|
||||||
const setSourceData = useSearchStore((state) => state.setSourceData);
|
const setSourceData = useSearchStore((state) => state.setSourceData);
|
||||||
|
|
||||||
@@ -163,13 +165,13 @@ function DropdownList({
|
|||||||
function findConnectorIcon(item: any) {
|
function findConnectorIcon(item: any) {
|
||||||
const id = item?._source?.source?.id || "";
|
const id = item?._source?.source?.id || "";
|
||||||
|
|
||||||
const result_source = datasourceData.find(
|
const result_source = datasourceData[endpoint_http]?.find(
|
||||||
(data: any) => data._source.id === id
|
(data: any) => data._source.id === id
|
||||||
);
|
);
|
||||||
|
|
||||||
const connector_id = result_source?._source?.connector?.id;
|
const connector_id = result_source?._source?.connector?.id;
|
||||||
|
|
||||||
const result_connector = connector_data.find(
|
const result_connector = connector_data[endpoint_http]?.find(
|
||||||
(data: any) => data._source.id === connector_id
|
(data: any) => data._source.id === connector_id
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -184,7 +186,7 @@ function DropdownList({
|
|||||||
return theme === "dark" ? source_default_dark_img : source_default_img;
|
return theme === "dark" ? source_default_dark_img : source_default_img;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (icons?.includes("http")) {
|
if (icons?.startsWith("http://") || icons?.startsWith("https://")) {
|
||||||
return icons;
|
return icons;
|
||||||
} else {
|
} else {
|
||||||
return endpoint_http + icons;
|
return endpoint_http + icons;
|
||||||
@@ -201,7 +203,7 @@ function DropdownList({
|
|||||||
return file_efault_img;
|
return file_efault_img;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedIcon?.includes("http")) {
|
if (selectedIcon?.startsWith("http://") || selectedIcon?.startsWith("https://")) {
|
||||||
return selectedIcon;
|
return selectedIcon;
|
||||||
} else {
|
} else {
|
||||||
return endpoint_http + selectedIcon;
|
return endpoint_http + selectedIcon;
|
||||||
@@ -218,7 +220,7 @@ function DropdownList({
|
|||||||
return theme === "dark" ? source_default_dark_img : source_default_img;
|
return theme === "dark" ? source_default_dark_img : source_default_img;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedIcon?.includes("http")) {
|
if (selectedIcon?.startsWith("http://") || selectedIcon?.startsWith("https://")) {
|
||||||
return selectedIcon;
|
return selectedIcon;
|
||||||
} else {
|
} else {
|
||||||
return endpoint_http + selectedIcon;
|
return endpoint_http + selectedIcon;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import source_default_dark_img from "@/assets/images/source_default_dark.png";
|
|||||||
import { useSearchStore } from "@/stores/searchStore";
|
import { useSearchStore } from "@/stores/searchStore";
|
||||||
import { useAppStore } from "@/stores/appStore";
|
import { useAppStore } from "@/stores/appStore";
|
||||||
import { useTheme } from "@/contexts/ThemeContext";
|
import { useTheme } from "@/contexts/ThemeContext";
|
||||||
|
import { useConnectStore } from "@/stores/connectStore";
|
||||||
|
|
||||||
interface FooterProps {
|
interface FooterProps {
|
||||||
isChat: boolean;
|
isChat: boolean;
|
||||||
@@ -19,8 +20,10 @@ interface FooterProps {
|
|||||||
|
|
||||||
export default function Footer({ name }: FooterProps) {
|
export default function Footer({ name }: FooterProps) {
|
||||||
const sourceData = useSearchStore((state) => state.sourceData);
|
const sourceData = useSearchStore((state) => state.sourceData);
|
||||||
const connector_data = useAppStore((state) => state.connector_data);
|
|
||||||
const datasourceData = useAppStore((state) => state.datasourceData);
|
const connector_data = useConnectStore((state) => state.connector_data);
|
||||||
|
const datasourceData = useConnectStore((state) => state.datasourceData);
|
||||||
|
|
||||||
const endpoint_http = useAppStore((state) => state.endpoint_http);
|
const endpoint_http = useAppStore((state) => state.endpoint_http);
|
||||||
|
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
@@ -28,13 +31,13 @@ export default function Footer({ name }: FooterProps) {
|
|||||||
function findConnectorIcon(item: any) {
|
function findConnectorIcon(item: any) {
|
||||||
const id = item?._source?.source?.id || "";
|
const id = item?._source?.source?.id || "";
|
||||||
|
|
||||||
const result_source = datasourceData.find(
|
const result_source = datasourceData[endpoint_http]?.find(
|
||||||
(data: any) => data._source.id === id
|
(data: any) => data._source.id === id
|
||||||
);
|
);
|
||||||
|
|
||||||
const connector_id = result_source?._source?.connector?.id;
|
const connector_id = result_source?._source?.connector?.id;
|
||||||
|
|
||||||
const result_connector = connector_data.find(
|
const result_connector = connector_data[endpoint_http]?.find(
|
||||||
(data: any) => data._source.id === connector_id
|
(data: any) => data._source.id === connector_id
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -49,7 +52,7 @@ export default function Footer({ name }: FooterProps) {
|
|||||||
return theme === "dark" ? source_default_dark_img : source_default_img;
|
return theme === "dark" ? source_default_dark_img : source_default_img;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (icons?.includes("http")) {
|
if (icons?.startsWith("http://") || icons?.startsWith("https://")) {
|
||||||
return icons;
|
return icons;
|
||||||
} else {
|
} else {
|
||||||
return endpoint_http + icons;
|
return endpoint_http + icons;
|
||||||
|
|||||||
@@ -70,14 +70,22 @@ export default function ChatInput({
|
|||||||
}
|
}
|
||||||
}, [inputValue, disabled, onSend]);
|
}, [inputValue, disabled, onSend]);
|
||||||
|
|
||||||
|
const pressedKeys = new Set<string>();
|
||||||
|
|
||||||
const handleKeyDown = useCallback(
|
const handleKeyDown = useCallback(
|
||||||
(e: KeyboardEvent) => {
|
(e: KeyboardEvent) => {
|
||||||
|
pressedKeys.add(e.code);
|
||||||
|
|
||||||
if (e.code === "MetaLeft" || e.code === "MetaRight") {
|
if (e.code === "MetaLeft" || e.code === "MetaRight") {
|
||||||
setIsCommandPressed(true);
|
setIsCommandPressed(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.metaKey) {
|
if (pressedKeys.has("MetaLeft") || pressedKeys.has("MetaRight")) {
|
||||||
|
e.preventDefault();
|
||||||
switch (e.code) {
|
switch (e.code) {
|
||||||
|
case "Comma":
|
||||||
|
setIsCommandPressed(false);
|
||||||
|
break;
|
||||||
case "KeyI":
|
case "KeyI":
|
||||||
handleToggleFocus();
|
handleToggleFocus();
|
||||||
break;
|
break;
|
||||||
@@ -88,7 +96,7 @@ export default function ChatInput({
|
|||||||
console.log("KeyM");
|
console.log("KeyM");
|
||||||
break;
|
break;
|
||||||
case "Enter":
|
case "Enter":
|
||||||
isChatMode && (curChatEnd ? handleSubmit() : disabledChange());
|
isChatMode && (curChatEnd ? handleSubmit() : disabledChange?.());
|
||||||
break;
|
break;
|
||||||
case "KeyO":
|
case "KeyO":
|
||||||
console.log("KeyO");
|
console.log("KeyO");
|
||||||
@@ -107,10 +115,19 @@ export default function ChatInput({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[handleToggleFocus, isChatMode, handleSubmit]
|
[
|
||||||
|
handleToggleFocus,
|
||||||
|
isChatMode,
|
||||||
|
handleSubmit,
|
||||||
|
setSourceData,
|
||||||
|
setIsCommandPressed,
|
||||||
|
disabledChange,
|
||||||
|
curChatEnd,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleKeyUp = useCallback((e: KeyboardEvent) => {
|
const handleKeyUp = useCallback((e: KeyboardEvent) => {
|
||||||
|
pressedKeys.delete(e.code);
|
||||||
if (e.code === "MetaLeft" || e.code === "MetaRight") {
|
if (e.code === "MetaLeft" || e.code === "MetaRight") {
|
||||||
setIsCommandPressed(false);
|
setIsCommandPressed(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ function Search({ isChatMode, input }: SearchProps) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const debouncedSearch = useCallback(debounce(getSuggest, 300), [input]);
|
const debouncedSearch = useCallback(debounce(getSuggest, 500), [input]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
!isChatMode && !sourceData && debouncedSearch();
|
!isChatMode && !sourceData && debouncedSearch();
|
||||||
|
|||||||
@@ -1,7 +1,18 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect, useCallback } from "react";
|
||||||
import { Cloud } from "lucide-react";
|
import {
|
||||||
|
RefreshCcw,
|
||||||
|
Globe,
|
||||||
|
PackageOpen,
|
||||||
|
GitFork,
|
||||||
|
CalendarSync,
|
||||||
|
Trash2,
|
||||||
|
} from "lucide-react";
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||||
|
import {
|
||||||
|
onOpenUrl,
|
||||||
|
getCurrent as getCurrentDeepLinkUrls,
|
||||||
|
} from "@tauri-apps/plugin-deep-link";
|
||||||
|
|
||||||
import { UserProfile } from "./UserProfile";
|
import { UserProfile } from "./UserProfile";
|
||||||
import { DataSourcesList } from "./DataSourcesList";
|
import { DataSourcesList } from "./DataSourcesList";
|
||||||
@@ -11,39 +22,54 @@ import { OpenBrowserURL } from "@/utils/index";
|
|||||||
import { useAppStore } from "@/stores/appStore";
|
import { useAppStore } from "@/stores/appStore";
|
||||||
import { useAuthStore } from "@/stores/authStore";
|
import { useAuthStore } from "@/stores/authStore";
|
||||||
import { tauriFetch } from "@/api/tauriFetchClient";
|
import { tauriFetch } from "@/api/tauriFetchClient";
|
||||||
import {
|
import { useConnectStore } from "@/stores/connectStore";
|
||||||
onOpenUrl,
|
import bannerImg from "@/assets/images/coco-cloud-banner.jpeg";
|
||||||
getCurrent as getCurrentDeepLinkUrls,
|
|
||||||
} from "@tauri-apps/plugin-deep-link";
|
|
||||||
|
|
||||||
export default function CocoCloud() {
|
export default function CocoCloud() {
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
const [isConnect] = useState(true);
|
const [isConnect, setIsConnect] = useState(true);
|
||||||
|
|
||||||
const app_uid = useAppStore((state) => state.app_uid);
|
const app_uid = useAppStore((state) => state.app_uid);
|
||||||
const setAppUid = useAppStore((state) => state.setAppUid);
|
const setAppUid = useAppStore((state) => state.setAppUid);
|
||||||
const endpoint_http = useAppStore((state) => state.endpoint_http);
|
const setEndpoint = useAppStore((state) => state.setEndpoint);
|
||||||
|
const endpoint = useAppStore((state) => state.endpoint);
|
||||||
|
|
||||||
const { auth, setAuth } = useAuthStore();
|
const auth = useAuthStore((state) => state.auth);
|
||||||
|
const setAuth = useAuthStore((state) => state.setAuth);
|
||||||
const userInfo = useAuthStore((state) => state.userInfo);
|
const userInfo = useAuthStore((state) => state.userInfo);
|
||||||
const setUserInfo = useAuthStore((state) => state.setUserInfo);
|
const setUserInfo = useAuthStore((state) => state.setUserInfo);
|
||||||
|
const defaultService = useConnectStore((state) => state.defaultService);
|
||||||
|
const currentService = useConnectStore((state) => state.currentService);
|
||||||
|
const setDefaultService = useConnectStore((state) => state.setDefaultService);
|
||||||
|
const setCurrentService = useConnectStore((state) => state.setCurrentService);
|
||||||
|
const deleteOtherService = useConnectStore(
|
||||||
|
(state) => state.deleteOtherService
|
||||||
|
);
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [refreshLoading, setRefreshLoading] = useState(false);
|
||||||
|
|
||||||
const getProfile = async () => {
|
useEffect(() => {
|
||||||
|
console.log("currentService", currentService);
|
||||||
|
setLoading(false);
|
||||||
|
setRefreshLoading(false);
|
||||||
|
setError(null);
|
||||||
|
setEndpoint(currentService.endpoint);
|
||||||
|
setIsConnect(true);
|
||||||
|
}, [JSON.stringify(currentService)]);
|
||||||
|
|
||||||
|
const getProfile = useCallback(async () => {
|
||||||
const response: any = await tauriFetch({
|
const response: any = await tauriFetch({
|
||||||
url: `/provider/account/profile`,
|
url: `/account/profile`,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
});
|
});
|
||||||
console.log("getProfile", response);
|
console.log("getProfile", response);
|
||||||
setUserInfo(response.data || {});
|
setUserInfo(response.data || {}, endpoint);
|
||||||
};
|
}, [endpoint]);
|
||||||
|
|
||||||
const handleOAuthCallback = async (
|
const handleOAuthCallback = useCallback(
|
||||||
code: string | null,
|
async (code: string | null, provider: string | null) => {
|
||||||
provider: string | null
|
|
||||||
) => {
|
|
||||||
if (!code) {
|
if (!code) {
|
||||||
setError("No authorization code received");
|
setError("No authorization code received");
|
||||||
return;
|
return;
|
||||||
@@ -66,14 +92,18 @@ export default function CocoCloud() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (response.data?.access_token) {
|
if (response.data?.access_token) {
|
||||||
await setAuth({
|
await setAuth(
|
||||||
|
{
|
||||||
token: response.data?.access_token,
|
token: response.data?.access_token,
|
||||||
expires: response.data?.expire_at,
|
expires: response.data?.expire_at,
|
||||||
plan: { upgraded: false, last_checked: 0 },
|
plan: { upgraded: false, last_checked: 0 },
|
||||||
});
|
},
|
||||||
|
endpoint
|
||||||
|
);
|
||||||
|
|
||||||
getProfile();
|
getProfile();
|
||||||
} else {
|
} else {
|
||||||
|
await setAuth(undefined, endpoint);
|
||||||
setError("Sign in failed: " + response.data?.error?.reason);
|
setError("Sign in failed: " + response.data?.error?.reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,12 +113,14 @@ export default function CocoCloud() {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Sign in failed:", error);
|
console.error("Sign in failed:", error);
|
||||||
setError("Sign in failed: catch");
|
setError("Sign in failed: catch");
|
||||||
await setAuth(undefined);
|
await setAuth(undefined, endpoint);
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
[app_uid, endpoint]
|
||||||
|
);
|
||||||
|
|
||||||
const handleUrl = (url: string) => {
|
const handleUrl = (url: string) => {
|
||||||
try {
|
try {
|
||||||
@@ -108,7 +140,6 @@ export default function CocoCloud() {
|
|||||||
// default:
|
// default:
|
||||||
// console.log("Unhandled deep link path:", urlObject.pathname);
|
// console.log("Unhandled deep link path:", urlObject.pathname);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to parse URL:", err);
|
console.error("Failed to parse URL:", err);
|
||||||
setError("Invalid URL format");
|
setError("Invalid URL format");
|
||||||
@@ -135,87 +166,167 @@ export default function CocoCloud() {
|
|||||||
return () => {
|
return () => {
|
||||||
unlisten.then((fn) => fn());
|
unlisten.then((fn) => fn());
|
||||||
};
|
};
|
||||||
}, []);
|
}, [app_uid]);
|
||||||
|
|
||||||
|
const LoginClick = useCallback(() => {
|
||||||
|
if (loading) return;
|
||||||
|
setAuth(undefined, endpoint);
|
||||||
|
|
||||||
function LoginClick() {
|
|
||||||
let uid = uuidv4();
|
let uid = uuidv4();
|
||||||
setAppUid(uid);
|
setAppUid(uid);
|
||||||
|
|
||||||
|
console.log("LoginClick", uid, currentService.auth_provider.sso.url);
|
||||||
|
|
||||||
OpenBrowserURL(
|
OpenBrowserURL(
|
||||||
`${endpoint_http}/sso/login/?provider=coco-cloud&product=coco&request_id=${uid}`
|
`${currentService.auth_provider.sso.url}/?provider=coco-cloud&product=coco&request_id=${uid}`
|
||||||
);
|
);
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
}, [JSON.stringify(currentService)]);
|
||||||
|
|
||||||
|
function goToHref(url: string) {
|
||||||
|
OpenBrowserURL(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const refreshClick = useCallback(() => {
|
||||||
<div className="flex min-h-screen bg-gray-50">
|
setRefreshLoading(true);
|
||||||
<Sidebar />
|
tauriFetch({
|
||||||
|
url: `/provider/_info`,
|
||||||
|
method: "GET",
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
setEndpoint(res.data.endpoint);
|
||||||
|
setCurrentService(res.data || {});
|
||||||
|
if (res.data?.endpoint === "https://coco.infini.cloud/") {
|
||||||
|
setDefaultService(res.data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setRefreshLoading(false);
|
||||||
|
});
|
||||||
|
}, [JSON.stringify(defaultService)]);
|
||||||
|
|
||||||
<main className="flex-1">
|
function addService() {
|
||||||
|
setIsConnect(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteClick = useCallback(() => {
|
||||||
|
deleteOtherService(currentService);
|
||||||
|
setAuth(undefined, endpoint);
|
||||||
|
setUserInfo({}, endpoint);
|
||||||
|
}, [JSON.stringify(currentService), endpoint]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex bg-gray-50 dark:bg-gray-900">
|
||||||
|
<Sidebar addService={addService} />
|
||||||
|
|
||||||
|
<main className="flex-1 p-4 py-8">
|
||||||
<div>
|
<div>
|
||||||
{error && (
|
{error && (
|
||||||
<div className="text-red-500 dark:text-red-400 p-4">
|
<div className="text-red-500 dark:text-red-400 mb-4">
|
||||||
Error: {error}
|
Error: {error}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isConnect ? (
|
{isConnect ? (
|
||||||
<div className="max-w-4xl mx-auto px-4 py-8">
|
<div className="max-w-4xl mx-auto">
|
||||||
<div className="flex items-center justify-between mb-8">
|
<div className="w-full rounded-[4px] bg-[rgba(229,229,229,1)] dark:bg-gray-800 mb-6">
|
||||||
|
<img
|
||||||
|
src={currentService.provider.banner || bannerImg}
|
||||||
|
alt="banner"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<div className="flex items-center space-x-2 px-4 py-2 bg-white rounded-md border border-gray-200">
|
<div className="flex items-center text-gray-900 dark:text-white font-medium">
|
||||||
<Cloud className="w-5 h-5 text-blue-500" />
|
{currentService.name}
|
||||||
<span className="font-medium text-[#333]">Coco Cloud</span>
|
|
||||||
</div>
|
</div>
|
||||||
<span className="px-3 py-1 text-sm text-blue-600 bg-blue-50 rounded-md">
|
|
||||||
Available
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<button className="p-2 text-gray-500 hover:text-gray-700">
|
<div className="flex gap-2">
|
||||||
<Cloud className="w-5 h-5" />
|
<button
|
||||||
|
className="p-2 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300 rounded-[6px] bg-white dark:bg-gray-800 border border-[rgba(228,229,239,1)] dark:border-gray-700"
|
||||||
|
onClick={() => goToHref(currentService.provider.website)}
|
||||||
|
>
|
||||||
|
<Globe className="w-3.5 h-3.5" />
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
className="p-2 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300 rounded-[6px] bg-white dark:bg-gray-800 border border-[rgba(228,229,239,1)] dark:border-gray-700"
|
||||||
|
onClick={() => refreshClick()}
|
||||||
|
>
|
||||||
|
<RefreshCcw
|
||||||
|
className={`w-3.5 h-3.5 ${
|
||||||
|
refreshLoading ? "animate-spin" : ""
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
{currentService.endpoint !== defaultService.endpoint ? (
|
||||||
|
<button
|
||||||
|
className="p-2 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300 rounded-[6px] bg-white dark:bg-gray-800 border border-[rgba(228,229,239,1)] dark:border-gray-700"
|
||||||
|
onClick={() => deleteClick()}
|
||||||
|
>
|
||||||
|
<Trash2 className="w-3.5 h-3.5 text-[#ff4747]" />
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<div className="text-sm text-gray-500 mb-4">
|
<div className="text-sm text-gray-500 dark:text-gray-400 mb-2 flex">
|
||||||
<span>Service provision: INFINI Labs</span>
|
<span className="flex items-center gap-1">
|
||||||
|
<PackageOpen className="w-4 h-4" />{" "}
|
||||||
|
{currentService.provider.name}
|
||||||
|
</span>
|
||||||
<span className="mx-4">|</span>
|
<span className="mx-4">|</span>
|
||||||
<span>Version Number: v2.3.0</span>
|
<span className="flex items-center gap-1">
|
||||||
|
<GitFork className="w-4 h-4" />{" "}
|
||||||
|
{currentService.version.number}
|
||||||
|
</span>
|
||||||
<span className="mx-4">|</span>
|
<span className="mx-4">|</span>
|
||||||
<span>Update time: 2023-05-12</span>
|
<span className="flex items-center gap-1">
|
||||||
|
<CalendarSync className="w-4 h-4" /> {currentService.updated}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-gray-600 leading-relaxed">
|
<p className="text-gray-600 dark:text-gray-300 leading-relaxed">
|
||||||
Coco Cloud provides users with a cloud storage and data
|
{currentService.provider.description}
|
||||||
integration platform that supports account registration and data
|
|
||||||
source management. Users can integrate multiple data sources
|
|
||||||
(such as Google Drive, yuque, GitHub, etc.), easily access and
|
|
||||||
search for files, documents and codes across platforms, and
|
|
||||||
achieve efficient data collaboration and management.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{currentService.auth_provider.sso.url ? (
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<h2 className="text-lg font-medium text-gray-900 mb-4">
|
<h2 className="text-lg font-medium text-gray-900 dark:text-white mb-4">
|
||||||
Account Information
|
Account Information
|
||||||
</h2>
|
</h2>
|
||||||
{auth ? (
|
{auth && auth[endpoint] ? (
|
||||||
<UserProfile userInfo={userInfo} />
|
<UserProfile userInfo={userInfo[endpoint]} />
|
||||||
) : (
|
) : (
|
||||||
|
<div>
|
||||||
<button
|
<button
|
||||||
className="px-6 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition-colors"
|
className="px-6 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition-colors mb-3"
|
||||||
onClick={LoginClick}
|
onClick={LoginClick}
|
||||||
>
|
>
|
||||||
{loading ? "Login..." : "Login"}
|
{loading ? "Login..." : "Login"}
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
className="text-xs text-[#0096FB] dark:text-blue-400 block"
|
||||||
|
onClick={() =>
|
||||||
|
goToHref(currentService.provider.privacy_policy)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
EULA | Privacy Policy
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{auth ? <DataSourcesList /> : null}
|
{auth && auth[endpoint] ? <DataSourcesList /> : null}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<ConnectService />
|
<ConnectService setIsConnect={setIsConnect} />
|
||||||
)}
|
)}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,52 +1,121 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState, useCallback } from "react";
|
||||||
import { ArrowLeft } from 'lucide-react';
|
import { ChevronLeft } from "lucide-react";
|
||||||
|
|
||||||
export function ConnectService() {
|
import { useConnectStore } from "@/stores/connectStore";
|
||||||
const [sourceName, setSourceName] = useState('');
|
import { tauriFetch } from "@/api/tauriFetchClient";
|
||||||
|
import { useAppStore } from "@/stores/appStore";
|
||||||
|
|
||||||
|
interface ConnectServiceProps {
|
||||||
|
setIsConnect: (isConnect: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ConnectService({ setIsConnect }: ConnectServiceProps) {
|
||||||
|
const addOtherServices = useConnectStore((state) => state.addOtherServices);
|
||||||
|
const setCurrentService = useConnectStore((state) => state.setCurrentService);
|
||||||
|
const defaultService = useConnectStore((state) => state.defaultService);
|
||||||
|
const otherServices = useConnectStore((state) => state.otherServices);
|
||||||
|
|
||||||
|
const setEndpoint = useAppStore((state) => state.setEndpoint);
|
||||||
|
|
||||||
|
const [endpointLink, setEndpointLink] = useState("");
|
||||||
|
const [refreshLoading, setRefreshLoading] = useState(false);
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
console.log('Connecting Google Drive with name:', sourceName);
|
console.log("Connecting Google Drive with name:", endpointLink);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const goBack = () => {
|
||||||
|
setIsConnect(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addService = useCallback(() => {
|
||||||
|
if (!endpointLink) return;
|
||||||
|
if (!endpointLink.startsWith("http://") && !endpointLink.startsWith("https://")) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setRefreshLoading(true);
|
||||||
|
//
|
||||||
|
let baseURL = endpointLink;
|
||||||
|
if (baseURL.endsWith("/")) {
|
||||||
|
baseURL = baseURL.slice(0, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
tauriFetch({
|
||||||
|
url: `${baseURL}/provider/_info`,
|
||||||
|
method: "GET",
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
if (
|
||||||
|
res.data?.endpoint === defaultService.endpoint ||
|
||||||
|
otherServices.some(
|
||||||
|
(item: any) => item.endpoint === res.data?.endpoint
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
console.error(`${res.data?.endpoint} Repeated`);
|
||||||
|
} else {
|
||||||
|
addOtherServices(res.data);
|
||||||
|
setCurrentService(res.data);
|
||||||
|
setEndpoint(res.data.endpoint);
|
||||||
|
setIsConnect(true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setRefreshLoading(false);
|
||||||
|
});
|
||||||
|
}, [endpointLink]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-8 max-w-4xl">
|
<div className="max-w-4xl">
|
||||||
<div className="mb-8">
|
<div className="flex items-center gap-2 mb-8">
|
||||||
<button className="flex items-center text-gray-600 hover:text-gray-900">
|
<button
|
||||||
<ArrowLeft className="w-5 h-5 mr-2" />
|
className=" text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 border border-[rgba(228,229,239,1)] dark:border-gray-700 p-1"
|
||||||
<span>Connect Google Drive</span>
|
onClick={goBack}
|
||||||
|
>
|
||||||
|
<ChevronLeft className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
|
<div className="text-xl text-[#101010] dark:text-white">
|
||||||
|
Connecting to third-party services
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<p className="text-gray-600">
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
Coco needs to obtain authorization from your Google Drive account
|
Third-party services are provided by other platforms or providers, and
|
||||||
|
users can integrate these services into Coco AI to expand the scope of
|
||||||
|
search data.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-6">
|
<form onSubmit={handleSubmit} className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="sourceName" className="block text-sm font-medium text-gray-700 mb-1">
|
<label
|
||||||
Data Source Name
|
htmlFor="endpoint"
|
||||||
|
className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2.5"
|
||||||
|
>
|
||||||
|
Server address
|
||||||
</label>
|
</label>
|
||||||
|
<div className="flex gap-2">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="sourceName"
|
id="endpoint"
|
||||||
value={sourceName}
|
value={endpointLink}
|
||||||
onChange={(e) => setSourceName(e.target.value)}
|
placeholder="For example: https://coco.infini.cloud/"
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
onChange={(e) => setEndpointLink(e.target.value)}
|
||||||
placeholder="Your Google Drive"
|
className="text-[#101010] dark:text-white flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-800"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors"
|
className="px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors"
|
||||||
|
onClick={addService}
|
||||||
>
|
>
|
||||||
Save
|
{refreshLoading ? "Connecting..." : "Connect"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
import { Link2, Trash2 } from "lucide-react";
|
import { Link2 } from "lucide-react";
|
||||||
|
|
||||||
|
import { useAppStore } from "@/stores/appStore";
|
||||||
|
import source_default_img from "@/assets/images/source_default.png";
|
||||||
|
import source_default_dark_img from "@/assets/images/source_default_dark.png";
|
||||||
|
import { useTheme } from "@/contexts/ThemeContext";
|
||||||
|
import { useConnectStore } from "@/stores/connectStore";
|
||||||
|
|
||||||
interface Account {
|
interface Account {
|
||||||
email: string;
|
email: string;
|
||||||
@@ -7,56 +13,88 @@ interface Account {
|
|||||||
|
|
||||||
interface DataSourceItemProps {
|
interface DataSourceItemProps {
|
||||||
name: string;
|
name: string;
|
||||||
type: string;
|
connector: any;
|
||||||
accounts: Account[];
|
accounts?: Account[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DataSourceItem({ name, type, accounts }: DataSourceItemProps) {
|
export function DataSourceItem({ name, connector }: DataSourceItemProps) {
|
||||||
const isConnected = accounts.length > 0;
|
// const isConnected = true;
|
||||||
|
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
|
const connector_data = useConnectStore((state) => state.connector_data);
|
||||||
|
const endpoint_http = useAppStore((state) => state.endpoint_http);
|
||||||
|
|
||||||
|
function findConnectorIcon() {
|
||||||
|
const connector_id = connector?.id;
|
||||||
|
|
||||||
|
const result_connector = connector_data[endpoint_http]?.find(
|
||||||
|
(data: any) => data._source.id === connector_id
|
||||||
|
);
|
||||||
|
|
||||||
|
return result_connector?._source;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTypeIcon() {
|
||||||
|
const connectorSource = findConnectorIcon();
|
||||||
|
const icons = connectorSource?.icon;
|
||||||
|
|
||||||
|
if (!icons) {
|
||||||
|
return theme === "dark" ? source_default_dark_img : source_default_img;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (icons?.startsWith("http://") || icons?.startsWith("https://")) {
|
||||||
|
return icons;
|
||||||
|
} else {
|
||||||
|
return endpoint_http + icons;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="border border-gray-200 rounded-lg p-4">
|
<div className="border border-gray-200 dark:border-gray-700 rounded-lg p-4 bg-white dark:bg-gray-800">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<img src={`/icons/${type}.svg`} alt={name} className="w-6 h-6" />
|
<img src={getTypeIcon()} alt={name} className="w-6 h-6" />
|
||||||
<span className="font-medium">{name}</span>
|
<span className="font-medium text-gray-900 dark:text-white">
|
||||||
|
{name}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<button className="text-blue-500 hover:text-blue-600 flex items-center space-x-1">
|
<button className="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 flex items-center space-x-1">
|
||||||
<Link2 className="w-4 h-4" />
|
<Link2 className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-500 mb-2">
|
{/* <div className="text-sm text-gray-500 dark:text-gray-400 mb-2">
|
||||||
{isConnected ? "Manage" : "Connect Accounts"}
|
{isConnected ? "Manage" : "Connect Accounts"}
|
||||||
</div>
|
</div> */}
|
||||||
|
|
||||||
{accounts.map((account, index) => (
|
{/* {accounts.map((account, index) => (
|
||||||
<div
|
<div
|
||||||
key={account.email}
|
key={account.email}
|
||||||
className="flex items-center justify-between py-2 border-t border-gray-100"
|
className="flex items-center justify-between py-2 border-t border-gray-100 dark:border-gray-700"
|
||||||
>
|
>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<div className="w-8 h-8 bg-gray-100 rounded-full flex items-center justify-center">
|
<div className="w-8 h-8 bg-gray-100 dark:bg-gray-700 rounded-full flex items-center justify-center">
|
||||||
<span className="text-sm text-gray-500">
|
<span className="text-sm text-gray-500 dark:text-gray-400">
|
||||||
{account.email[0].toUpperCase()}
|
{account.email[0].toUpperCase()}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm font-medium">
|
<div className="text-sm font-medium text-gray-900 dark:text-white">
|
||||||
{index === 0 ? "My network disk" : `Network disk ${index + 1}`}
|
{index === 0 ? "My network disk" : `Network disk ${index + 1}`}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-gray-500">{account.email}</div>
|
<div className="text-xs text-gray-500 dark:text-gray-400">{account.email}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<span className="text-xs text-gray-500">
|
<span className="text-xs text-gray-500 dark:text-gray-400">
|
||||||
Recently Synced: {account.lastSync}
|
Recently Synced: {account.lastSync}
|
||||||
</span>
|
</span>
|
||||||
<button className="text-gray-400 hover:text-gray-600">
|
<button className="text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300">
|
||||||
<Trash2 className="w-4 h-4" />
|
<Trash2 className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))} */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,55 @@
|
|||||||
import { DataSourceItem } from './DataSourceItem';
|
import { useEffect, useState } from "react";
|
||||||
|
import { RefreshCcw } from "lucide-react";
|
||||||
|
|
||||||
|
import { DataSourceItem } from "./DataSourceItem";
|
||||||
|
import { useConnectStore } from "@/stores/connectStore";
|
||||||
|
import { tauriFetch } from "@/api/tauriFetchClient";
|
||||||
|
import { useAppStore } from "@/stores/appStore";
|
||||||
|
|
||||||
export function DataSourcesList() {
|
export function DataSourcesList() {
|
||||||
const dataSources = [
|
const datasourceData = useConnectStore((state) => state.datasourceData);
|
||||||
{
|
const setDatasourceData = useConnectStore((state) => state.setDatasourceData);
|
||||||
id: 'google-drive',
|
|
||||||
name: 'Google Drive',
|
const endpoint_http = useAppStore((state) => state.endpoint_http);
|
||||||
type: 'google',
|
|
||||||
accounts: [
|
const [refreshLoading, setRefreshLoading] = useState(false);
|
||||||
{ email: 'an121245@gmail.com', lastSync: '2025-01-02 09:50 AM' },
|
|
||||||
{ email: '9paiii@gmail.com', lastSync: '2025-01-02 09:50 AM' }
|
async function getDatasourceData() {
|
||||||
]
|
setRefreshLoading(true);
|
||||||
},
|
try {
|
||||||
{
|
const response = await tauriFetch({
|
||||||
id: 'yuque',
|
url: `/datasource/_search`,
|
||||||
name: 'Yuque',
|
method: "GET",
|
||||||
type: 'yuque',
|
});
|
||||||
accounts: []
|
console.log("datasource", response);
|
||||||
},
|
const data = response.data?.hits?.hits || [];
|
||||||
{
|
setDatasourceData(data, endpoint_http);
|
||||||
id: 'github',
|
} catch (error) {
|
||||||
name: 'Github',
|
console.error("Failed to fetch user data:", error);
|
||||||
type: 'github',
|
|
||||||
accounts: []
|
|
||||||
}
|
}
|
||||||
];
|
setRefreshLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getDatasourceData()
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h2 className="text-xl font-medium text-gray-900">Data Source</h2>
|
<h2 className="flex justify-between text-xl font-medium text-gray-900 dark:text-white">
|
||||||
|
Data Source
|
||||||
|
<button
|
||||||
|
className="p-2 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300 rounded-[6px] bg-white dark:bg-gray-800 border border-[rgba(228,229,239,1)] dark:border-gray-700"
|
||||||
|
onClick={() => getDatasourceData()}
|
||||||
|
>
|
||||||
|
<RefreshCcw
|
||||||
|
className={`w-3.5 h-3.5 ${refreshLoading ? "animate-spin" : ""}`}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{dataSources.map(source => (
|
{datasourceData[endpoint_http]?.map((source) => (
|
||||||
<DataSourceItem key={source.id} {...source} />
|
<DataSourceItem key={source._id} {...source._source} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,24 +1,160 @@
|
|||||||
import { Cloud, Plus } from "lucide-react";
|
import { useState, useEffect } from "react";
|
||||||
|
import { Plus } from "lucide-react";
|
||||||
|
|
||||||
|
import cocoLogoImg from "@/assets/app-icon.png";
|
||||||
|
import { tauriFetch } from "@/api/tauriFetchClient";
|
||||||
|
import { useConnectStore } from "@/stores/connectStore";
|
||||||
|
import { useAppStore } from "@/stores/appStore";
|
||||||
|
|
||||||
|
interface SidebarProps {
|
||||||
|
addService: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
type StringBooleanMap = {
|
||||||
|
[key: string]: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Sidebar({ addService }: SidebarProps) {
|
||||||
|
const defaultService = useConnectStore((state) => state.defaultService);
|
||||||
|
const currentService = useConnectStore((state) => state.currentService);
|
||||||
|
const otherServices = useConnectStore((state) => state.otherServices);
|
||||||
|
const setCurrentService = useConnectStore((state) => state.setCurrentService);
|
||||||
|
|
||||||
|
const setEndpoint = useAppStore((state) => state.setEndpoint);
|
||||||
|
|
||||||
|
const [defaultHealth, setDefaultHealth] = useState(false);
|
||||||
|
const [otherHealth, setOtherHealth] = useState<StringBooleanMap>({});
|
||||||
|
|
||||||
|
const addServiceClick = () => {
|
||||||
|
addService();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getDefaultHealth();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getOtherHealth(currentService);
|
||||||
|
setEndpoint(currentService.endpoint);
|
||||||
|
}, [currentService.endpoint]);
|
||||||
|
|
||||||
|
const getDefaultHealth = () => {
|
||||||
|
let baseURL = defaultService.endpoint
|
||||||
|
if (baseURL.endsWith("/")) {
|
||||||
|
baseURL = baseURL.slice(0, -1);
|
||||||
|
}
|
||||||
|
tauriFetch({
|
||||||
|
url: `${baseURL}/health`,
|
||||||
|
method: "GET",
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
// "services": {
|
||||||
|
// "system_cluster": "yellow"
|
||||||
|
// },
|
||||||
|
// "status": "yellow"
|
||||||
|
setDefaultHealth(res.data?.status !== "red");
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getOtherHealth = (item: any) => {
|
||||||
|
if (!item.endpoint) return;
|
||||||
|
//
|
||||||
|
let baseURL = item.endpoint
|
||||||
|
if (baseURL.endsWith("/")) {
|
||||||
|
baseURL = baseURL.slice(0, -1);
|
||||||
|
}
|
||||||
|
tauriFetch({
|
||||||
|
url: `${baseURL}/health`,
|
||||||
|
method: "GET",
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
let obj = {
|
||||||
|
...otherHealth,
|
||||||
|
[item.endpoint]: res.data?.status !== "red",
|
||||||
|
};
|
||||||
|
setOtherHealth(obj);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export function Sidebar() {
|
|
||||||
return (
|
return (
|
||||||
<div className="w-64 border-r border-gray-200 bg-white">
|
<div className="w-64 min-h-[550px] border-r border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800">
|
||||||
<div className="p-4">
|
<div className="p-4 py-8">
|
||||||
<div className="flex items-center space-x-2 px-3 py-2 bg-blue-50 text-blue-600 rounded-lg mb-6">
|
<div
|
||||||
<Cloud className="w-5 h-5" />
|
className={`flex items-center space-x-2 px-3 py-2 bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400 rounded-lg mb-6 ${
|
||||||
<span className="font-medium">Coco Cloud</span>
|
currentService.endpoint === defaultService.endpoint
|
||||||
|
? "border border-[rgba(0,135,255,1)]"
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
setCurrentService(defaultService);
|
||||||
|
setEndpoint(defaultService.endpoint);
|
||||||
|
getDefaultHealth();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={defaultService.provider.icon || cocoLogoImg}
|
||||||
|
alt="cocoLogoImg"
|
||||||
|
className="w-5 h-5"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span className="font-medium">{defaultService.name}</span>
|
||||||
<div className="flex-1" />
|
<div className="flex-1" />
|
||||||
<button className="text-blue-600 hover:text-blue-700">
|
<button className="text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300">
|
||||||
<Cloud className="w-4 h-4" />
|
{defaultHealth ? (
|
||||||
|
<div className="w-3 h-3 rounded-full bg-[#00DB5E]"></div>
|
||||||
|
) : (
|
||||||
|
<div className="w-3 h-3 rounded-full bg-[#FF4747]"></div>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="text-sm font-medium text-gray-500 dark:text-gray-400 mb-2">
|
||||||
<div className="text-sm font-medium text-gray-500 px-3 mb-2">
|
|
||||||
Third-party services
|
Third-party services
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button className="w-full flex items-center justify-center p-2 border-2 border-dashed border-gray-200 rounded-lg text-gray-400 hover:text-gray-600 hover:border-gray-300">
|
{otherServices?.map((item, index) => (
|
||||||
|
<div
|
||||||
|
key={item.name + index}
|
||||||
|
className={`flex items-center space-x-2 px-3 py-2 bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400 rounded-lg mb-2 ${
|
||||||
|
currentService.endpoint === item.endpoint
|
||||||
|
? "border border-[rgba(0,135,255,1)]"
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
setEndpoint(item.endpoint);
|
||||||
|
setCurrentService(item);
|
||||||
|
getOtherHealth(item);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={item.provider.icon || cocoLogoImg}
|
||||||
|
alt="LogoImg"
|
||||||
|
className="w-5 h-5"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span className="font-medium">{item.name}</span>
|
||||||
|
<div className="flex-1" />
|
||||||
|
<button className="text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300">
|
||||||
|
{otherHealth[item.endpoint] ? (
|
||||||
|
<div className="w-3 h-3 rounded-full bg-[#00DB5E]"></div>
|
||||||
|
) : (
|
||||||
|
<div className="w-3 h-3 rounded-full bg-[#FF4747]"></div>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<button
|
||||||
|
className="w-full flex items-center justify-center p-2 border-2 border-dashed border-gray-200 dark:border-gray-700 rounded-lg text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300 hover:border-gray-300 dark:hover:border-gray-600"
|
||||||
|
onClick={addServiceClick}
|
||||||
|
>
|
||||||
<Plus className="w-5 h-5" />
|
<Plus className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import { User, Edit, LogOut } from "lucide-react";
|
import { User, LogOut } from "lucide-react";
|
||||||
|
|
||||||
import { useAuthStore } from "@/stores/authStore";
|
import { useAuthStore } from "@/stores/authStore";
|
||||||
|
import { useAppStore } from "@/stores/appStore";
|
||||||
|
|
||||||
interface UserPreferences {
|
interface UserPreferences {
|
||||||
theme: "dark" | "light";
|
theme: "dark" | "light";
|
||||||
language: string;
|
language: string;
|
||||||
}
|
}
|
||||||
interface UserInfo {
|
interface UserInfo {
|
||||||
username: string;
|
name: string;
|
||||||
email: string;
|
email: string;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
roles: string[]; // ["admin", "editor"]
|
roles: string[]; // ["admin", "editor"]
|
||||||
@@ -21,45 +22,40 @@ interface UserProfileProps {
|
|||||||
export function UserProfile({ userInfo }: UserProfileProps) {
|
export function UserProfile({ userInfo }: UserProfileProps) {
|
||||||
const setAuth = useAuthStore((state) => state.setAuth);
|
const setAuth = useAuthStore((state) => state.setAuth);
|
||||||
const setUserInfo = useAuthStore((state) => state.setUserInfo);
|
const setUserInfo = useAuthStore((state) => state.setUserInfo);
|
||||||
|
const endpoint = useAppStore((state) => state.endpoint);
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
setAuth(undefined);
|
setAuth(undefined, endpoint);
|
||||||
setUserInfo({});
|
setUserInfo({}, endpoint);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<div className="w-12 h-12 bg-gray-100 rounded-full flex items-center justify-center">
|
<div className="w-12 h-12 bg-gray-100 dark:bg-gray-700 rounded-full flex items-center justify-center">
|
||||||
{userInfo.avatar ? (
|
{userInfo?.avatar ? (
|
||||||
<img
|
<img src={userInfo?.avatar} alt="" className="w-6 h-6" />
|
||||||
src={userInfo.avatar}
|
|
||||||
alt=""
|
|
||||||
className="w-6 h-6"
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<User className="w-6 h-6 text-gray-500" />
|
<User className="w-6 h-6 text-gray-500 dark:text-gray-400" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-4">
|
||||||
<span className="font-medium text-gray-900">
|
<span className="font-medium text-gray-900 dark:text-white">
|
||||||
{userInfo.username || "-"}
|
{userInfo?.name || "-"}
|
||||||
</span>
|
</span>
|
||||||
<button className="text-gray-400 hover:text-gray-600">
|
|
||||||
<Edit className="w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<span className="text-sm text-gray-500">{userInfo.email || "-"}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button
|
<button
|
||||||
onClick={handleLogout}
|
onClick={handleLogout}
|
||||||
className="flex items-center space-x-1 text-red-500 hover:text-red-600"
|
className="flex items-center p-1 text-red-500 hover:text-red-600 dark:text-red-400 dark:hover:text-red-300 border border-[rgba(228,229,239,1)] dark:border-gray-700"
|
||||||
>
|
>
|
||||||
<LogOut className="w-4 h-4" />
|
<LogOut className="w-4 h-4" />
|
||||||
<span>Logout</span>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<span className="text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
{userInfo?.email || "-"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,8 +57,8 @@ export default function ChatInput() {
|
|||||||
method: "GET",
|
method: "GET",
|
||||||
});
|
});
|
||||||
setInfo(JSON.stringify(response));
|
setInfo(JSON.stringify(response));
|
||||||
console.log(response.status); // e.g. 200
|
// console.log(response.status); // e.g. 200
|
||||||
console.log(response.statusText); // e.g. "OK"
|
// console.log(response.statusText); // e.g. "OK"
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error sending message:", error);
|
console.error("Error sending message:", error);
|
||||||
setInfo(JSON.stringify(error));
|
setInfo(JSON.stringify(error));
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ const ChatAI = forwardRef<ChatAIRef, ChatAIProps>(
|
|||||||
if (isTauri()) {
|
if (isTauri()) {
|
||||||
createWin && createWin({
|
createWin && createWin({
|
||||||
label: "chat",
|
label: "chat",
|
||||||
title: "Coco AI",
|
title: "Coco Chat",
|
||||||
dragDropEnabled: true,
|
dragDropEnabled: true,
|
||||||
center: true,
|
center: true,
|
||||||
width: 900,
|
width: 900,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const ChatSwitch: React.FC<ChatSwitchProps> = ({ isChatMode, onChange }) => {
|
|||||||
(event: KeyboardEvent) => {
|
(event: KeyboardEvent) => {
|
||||||
if (event.metaKey && event.key === "t") {
|
if (event.metaKey && event.key === "t") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
console.log("Switch mode triggered");
|
// console.log("Switch mode triggered");
|
||||||
handleToggle();
|
handleToggle();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,14 +5,16 @@ import {formatter} from "@/utils/index"
|
|||||||
import source_default_img from "@/assets/images/source_default.png";
|
import source_default_img from "@/assets/images/source_default.png";
|
||||||
import source_default_dark_img from "@/assets/images/source_default_dark.png";
|
import source_default_dark_img from "@/assets/images/source_default_dark.png";
|
||||||
import { useTheme } from "@/contexts/ThemeContext";
|
import { useTheme } from "@/contexts/ThemeContext";
|
||||||
|
import { useConnectStore } from "@/stores/connectStore";
|
||||||
|
|
||||||
interface DocumentDetailProps {
|
interface DocumentDetailProps {
|
||||||
document: any;
|
document: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DocumentDetail: React.FC<DocumentDetailProps> = ({ document }) => {
|
export const DocumentDetail: React.FC<DocumentDetailProps> = ({ document }) => {
|
||||||
const connector_data = useAppStore((state) => state.connector_data);
|
const connector_data = useConnectStore((state) => state.connector_data);
|
||||||
const datasourceData = useAppStore((state) => state.datasourceData);
|
const datasourceData = useConnectStore((state) => state.datasourceData);
|
||||||
|
|
||||||
const endpoint_http = useAppStore((state) => state.endpoint_http);
|
const endpoint_http = useAppStore((state) => state.endpoint_http);
|
||||||
|
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
@@ -20,13 +22,13 @@ export const DocumentDetail: React.FC<DocumentDetailProps> = ({ document }) => {
|
|||||||
function findConnectorIcon(item: any) {
|
function findConnectorIcon(item: any) {
|
||||||
const id = item?._source?.source?.id || "";
|
const id = item?._source?.source?.id || "";
|
||||||
|
|
||||||
const result_source = datasourceData.find(
|
const result_source = datasourceData[endpoint_http]?.find(
|
||||||
(data: any) => data._source.id === id
|
(data: any) => data._source.id === id
|
||||||
);
|
);
|
||||||
|
|
||||||
const connector_id = result_source?._source?.connector?.id;
|
const connector_id = result_source?._source?.connector?.id;
|
||||||
|
|
||||||
const result_connector = connector_data.find(
|
const result_connector = connector_data[endpoint_http]?.find(
|
||||||
(data: any) => data._source.id === connector_id
|
(data: any) => data._source.id === connector_id
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -41,7 +43,7 @@ export const DocumentDetail: React.FC<DocumentDetailProps> = ({ document }) => {
|
|||||||
return theme === "dark" ? source_default_dark_img : source_default_img;
|
return theme === "dark" ? source_default_dark_img : source_default_img;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (icons?.includes("http")) {
|
if (icons?.startsWith("http://") || icons?.startsWith("https://")) {
|
||||||
return icons;
|
return icons;
|
||||||
} else {
|
} else {
|
||||||
return endpoint_http + icons;
|
return endpoint_http + icons;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { useSearchStore } from "@/stores/searchStore";
|
|||||||
import { SearchHeader } from "./SearchHeader";
|
import { SearchHeader } from "./SearchHeader";
|
||||||
import file_efault_img from "@/assets/images/file_efault.png";
|
import file_efault_img from "@/assets/images/file_efault.png";
|
||||||
import noDataImg from "@/assets/coconut-tree.png";
|
import noDataImg from "@/assets/coconut-tree.png";
|
||||||
|
import { useConnectStore } from "@/stores/connectStore";
|
||||||
|
|
||||||
interface DocumentListProps {
|
interface DocumentListProps {
|
||||||
onSelectDocument: (id: string) => void;
|
onSelectDocument: (id: string) => void;
|
||||||
@@ -25,8 +26,9 @@ export const DocumentList: React.FC<DocumentListProps> = ({
|
|||||||
getDocDetail,
|
getDocDetail,
|
||||||
isChatMode,
|
isChatMode,
|
||||||
}) => {
|
}) => {
|
||||||
const connector_data = useAppStore((state) => state.connector_data);
|
const connector_data = useConnectStore((state) => state.connector_data);
|
||||||
const datasourceData = useAppStore((state) => state.datasourceData);
|
const datasourceData = useConnectStore((state) => state.datasourceData);
|
||||||
|
|
||||||
const sourceData = useSearchStore((state) => state.sourceData);
|
const sourceData = useSearchStore((state) => state.sourceData);
|
||||||
const endpoint_http = useAppStore((state) => state.endpoint_http);
|
const endpoint_http = useAppStore((state) => state.endpoint_http);
|
||||||
|
|
||||||
@@ -109,13 +111,13 @@ export const DocumentList: React.FC<DocumentListProps> = ({
|
|||||||
function findConnectorIcon(item: any) {
|
function findConnectorIcon(item: any) {
|
||||||
const id = item?._source?.source?.id || "";
|
const id = item?._source?.source?.id || "";
|
||||||
|
|
||||||
const result_source = datasourceData.find(
|
const result_source = datasourceData[endpoint_http]?.find(
|
||||||
(data: any) => data._source.id === id
|
(data: any) => data._source.id === id
|
||||||
);
|
);
|
||||||
|
|
||||||
const connector_id = result_source?._source?.connector?.id;
|
const connector_id = result_source?._source?.connector?.id;
|
||||||
|
|
||||||
const result_connector = connector_data.find(
|
const result_connector = connector_data[endpoint_http]?.find(
|
||||||
(data: any) => data._source.id === connector_id
|
(data: any) => data._source.id === connector_id
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -132,7 +134,7 @@ export const DocumentList: React.FC<DocumentListProps> = ({
|
|||||||
return file_efault_img;
|
return file_efault_img;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedIcon?.includes("http")) {
|
if (selectedIcon?.startsWith("http://") || selectedIcon?.startsWith("https://")) {
|
||||||
return selectedIcon;
|
return selectedIcon;
|
||||||
} else {
|
} else {
|
||||||
return endpoint_http + selectedIcon;
|
return endpoint_http + selectedIcon;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ function DropdownList({ selected, suggests }: DropdownListProps) {
|
|||||||
try {
|
try {
|
||||||
if (isTauri()) {
|
if (isTauri()) {
|
||||||
await open(url);
|
await open(url);
|
||||||
console.log("URL opened in default browser");
|
// console.log("URL opened in default browser");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to open URL:", error);
|
console.error("Failed to open URL:", error);
|
||||||
@@ -27,12 +27,12 @@ function DropdownList({ selected, suggests }: DropdownListProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
console.log(
|
// console.log(
|
||||||
"handleKeyDown",
|
// "handleKeyDown",
|
||||||
e.key,
|
// e.key,
|
||||||
showIndex,
|
// showIndex,
|
||||||
e.key >= "0" && e.key <= "9" && showIndex
|
// e.key >= "0" && e.key <= "9" && showIndex
|
||||||
);
|
// );
|
||||||
if (!suggests.length) return;
|
if (!suggests.length) return;
|
||||||
|
|
||||||
if (e.key === "ArrowUp") {
|
if (e.key === "ArrowUp") {
|
||||||
@@ -51,7 +51,7 @@ function DropdownList({ selected, suggests }: DropdownListProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (e.key === "Enter" && selectedItem !== null) {
|
if (e.key === "Enter" && selectedItem !== null) {
|
||||||
console.log("Enter key pressed", selectedItem);
|
// console.log("Enter key pressed", selectedItem);
|
||||||
const item = suggests[selectedItem];
|
const item = suggests[selectedItem];
|
||||||
if (item?._source?.url) {
|
if (item?._source?.url) {
|
||||||
handleOpenURL(item?._source?.url);
|
handleOpenURL(item?._source?.url);
|
||||||
@@ -61,7 +61,7 @@ function DropdownList({ selected, suggests }: DropdownListProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (e.key >= "0" && e.key <= "9" && showIndex) {
|
if (e.key >= "0" && e.key <= "9" && showIndex) {
|
||||||
console.log(`number ${e.key}`);
|
// console.log(`number ${e.key}`);
|
||||||
const item = suggests[parseInt(e.key, 10)];
|
const item = suggests[parseInt(e.key, 10)];
|
||||||
if (item?._source?.url) {
|
if (item?._source?.url) {
|
||||||
handleOpenURL(item?._source?.url);
|
handleOpenURL(item?._source?.url);
|
||||||
@@ -72,7 +72,7 @@ function DropdownList({ selected, suggests }: DropdownListProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyUp = (e: KeyboardEvent) => {
|
const handleKeyUp = (e: KeyboardEvent) => {
|
||||||
console.log("handleKeyUp", e.key);
|
// console.log("handleKeyUp", e.key);
|
||||||
if (!suggests.length) return;
|
if (!suggests.length) return;
|
||||||
|
|
||||||
if (!e.metaKey) {
|
if (!e.metaKey) {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export default function Account() {
|
|||||||
|
|
||||||
const setupAuthListener = async () => {
|
const setupAuthListener = async () => {
|
||||||
try {
|
try {
|
||||||
if (!auth) {
|
if (!(auth && auth[endpoint_http])) {
|
||||||
// Replace the current route with signin
|
// Replace the current route with signin
|
||||||
// navigate("/signin", { replace: true });
|
// navigate("/signin", { replace: true });
|
||||||
}
|
}
|
||||||
@@ -55,7 +55,7 @@ export default function Account() {
|
|||||||
|
|
||||||
cleanup();
|
cleanup();
|
||||||
};
|
};
|
||||||
}, [auth]);
|
}, [JSON.stringify(auth)]);
|
||||||
|
|
||||||
async function signIn() {
|
async function signIn() {
|
||||||
let res: (url: URL) => void;
|
let res: (url: URL) => void;
|
||||||
@@ -114,7 +114,7 @@ export default function Account() {
|
|||||||
user_id,
|
user_id,
|
||||||
expires,
|
expires,
|
||||||
plan: { upgraded: false, last_checked: 0 },
|
plan: { upgraded: false, last_checked: 0 },
|
||||||
});
|
}, endpoint_http);
|
||||||
|
|
||||||
getCurrentWindow()
|
getCurrentWindow()
|
||||||
.setFocus()
|
.setFocus()
|
||||||
@@ -123,7 +123,7 @@ export default function Account() {
|
|||||||
return navigate("/");
|
return navigate("/");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Sign in failed:", error);
|
console.error("Sign in failed:", error);
|
||||||
await setAuth(undefined);
|
await setAuth(undefined, endpoint_http);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
Sun,
|
Sun,
|
||||||
Power,
|
Power,
|
||||||
Tags,
|
Tags,
|
||||||
|
// Trash2,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { isTauri, invoke } from "@tauri-apps/api/core";
|
import { isTauri, invoke } from "@tauri-apps/api/core";
|
||||||
import {
|
import {
|
||||||
@@ -21,6 +22,8 @@ import { Shortcut } from "./shortcut";
|
|||||||
import { useShortcutEditor } from "@/hooks/useShortcutEditor";
|
import { useShortcutEditor } from "@/hooks/useShortcutEditor";
|
||||||
import { ThemeOption } from "./index2";
|
import { ThemeOption } from "./index2";
|
||||||
import { useAppStore } from "@/stores/appStore";
|
import { useAppStore } from "@/stores/appStore";
|
||||||
|
// import { useAuthStore } from "@/stores/authStore";
|
||||||
|
// import { useConnectStore } from "@/stores/connectStore";
|
||||||
|
|
||||||
export default function GeneralSettings() {
|
export default function GeneralSettings() {
|
||||||
const [launchAtLogin, setLaunchAtLogin] = useState(true);
|
const [launchAtLogin, setLaunchAtLogin] = useState(true);
|
||||||
@@ -28,6 +31,10 @@ export default function GeneralSettings() {
|
|||||||
const showTooltip = useAppStore((state) => state.showTooltip);
|
const showTooltip = useAppStore((state) => state.showTooltip);
|
||||||
const setShowTooltip = useAppStore((state) => state.setShowTooltip);
|
const setShowTooltip = useAppStore((state) => state.setShowTooltip);
|
||||||
|
|
||||||
|
// const setAuth = useAuthStore((state) => state.setAuth);
|
||||||
|
// const setUserInfo = useAuthStore((state) => state.setUserInfo);
|
||||||
|
// const endpoint = useAppStore((state) => state.endpoint);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchAutoStartStatus = async () => {
|
const fetchAutoStartStatus = async () => {
|
||||||
if (isTauri()) {
|
if (isTauri()) {
|
||||||
@@ -84,13 +91,13 @@ export default function GeneralSettings() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const changeShortcut = (key: Shortcut) => {
|
const changeShortcut = (key: Shortcut) => {
|
||||||
setShortcut(key)
|
setShortcut(key);
|
||||||
//
|
//
|
||||||
if (key.length === 0) return;
|
if (key.length === 0) return;
|
||||||
invoke("change_shortcut", { key: key?.join("+") }).catch((err) => {
|
invoke("change_shortcut", { key: key?.join("+") }).catch((err) => {
|
||||||
console.error("Failed to save hotkey:", err);
|
console.error("Failed to save hotkey:", err);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const { isEditing, currentKeys, startEditing, saveShortcut, cancelEditing } =
|
const { isEditing, currentKeys, startEditing, saveShortcut, cancelEditing } =
|
||||||
useShortcutEditor(shortcut, changeShortcut);
|
useShortcutEditor(shortcut, changeShortcut);
|
||||||
@@ -115,6 +122,15 @@ export default function GeneralSettings() {
|
|||||||
saveShortcut();
|
saveShortcut();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// const clearAllCache = useCallback(() => {
|
||||||
|
// setAuth(undefined, endpoint);
|
||||||
|
// setUserInfo({}, endpoint);
|
||||||
|
|
||||||
|
// useConnectStore.persist.clearStorage();
|
||||||
|
|
||||||
|
// useAppStore.persist.clearStorage();
|
||||||
|
// }, [endpoint]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
<div>
|
<div>
|
||||||
@@ -205,6 +221,23 @@ export default function GeneralSettings() {
|
|||||||
Manage Favorites
|
Manage Favorites
|
||||||
</button>
|
</button>
|
||||||
</SettingsItem> */}
|
</SettingsItem> */}
|
||||||
|
|
||||||
|
{/* <SettingsItem
|
||||||
|
icon={Trash2}
|
||||||
|
title="Clear Cache"
|
||||||
|
description="Clear cached data and settings"
|
||||||
|
>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button
|
||||||
|
onClick={clearAllCache}
|
||||||
|
className=" px-4 py-2 text-red-500 hover:text-red-600 dark:text-red-400 dark:hover:text-red-300 border border-red-200 dark:border-red-800 rounded-md hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors"
|
||||||
|
>
|
||||||
|
Clear All Cache
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SettingsItem> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@headlessui/react";
|
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@headlessui/react";
|
||||||
import { Settings, Puzzle, User, Settings2, Info } from "lucide-react";
|
import { Settings, Puzzle, Settings2, Info, Server } from "lucide-react";
|
||||||
import { useSearchParams } from "react-router-dom";
|
import { useSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
import SettingsPanel from "./SettingsPanel";
|
import SettingsPanel from "./SettingsPanel";
|
||||||
@@ -25,7 +25,7 @@ function SettingsPage() {
|
|||||||
const tabs = [
|
const tabs = [
|
||||||
{ name: "General", icon: Settings },
|
{ name: "General", icon: Settings },
|
||||||
{ name: "Extensions", icon: Puzzle },
|
{ name: "Extensions", icon: Puzzle },
|
||||||
{ name: "Connect", icon: User },
|
{ name: "Connect", icon: Server },
|
||||||
{ name: "Advanced", icon: Settings2 },
|
{ name: "Advanced", icon: Settings2 },
|
||||||
{ name: "About", icon: Info },
|
{ name: "About", icon: Info },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useRouteError } from "react-router-dom";
|
import { useRouteError } from "react-router-dom";
|
||||||
|
|
||||||
import errorImg from "./assets/error_page.png";
|
import errorImg from "./assets/error_page.png";
|
||||||
|
import ApiDetails from "@/components/AppAI/ApiDetails";
|
||||||
|
|
||||||
export default function ErrorPage() {
|
export default function ErrorPage() {
|
||||||
const error: any = useRouteError();
|
const error: any = useRouteError();
|
||||||
@@ -9,14 +10,21 @@ export default function ErrorPage() {
|
|||||||
return (
|
return (
|
||||||
<div className="w-full h-screen bg-white shadow-[0px_16px_32px_0px_rgba(0,0,0,0.4)] rounded-xl border-[2px] border-[#E6E6E6] m-auto">
|
<div className="w-full h-screen bg-white shadow-[0px_16px_32px_0px_rgba(0,0,0,0.4)] rounded-xl border-[2px] border-[#E6E6E6] m-auto">
|
||||||
<div className="flex flex-col justify-center items-center">
|
<div className="flex flex-col justify-center items-center">
|
||||||
<img src={errorImg} alt="error-page" className="w-[221px] h-[154px] mb-8 mt-[72px]"/>
|
<img
|
||||||
|
src={errorImg}
|
||||||
|
alt="error-page"
|
||||||
|
className="w-[221px] h-[154px] mb-8 mt-[72px]"
|
||||||
|
/>
|
||||||
<div className="w-[380px] h-[46px] px-5 font-normal text-base text-[rgba(0,0,0,0.85)] leading-[25px] text-center mb-4">
|
<div className="w-[380px] h-[46px] px-5 font-normal text-base text-[rgba(0,0,0,0.85)] leading-[25px] text-center mb-4">
|
||||||
Sorry, there is an error in your Coco App. Please contact the administrator.
|
Sorry, there is an error in your Coco App. Please contact the
|
||||||
|
administrator.
|
||||||
</div>
|
</div>
|
||||||
<div className="w-[380px] h-[45px] font-normal text-[10px] text-[rgba(135,135,135,0.85)] leading-[16px] text-center">
|
<div className="w-[380px] h-[45px] font-normal text-[10px] text-[rgba(135,135,135,0.85)] leading-[16px] text-center">
|
||||||
<i>{error.statusText || error.message}</i>
|
<i>{error.statusText || error.message}</i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ApiDetails />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -25,7 +33,8 @@ export default function ErrorPage() {
|
|||||||
<div className="error-content">
|
<div className="error-content">
|
||||||
<h1 className="error-title">Oops!</h1>
|
<h1 className="error-title">Oops!</h1>
|
||||||
<p className="error-message">
|
<p className="error-message">
|
||||||
Sorry, there is an error in your Coco App. Please contact the administrator.
|
Sorry, there is an error in your Coco App. Please contact the
|
||||||
|
administrator.
|
||||||
</p>
|
</p>
|
||||||
<p className="error-details">
|
<p className="error-details">
|
||||||
<i>{error.statusText || error.message}</i>
|
<i>{error.statusText || error.message}</i>
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ export default function useSettingsWindow() {
|
|||||||
const url = tab ? `/ui/settings?tab=${tab}` : `/ui/settings`;
|
const url = tab ? `/ui/settings?tab=${tab}` : `/ui/settings`;
|
||||||
const options: CreateWindowOptions = {
|
const options: CreateWindowOptions = {
|
||||||
label: "settings",
|
label: "settings",
|
||||||
title: "Settings Window",
|
title: "Coco Settings",
|
||||||
width: 1000,
|
width: 1000,
|
||||||
height: 600,
|
height: 700,
|
||||||
alwaysOnTop: false,
|
alwaysOnTop: false,
|
||||||
shadow: true,
|
shadow: true,
|
||||||
decorations: true,
|
decorations: true,
|
||||||
@@ -29,6 +29,7 @@ export default function useSettingsWindow() {
|
|||||||
minimizable: false,
|
minimizable: false,
|
||||||
maximizable: false,
|
maximizable: false,
|
||||||
dragDropEnabled: true,
|
dragDropEnabled: true,
|
||||||
|
resizable: false,
|
||||||
center: true,
|
center: true,
|
||||||
url,
|
url,
|
||||||
};
|
};
|
||||||
@@ -45,6 +46,21 @@ export default function useSettingsWindow() {
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleKeyDown = useCallback(
|
||||||
|
(e: KeyboardEvent) => {
|
||||||
|
if (e.metaKey) {
|
||||||
|
switch (e.code) {
|
||||||
|
case "Comma":
|
||||||
|
openSettingsWindow()
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[openSettingsWindow]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unlisten = listen("open_settings", (event) => {
|
const unlisten = listen("open_settings", (event) => {
|
||||||
console.log("open_settings event received:", event);
|
console.log("open_settings event received:", event);
|
||||||
@@ -52,11 +68,13 @@ export default function useSettingsWindow() {
|
|||||||
|
|
||||||
openSettingsWindow(tab);
|
openSettingsWindow(tab);
|
||||||
});
|
});
|
||||||
|
window.addEventListener("keydown", handleKeyDown);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unlisten.then((fn) => fn());
|
unlisten.then((fn) => fn());
|
||||||
|
window.addEventListener("keydown", handleKeyDown);
|
||||||
};
|
};
|
||||||
}, []);
|
}, [openSettingsWindow, handleKeyDown]);
|
||||||
|
|
||||||
return { openSettingsWindow };
|
return { openSettingsWindow };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,9 +62,13 @@
|
|||||||
@apply box-border border-[--border];
|
@apply box-border border-[--border];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html{
|
||||||
|
@apply h-full;
|
||||||
|
}
|
||||||
|
|
||||||
body,
|
body,
|
||||||
#root {
|
#root {
|
||||||
@apply text-gray-900 antialiased;
|
@apply h-full text-gray-900 antialiased;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark body,
|
.dark body,
|
||||||
|
|||||||
@@ -10,14 +10,17 @@ import { useAppStore } from "@/stores/appStore";
|
|||||||
import { useAuthStore } from "@/stores/authStore";
|
import { useAuthStore } from "@/stores/authStore";
|
||||||
import { tauriFetch } from "@/api/tauriFetchClient";
|
import { tauriFetch } from "@/api/tauriFetchClient";
|
||||||
import ApiDetails from "@/components/AppAI/ApiDetails";
|
import ApiDetails from "@/components/AppAI/ApiDetails";
|
||||||
|
import { useConnectStore } from "@/stores/connectStore";
|
||||||
|
|
||||||
export default function DesktopApp() {
|
export default function DesktopApp() {
|
||||||
const initializeListeners = useAppStore((state) => state.initializeListeners);
|
const initializeListeners = useAppStore((state) => state.initializeListeners);
|
||||||
const initializeListeners_auth = useAuthStore(
|
const initializeListeners_auth = useAuthStore(
|
||||||
(state) => state.initializeListeners
|
(state) => state.initializeListeners
|
||||||
);
|
);
|
||||||
const setConnectorData = useAppStore((state) => state.setConnectorData);
|
const setConnectorData = useConnectStore((state) => state.setConnectorData);
|
||||||
const setDatasourceData = useAppStore((state) => state.setDatasourceData);
|
const setDatasourceData = useConnectStore((state) => state.setDatasourceData);
|
||||||
|
|
||||||
|
const endpoint_http = useAppStore((state) => state.endpoint_http);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initializeListeners();
|
initializeListeners();
|
||||||
@@ -35,7 +38,7 @@ export default function DesktopApp() {
|
|||||||
});
|
});
|
||||||
console.log("connector", response);
|
console.log("connector", response);
|
||||||
const data = response.data?.hits?.hits || [];
|
const data = response.data?.hits?.hits || [];
|
||||||
setConnectorData(data);
|
setConnectorData(data, endpoint_http);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to fetch user data:", error);
|
console.error("Failed to fetch user data:", error);
|
||||||
}
|
}
|
||||||
@@ -49,7 +52,7 @@ export default function DesktopApp() {
|
|||||||
});
|
});
|
||||||
console.log("datasource", response);
|
console.log("datasource", response);
|
||||||
const data = response.data?.hits?.hits || [];
|
const data = response.data?.hits?.hits || [];
|
||||||
setDatasourceData(data);
|
setDatasourceData(data, endpoint_http);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to fetch user data:", error);
|
console.error("Failed to fetch user data:", error);
|
||||||
}
|
}
|
||||||
@@ -91,7 +94,7 @@ export default function DesktopApp() {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
className={`w-[680px] h-[590px] m-auto rounded-xl overflow-hidden relative border border-[#E6E6E6] dark:border-[#272626] ${
|
className={`w-full h-full m-auto rounded-xl overflow-hidden relative border border-[#E6E6E6] dark:border-[#272626] ${
|
||||||
isTransitioned
|
isTransitioned
|
||||||
? "bg-chat_bg_light dark:bg-chat_bg_dark"
|
? "bg-chat_bg_light dark:bg-chat_bg_dark"
|
||||||
: "bg-search_bg_light dark:bg-search_bg_dark"
|
: "bg-search_bg_light dark:bg-search_bg_dark"
|
||||||
|
|||||||
@@ -15,10 +15,6 @@ export type IAppStore = {
|
|||||||
endpoint_http: string,
|
endpoint_http: string,
|
||||||
endpoint_websocket: string,
|
endpoint_websocket: string,
|
||||||
setEndpoint: (endpoint: AppEndpoint) => void,
|
setEndpoint: (endpoint: AppEndpoint) => void,
|
||||||
connector_data: any[],
|
|
||||||
setConnectorData: (connector_data: any[]) => void,
|
|
||||||
datasourceData: any[],
|
|
||||||
setDatasourceData: (datasourceData: any[]) => void,
|
|
||||||
initializeListeners: () => void;
|
initializeListeners: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -53,18 +49,6 @@ export const useAppStore = create<IAppStore>()(
|
|||||||
endpoint_websocket
|
endpoint_websocket
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
connector_data: [],
|
|
||||||
setConnectorData: async (connector_data: any[]) => {
|
|
||||||
set({
|
|
||||||
connector_data
|
|
||||||
});
|
|
||||||
},
|
|
||||||
datasourceData: [],
|
|
||||||
setDatasourceData: async (datasourceData: any[]) => {
|
|
||||||
set({
|
|
||||||
datasourceData
|
|
||||||
});
|
|
||||||
},
|
|
||||||
initializeListeners: () => {
|
initializeListeners: () => {
|
||||||
listen(ENDPOINT_CHANGE_EVENT, (event: any) => {
|
listen(ENDPOINT_CHANGE_EVENT, (event: any) => {
|
||||||
const { endpoint, endpoint_http, endpoint_websocket } = event.payload;
|
const { endpoint, endpoint_http, endpoint_websocket } = event.payload;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { persist } from "zustand/middleware";
|
import { persist } from "zustand/middleware";
|
||||||
import { listen, emit } from '@tauri-apps/api/event';
|
import { listen, emit } from '@tauri-apps/api/event';
|
||||||
|
import { produce } from 'immer'
|
||||||
|
|
||||||
const AUTH_CHANGE_EVENT = 'auth-changed';
|
const AUTH_CHANGE_EVENT = 'auth-changed';
|
||||||
const USERINFO_CHANGE_EVENT = 'userInfo-changed';
|
const USERINFO_CHANGE_EVENT = 'userInfo-changed';
|
||||||
@@ -10,19 +11,27 @@ export type Plan = {
|
|||||||
last_checked: number;
|
last_checked: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AuthStore = {
|
export type AuthProp = {
|
||||||
token: string;
|
token: string;
|
||||||
user_id?: string | null;
|
user_id?: string | null;
|
||||||
expires?: number;
|
expires?: number;
|
||||||
plan?: Plan | null;
|
plan?: Plan | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type AuthMapProp = {
|
||||||
|
[key: string]: AuthProp;
|
||||||
|
};
|
||||||
|
|
||||||
|
type userInfoMapProp = {
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
|
||||||
export type IAuthStore = {
|
export type IAuthStore = {
|
||||||
[x: string]: any;
|
[x: string]: any;
|
||||||
auth: AuthStore | undefined;
|
auth: AuthMapProp | undefined;
|
||||||
userInfo: any;
|
userInfo: userInfoMapProp;
|
||||||
setAuth: (auth: AuthStore | undefined) => void;
|
setAuth: (auth: AuthProp | undefined, key: string) => void;
|
||||||
resetAuth: () => void;
|
resetAuth: (key: string) => void;
|
||||||
initializeListeners: () => void;
|
initializeListeners: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -31,24 +40,43 @@ export const useAuthStore = create<IAuthStore>()(
|
|||||||
(set) => ({
|
(set) => ({
|
||||||
auth: undefined,
|
auth: undefined,
|
||||||
userInfo: {},
|
userInfo: {},
|
||||||
setAuth: async (auth) => {
|
setAuth: async (auth, key) => {
|
||||||
set({ auth })
|
set(
|
||||||
await emit(AUTH_CHANGE_EVENT, {
|
produce((draft) => {
|
||||||
auth
|
draft.auth[key] = auth
|
||||||
});
|
})
|
||||||
},
|
);
|
||||||
resetAuth: async () => {
|
|
||||||
set({ auth: undefined })
|
|
||||||
|
|
||||||
await emit(AUTH_CHANGE_EVENT, {
|
await emit(AUTH_CHANGE_EVENT, {
|
||||||
auth: undefined
|
auth: {
|
||||||
|
[key]: auth
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
setUserInfo: async (userInfo: any) => {
|
resetAuth: async (key: string) => {
|
||||||
set({ userInfo })
|
set(
|
||||||
|
produce((draft) => {
|
||||||
|
draft.auth[key] = undefined
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
await emit(AUTH_CHANGE_EVENT, {
|
||||||
|
auth: {
|
||||||
|
[key]: undefined
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
setUserInfo: async (userInfo: any, key: string) => {
|
||||||
|
set(
|
||||||
|
produce((draft) => {
|
||||||
|
draft.userInfo[key] = userInfo
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
await emit(USERINFO_CHANGE_EVENT, {
|
await emit(USERINFO_CHANGE_EVENT, {
|
||||||
userInfo
|
userInfo: {
|
||||||
|
[key]: userInfo
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
initializeListeners: () => {
|
initializeListeners: () => {
|
||||||
|
|||||||
124
src/stores/connectStore.ts
Normal file
124
src/stores/connectStore.ts
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import { create } from "zustand";
|
||||||
|
import { persist } from "zustand/middleware";
|
||||||
|
import { produce } from 'immer'
|
||||||
|
|
||||||
|
type keyArrayObject = {
|
||||||
|
[key: string]: any[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IConnectStore = {
|
||||||
|
defaultService: any;
|
||||||
|
setDefaultService: (service: any) => void;
|
||||||
|
otherServices: any[];
|
||||||
|
addOtherServices: (service: any) => void;
|
||||||
|
deleteOtherService: (service: any) => void;
|
||||||
|
currentService: any;
|
||||||
|
setCurrentService: (service: any) => void;
|
||||||
|
connector_data: keyArrayObject,
|
||||||
|
setConnectorData: (connector_data: any[], key: string) => void,
|
||||||
|
datasourceData: keyArrayObject,
|
||||||
|
setDatasourceData: (datasourceData: any[], key: string) => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useConnectStore = create<IConnectStore>()(
|
||||||
|
persist(
|
||||||
|
(set) => ({
|
||||||
|
defaultService: {
|
||||||
|
"name": "Coco Cloud",
|
||||||
|
"endpoint": "https://coco.infini.cloud/",
|
||||||
|
"provider": {
|
||||||
|
"name": "INFINI Labs",
|
||||||
|
"icon": "https://coco.infini.cloud/icon.png",
|
||||||
|
"website": "http://infinilabs.com",
|
||||||
|
"eula": "http://infinilabs.com/eula.txt",
|
||||||
|
"privacy_policy": "http://infinilabs.com/privacy_policy.txt",
|
||||||
|
"banner": "https://coco.infini.cloud/banner.jpg",
|
||||||
|
"description": "Coco AI Server - Search, Connect, Collaborate, AI-powered enterprise search, all in one space."
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"number": "1.0.0_SNAPSHOT"
|
||||||
|
},
|
||||||
|
"updated": "2025-01-24T12:12:17.326286927+08:00",
|
||||||
|
"public": false,
|
||||||
|
"auth_provider": {
|
||||||
|
"sso": {
|
||||||
|
"url": "https://coco.infini.cloud/sso/login/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setDefaultService: (defaultService: any) => set(
|
||||||
|
produce((draft) => {
|
||||||
|
draft.defaultService = defaultService
|
||||||
|
})
|
||||||
|
),
|
||||||
|
otherServices: [],
|
||||||
|
addOtherServices: (otherService: any) => {
|
||||||
|
set(produce((draft) => {
|
||||||
|
draft.otherServices.push(otherService);
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
deleteOtherService: (service: any) => {
|
||||||
|
set(produce((draft) => {
|
||||||
|
draft.otherServices = draft.otherServices.filter(
|
||||||
|
(item: any) => item.endpoint !== service.endpoint
|
||||||
|
);
|
||||||
|
draft.currentService = draft.defaultService;
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
currentService: {
|
||||||
|
"name": "Coco Cloud",
|
||||||
|
"endpoint": "https://coco.infini.cloud/",
|
||||||
|
"provider": {
|
||||||
|
"name": "INFINI Labs",
|
||||||
|
"icon": "https://coco.infini.cloud/icon.png",
|
||||||
|
"website": "http://infinilabs.com",
|
||||||
|
"eula": "http://infinilabs.com/eula.txt",
|
||||||
|
"privacy_policy": "http://infinilabs.com/privacy_policy.txt",
|
||||||
|
"banner": "https://coco.infini.cloud/banner.jpg",
|
||||||
|
"description": "Coco AI Server - Search, Connect, Collaborate, AI-powered enterprise search, all in one space."
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"number": "1.0.0_SNAPSHOT"
|
||||||
|
},
|
||||||
|
"updated": "2025-01-24T12:12:17.326286927+08:00",
|
||||||
|
"public": false,
|
||||||
|
"auth_provider": {
|
||||||
|
"sso": {
|
||||||
|
"url": "https://coco.infini.cloud/sso/login/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setCurrentService: (currentService: any) => {
|
||||||
|
set(produce((draft) => {
|
||||||
|
draft.currentService = currentService;
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
connector_data: {},
|
||||||
|
setConnectorData: async (connector_data: any[], key: string) => {
|
||||||
|
set(
|
||||||
|
produce((draft) => {
|
||||||
|
draft.connector_data[key] = connector_data
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
datasourceData: {},
|
||||||
|
setDatasourceData: async (datasourceData: any[], key: string) => {
|
||||||
|
set(
|
||||||
|
produce((draft) => {
|
||||||
|
draft.datasourceData[key] = datasourceData
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: "connect-store",
|
||||||
|
partialize: (state) => ({
|
||||||
|
defaultService: state.defaultService,
|
||||||
|
otherServices: state.otherServices,
|
||||||
|
currentService: state.currentService,
|
||||||
|
connector_data: state.connector_data,
|
||||||
|
datasourceData: state.datasourceData,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user