@lobehub/lobehub 2.0.0-next.233 → 2.0.0-next.235
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/e2e.yml +6 -12
- package/.github/workflows/test.yml +3 -3
- package/CHANGELOG.md +59 -0
- package/CLAUDE.md +1 -1
- package/changelog/v1.json +18 -0
- package/docs/development/basic/feature-development.mdx +4 -5
- package/docs/development/basic/feature-development.zh-CN.mdx +4 -5
- package/e2e/README.md +6 -6
- package/e2e/src/features/community/detail-pages.feature +9 -9
- package/e2e/src/features/community/interactions.feature +13 -13
- package/e2e/src/features/community/smoke.feature +6 -6
- package/e2e/src/steps/agent/conversation-mgmt.steps.ts +196 -25
- package/e2e/src/steps/agent/conversation.steps.ts +58 -0
- package/e2e/src/steps/agent/message-ops.steps.ts +20 -15
- package/e2e/src/steps/community/detail-pages.steps.ts +60 -19
- package/e2e/src/steps/community/interactions.steps.ts +145 -32
- package/e2e/src/steps/hooks.ts +12 -2
- package/locales/ar/components.json +1 -0
- package/locales/ar/file.json +4 -0
- package/locales/ar/models.json +29 -0
- package/locales/ar/setting.json +7 -0
- package/locales/bg-BG/components.json +1 -0
- package/locales/bg-BG/file.json +4 -0
- package/locales/bg-BG/models.json +1 -0
- package/locales/bg-BG/setting.json +7 -0
- package/locales/de-DE/components.json +1 -0
- package/locales/de-DE/file.json +4 -0
- package/locales/de-DE/models.json +29 -0
- package/locales/de-DE/setting.json +7 -0
- package/locales/en-US/common.json +0 -1
- package/locales/en-US/components.json +1 -0
- package/locales/en-US/file.json +4 -0
- package/locales/en-US/models.json +1 -0
- package/locales/en-US/setting.json +3 -0
- package/locales/es-ES/components.json +1 -0
- package/locales/es-ES/file.json +4 -0
- package/locales/es-ES/models.json +43 -0
- package/locales/es-ES/setting.json +7 -0
- package/locales/fa-IR/components.json +1 -0
- package/locales/fa-IR/file.json +4 -0
- package/locales/fa-IR/models.json +54 -0
- package/locales/fa-IR/setting.json +7 -0
- package/locales/fr-FR/components.json +1 -0
- package/locales/fr-FR/file.json +4 -0
- package/locales/fr-FR/models.json +31 -0
- package/locales/fr-FR/setting.json +7 -0
- package/locales/it-IT/components.json +1 -0
- package/locales/it-IT/file.json +4 -0
- package/locales/it-IT/models.json +43 -0
- package/locales/it-IT/setting.json +7 -0
- package/locales/ja-JP/components.json +1 -0
- package/locales/ja-JP/file.json +4 -0
- package/locales/ja-JP/models.json +28 -0
- package/locales/ja-JP/setting.json +7 -0
- package/locales/ko-KR/components.json +1 -0
- package/locales/ko-KR/file.json +4 -0
- package/locales/ko-KR/models.json +37 -0
- package/locales/ko-KR/setting.json +7 -0
- package/locales/nl-NL/components.json +1 -0
- package/locales/nl-NL/file.json +4 -0
- package/locales/nl-NL/models.json +13 -0
- package/locales/nl-NL/setting.json +7 -0
- package/locales/pl-PL/components.json +1 -0
- package/locales/pl-PL/file.json +4 -0
- package/locales/pl-PL/models.json +13 -0
- package/locales/pl-PL/setting.json +7 -0
- package/locales/pt-BR/components.json +1 -0
- package/locales/pt-BR/file.json +4 -0
- package/locales/pt-BR/models.json +29 -0
- package/locales/pt-BR/setting.json +7 -0
- package/locales/ru-RU/components.json +1 -0
- package/locales/ru-RU/file.json +4 -0
- package/locales/ru-RU/models.json +1 -0
- package/locales/ru-RU/setting.json +7 -0
- package/locales/tr-TR/components.json +1 -0
- package/locales/tr-TR/file.json +4 -0
- package/locales/tr-TR/models.json +29 -0
- package/locales/tr-TR/setting.json +7 -0
- package/locales/vi-VN/components.json +1 -0
- package/locales/vi-VN/file.json +4 -0
- package/locales/vi-VN/models.json +1 -0
- package/locales/vi-VN/setting.json +7 -0
- package/locales/zh-CN/file.json +4 -0
- package/locales/zh-CN/models.json +46 -0
- package/locales/zh-CN/setting.json +3 -0
- package/locales/zh-TW/components.json +1 -0
- package/locales/zh-TW/file.json +4 -0
- package/locales/zh-TW/models.json +35 -0
- package/locales/zh-TW/setting.json +7 -0
- package/package.json +5 -5
- package/packages/const/src/index.ts +1 -0
- package/packages/const/src/lobehubSkill.ts +55 -0
- package/packages/types/package.json +1 -1
- package/packages/types/src/files/upload.ts +11 -1
- package/packages/types/src/message/common/tools.ts +1 -1
- package/packages/types/src/serverConfig.ts +1 -0
- package/public/not-compatible.html +1296 -0
- package/src/app/[variants]/(main)/resource/features/FileDetail.tsx +20 -12
- package/src/app/[variants]/(main)/resource/features/modal/FullscreenModal.tsx +2 -4
- package/src/app/[variants]/layout.tsx +50 -1
- package/src/features/ChatInput/ActionBar/Tools/LobehubSkillServerItem.tsx +304 -0
- package/src/features/ChatInput/ActionBar/Tools/useControls.tsx +74 -10
- package/src/features/Conversation/Messages/AssistantGroup/Tool/Inspector/ToolTitle.tsx +9 -0
- package/src/features/FileViewer/Renderer/Code/index.tsx +224 -0
- package/src/features/FileViewer/Renderer/Image/index.tsx +8 -1
- package/src/features/FileViewer/Renderer/PDF/index.tsx +3 -1
- package/src/features/FileViewer/Renderer/PDF/style.ts +2 -1
- package/src/features/FileViewer/index.tsx +135 -24
- package/src/features/PageEditor/EditorCanvas/useSlashItems.tsx +7 -4
- package/src/features/PageEditor/store/initialState.ts +2 -1
- package/src/features/ResourceManager/components/Editor/FileContent.tsx +1 -4
- package/src/features/ResourceManager/components/Editor/FileCopilot.tsx +64 -0
- package/src/features/ResourceManager/components/Editor/index.tsx +98 -31
- package/src/features/ResourceManager/components/Explorer/ItemDropdown/useFileItemDropdown.tsx +3 -2
- package/src/features/ResourceManager/components/Explorer/ListView/ColumnResizeHandle.tsx +119 -0
- package/src/features/ResourceManager/components/Explorer/ListView/ListItem/index.tsx +67 -22
- package/src/features/ResourceManager/components/Explorer/ListView/Skeleton.tsx +46 -11
- package/src/features/ResourceManager/components/Explorer/ListView/index.tsx +140 -81
- package/src/features/ResourceManager/components/Explorer/ToolBar/SortDropdown.tsx +20 -12
- package/src/features/ResourceManager/components/Explorer/ToolBar/ViewSwitcher.tsx +18 -10
- package/src/features/ResourceManager/components/UploadDock/Item.tsx +38 -6
- package/src/features/ResourceManager/components/UploadDock/index.tsx +62 -41
- package/src/features/ResourceManager/index.tsx +1 -0
- package/src/helpers/toolEngineering/index.test.ts +3 -0
- package/src/helpers/toolEngineering/index.ts +12 -1
- package/src/locales/default/file.ts +4 -0
- package/src/locales/default/setting.ts +3 -0
- package/src/server/globalConfig/index.ts +1 -0
- package/src/server/modules/ModelRuntime/index.test.ts +214 -1
- package/src/server/modules/ModelRuntime/index.ts +43 -7
- package/src/server/routers/lambda/_helpers/resolveContext.ts +8 -8
- package/src/server/routers/lambda/agent.ts +1 -1
- package/src/server/routers/lambda/aiModel.ts +1 -1
- package/src/server/routers/lambda/comfyui.ts +1 -1
- package/src/server/routers/lambda/document.ts +44 -0
- package/src/server/routers/lambda/exporter.ts +1 -1
- package/src/server/routers/lambda/image.ts +13 -13
- package/src/server/routers/lambda/klavis.ts +10 -10
- package/src/server/routers/lambda/market/index.ts +6 -6
- package/src/server/routers/lambda/message.ts +2 -2
- package/src/server/routers/lambda/plugin.ts +1 -1
- package/src/server/routers/lambda/ragEval.ts +2 -2
- package/src/server/routers/lambda/topic.ts +3 -3
- package/src/server/routers/lambda/user.ts +10 -10
- package/src/server/routers/lambda/userMemories.ts +6 -6
- package/src/server/routers/tools/market.ts +261 -0
- package/src/server/services/document/index.ts +22 -0
- package/src/services/document/index.ts +4 -0
- package/src/services/upload.ts +22 -2
- package/src/store/chat/slices/plugin/actions/internals.ts +15 -2
- package/src/store/chat/slices/plugin/actions/pluginTypes.ts +104 -0
- package/src/store/file/slices/fileManager/action.test.ts +9 -3
- package/src/store/file/slices/fileManager/action.ts +165 -70
- package/src/store/file/slices/upload/action.ts +3 -0
- package/src/store/global/actions/general.ts +15 -0
- package/src/store/global/initialState.ts +13 -0
- package/src/store/serverConfig/selectors.ts +1 -0
- package/src/store/tool/initialState.ts +11 -2
- package/src/store/tool/selectors/index.ts +1 -0
- package/src/store/tool/selectors/tool.ts +3 -1
- package/src/store/tool/slices/lobehubSkillStore/action.ts +361 -0
- package/src/store/tool/slices/lobehubSkillStore/index.ts +4 -0
- package/src/store/tool/slices/lobehubSkillStore/initialState.ts +24 -0
- package/src/store/tool/slices/lobehubSkillStore/selectors.ts +145 -0
- package/src/store/tool/slices/lobehubSkillStore/types.ts +100 -0
- package/src/store/tool/store.ts +8 -2
- package/vitest.config.mts +1 -0
- package/src/features/FileViewer/Renderer/JavaScript/index.tsx +0 -66
- package/src/features/FileViewer/Renderer/TXT/index.tsx +0 -50
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import { ActionIcon, Flexbox, Icon, Tag } from '@lobehub/ui';
|
|
4
4
|
import { Descriptions, Divider } from 'antd';
|
|
5
|
-
import { cssVar } from 'antd-style';
|
|
6
5
|
import dayjs from 'dayjs';
|
|
7
6
|
import { BoltIcon, DownloadIcon } from 'lucide-react';
|
|
8
7
|
import { memo } from 'react';
|
|
@@ -12,10 +11,23 @@ import { type FileListItem } from '@/types/files';
|
|
|
12
11
|
import { downloadFile } from '@/utils/client/downloadFile';
|
|
13
12
|
import { formatSize } from '@/utils/format';
|
|
14
13
|
|
|
15
|
-
|
|
14
|
+
interface FileDetailProps extends FileListItem {
|
|
15
|
+
showDownloadButton?: boolean;
|
|
16
|
+
showTitle?: boolean;
|
|
17
|
+
}
|
|
16
18
|
|
|
17
|
-
const FileDetail = memo<
|
|
18
|
-
const {
|
|
19
|
+
const FileDetail = memo<FileDetailProps>((props) => {
|
|
20
|
+
const {
|
|
21
|
+
name,
|
|
22
|
+
embeddingStatus,
|
|
23
|
+
size,
|
|
24
|
+
createdAt,
|
|
25
|
+
updatedAt,
|
|
26
|
+
chunkCount,
|
|
27
|
+
url,
|
|
28
|
+
showDownloadButton = true,
|
|
29
|
+
showTitle = true,
|
|
30
|
+
} = props || {};
|
|
19
31
|
const { t } = useTranslation('file');
|
|
20
32
|
|
|
21
33
|
if (!props) return null;
|
|
@@ -64,16 +76,12 @@ const FileDetail = memo<FileListItem>((props) => {
|
|
|
64
76
|
];
|
|
65
77
|
|
|
66
78
|
return (
|
|
67
|
-
<Flexbox
|
|
68
|
-
padding={16}
|
|
69
|
-
style={{ borderInlineStart: `1px solid ${cssVar.colorSplit}` }}
|
|
70
|
-
width={DETAIL_PANEL_WIDTH}
|
|
71
|
-
>
|
|
79
|
+
<Flexbox>
|
|
72
80
|
<Descriptions
|
|
73
81
|
colon={false}
|
|
74
82
|
column={1}
|
|
75
83
|
extra={
|
|
76
|
-
|
|
84
|
+
showDownloadButton && url ? (
|
|
77
85
|
<ActionIcon
|
|
78
86
|
icon={DownloadIcon}
|
|
79
87
|
onClick={() => {
|
|
@@ -81,12 +89,12 @@ const FileDetail = memo<FileListItem>((props) => {
|
|
|
81
89
|
}}
|
|
82
90
|
title={t('download', { ns: 'common' })}
|
|
83
91
|
/>
|
|
84
|
-
)
|
|
92
|
+
) : undefined
|
|
85
93
|
}
|
|
86
94
|
items={items}
|
|
87
95
|
labelStyle={{ width: 120 }}
|
|
88
96
|
size={'small'}
|
|
89
|
-
title={t('detail.basic.title')}
|
|
97
|
+
title={showTitle ? t('detail.basic.title') : undefined}
|
|
90
98
|
/>
|
|
91
99
|
<Divider />
|
|
92
100
|
<Descriptions
|
|
@@ -5,8 +5,6 @@ import { ConfigProvider } from 'antd';
|
|
|
5
5
|
import { createStaticStyles, cx } from 'antd-style';
|
|
6
6
|
import { type ReactNode, useCallback, useState } from 'react';
|
|
7
7
|
|
|
8
|
-
import { DETAIL_PANEL_WIDTH } from '../FileDetail';
|
|
9
|
-
|
|
10
8
|
const styles = createStaticStyles(({ css, cssVar }) => ({
|
|
11
9
|
body: css`
|
|
12
10
|
height: 100%;
|
|
@@ -23,7 +21,7 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
|
|
|
23
21
|
inset-block: 0 0;
|
|
24
22
|
inset-inline-end: 0;
|
|
25
23
|
|
|
26
|
-
width:
|
|
24
|
+
width: 0;
|
|
27
25
|
border-inline-start: 1px solid ${cssVar.colorSplit};
|
|
28
26
|
|
|
29
27
|
background: ${cssVar.colorBgLayout};
|
|
@@ -46,7 +44,7 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
|
|
|
46
44
|
}
|
|
47
45
|
`,
|
|
48
46
|
modal_withDetail: css`
|
|
49
|
-
width: calc(100vw
|
|
47
|
+
width: calc(100vw) !important;
|
|
50
48
|
`,
|
|
51
49
|
}));
|
|
52
50
|
|
|
@@ -50,6 +50,9 @@ const RootLayout = async ({ children, params }: RootLayoutProps) => {
|
|
|
50
50
|
return (
|
|
51
51
|
<html dir={direction} lang={locale} suppressHydrationWarning>
|
|
52
52
|
<head>
|
|
53
|
+
{/* eslint-disable-next-line @typescript-eslint/no-use-before-define */}
|
|
54
|
+
<script dangerouslySetInnerHTML={{ __html: `(${outdateBrowserScript.toString()})();` }} />
|
|
55
|
+
|
|
53
56
|
{/* <script dangerouslySetInnerHTML={{ __html: 'setTimeout(() => {debugger}, 16)' }} /> */}
|
|
54
57
|
{process.env.DEBUG_REACT_SCAN === '1' && (
|
|
55
58
|
<Script
|
|
@@ -74,6 +77,52 @@ const RootLayout = async ({ children, params }: RootLayoutProps) => {
|
|
|
74
77
|
);
|
|
75
78
|
};
|
|
76
79
|
|
|
80
|
+
function outdateBrowserScript() {
|
|
81
|
+
// eslint-disable-next-line unicorn/consistent-function-scoping
|
|
82
|
+
function supportsImportMaps(): boolean {
|
|
83
|
+
return (
|
|
84
|
+
typeof HTMLScriptElement !== 'undefined' &&
|
|
85
|
+
typeof (HTMLScriptElement as any).supports === 'function' &&
|
|
86
|
+
(HTMLScriptElement as any).supports('importmap')
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// eslint-disable-next-line unicorn/consistent-function-scoping
|
|
91
|
+
function supportsCascadeLayers(): boolean {
|
|
92
|
+
if (typeof document === 'undefined') return false;
|
|
93
|
+
|
|
94
|
+
const el = document.createElement('div');
|
|
95
|
+
el.className = '__layer_test__';
|
|
96
|
+
el.style.position = 'absolute';
|
|
97
|
+
el.style.left = '-99999px';
|
|
98
|
+
el.style.top = '-99999px';
|
|
99
|
+
|
|
100
|
+
const style = document.createElement('style');
|
|
101
|
+
style.textContent = `
|
|
102
|
+
@layer a, b;
|
|
103
|
+
@layer a { .__layer_test__ { color: rgb(1, 2, 3); } }
|
|
104
|
+
@layer b { .__layer_test__ { color: rgb(4, 5, 6); } }
|
|
105
|
+
`;
|
|
106
|
+
|
|
107
|
+
document.documentElement.append(style);
|
|
108
|
+
document.documentElement.append(el);
|
|
109
|
+
|
|
110
|
+
const color = getComputedStyle(el).color;
|
|
111
|
+
|
|
112
|
+
el.remove();
|
|
113
|
+
style.remove();
|
|
114
|
+
|
|
115
|
+
return color === 'rgb(4, 5, 6)';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const isOutdateBrowser = !(supportsImportMaps() && supportsCascadeLayers());
|
|
119
|
+
if (isOutdateBrowser) {
|
|
120
|
+
window.location.href = '/not-compatible.html';
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
77
126
|
export default RootLayout;
|
|
78
127
|
|
|
79
128
|
export { generateMetadata } from './metadata';
|
|
@@ -99,7 +148,7 @@ export const generateViewport = async (props: DynamicLayoutProps): ResolvingView
|
|
|
99
148
|
|
|
100
149
|
export const generateStaticParams = () => {
|
|
101
150
|
const mobileOptions = isDesktop ? [false] : [true, false];
|
|
102
|
-
// only static for
|
|
151
|
+
// only static for several page, other go to dynamic
|
|
103
152
|
const staticLocales: Locales[] = [DEFAULT_LANG, 'zh-CN'];
|
|
104
153
|
|
|
105
154
|
const variants: { variants: string }[] = [];
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { Checkbox, Flexbox, Icon } from '@lobehub/ui';
|
|
2
|
+
import { Loader2, SquareArrowOutUpRight, Unplug } from 'lucide-react';
|
|
3
|
+
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
|
|
6
|
+
import { useAgentStore } from '@/store/agent';
|
|
7
|
+
import { agentSelectors } from '@/store/agent/selectors';
|
|
8
|
+
import { useToolStore } from '@/store/tool';
|
|
9
|
+
import { lobehubSkillStoreSelectors } from '@/store/tool/selectors';
|
|
10
|
+
import { LobehubSkillStatus } from '@/store/tool/slices/lobehubSkillStore/types';
|
|
11
|
+
|
|
12
|
+
const POLL_INTERVAL_MS = 1000;
|
|
13
|
+
const POLL_TIMEOUT_MS = 15_000;
|
|
14
|
+
|
|
15
|
+
interface LobehubSkillServerItemProps {
|
|
16
|
+
/**
|
|
17
|
+
* Display label for the provider
|
|
18
|
+
*/
|
|
19
|
+
label: string;
|
|
20
|
+
/**
|
|
21
|
+
* Provider ID (e.g., 'linear', 'github')
|
|
22
|
+
*/
|
|
23
|
+
provider: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const LobehubSkillServerItem = memo<LobehubSkillServerItemProps>(({ provider, label }) => {
|
|
27
|
+
const { t } = useTranslation('setting');
|
|
28
|
+
const [isConnecting, setIsConnecting] = useState(false);
|
|
29
|
+
const [isToggling, setIsToggling] = useState(false);
|
|
30
|
+
const [isWaitingAuth, setIsWaitingAuth] = useState(false);
|
|
31
|
+
|
|
32
|
+
const oauthWindowRef = useRef<Window | null>(null);
|
|
33
|
+
const windowCheckIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
34
|
+
const pollIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
35
|
+
const pollTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
36
|
+
|
|
37
|
+
const server = useToolStore(lobehubSkillStoreSelectors.getServerByIdentifier(provider));
|
|
38
|
+
const checkStatus = useToolStore((s) => s.checkLobehubSkillStatus);
|
|
39
|
+
const revokeConnect = useToolStore((s) => s.revokeLobehubSkill);
|
|
40
|
+
const getAuthorizeUrl = useToolStore((s) => s.getLobehubSkillAuthorizeUrl);
|
|
41
|
+
|
|
42
|
+
const cleanup = useCallback(() => {
|
|
43
|
+
if (windowCheckIntervalRef.current) {
|
|
44
|
+
clearInterval(windowCheckIntervalRef.current);
|
|
45
|
+
windowCheckIntervalRef.current = null;
|
|
46
|
+
}
|
|
47
|
+
if (pollIntervalRef.current) {
|
|
48
|
+
clearInterval(pollIntervalRef.current);
|
|
49
|
+
pollIntervalRef.current = null;
|
|
50
|
+
}
|
|
51
|
+
if (pollTimeoutRef.current) {
|
|
52
|
+
clearTimeout(pollTimeoutRef.current);
|
|
53
|
+
pollTimeoutRef.current = null;
|
|
54
|
+
}
|
|
55
|
+
oauthWindowRef.current = null;
|
|
56
|
+
setIsWaitingAuth(false);
|
|
57
|
+
}, []);
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
return () => {
|
|
61
|
+
cleanup();
|
|
62
|
+
};
|
|
63
|
+
}, [cleanup]);
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (server?.status === LobehubSkillStatus.CONNECTED && isWaitingAuth) {
|
|
67
|
+
cleanup();
|
|
68
|
+
}
|
|
69
|
+
}, [server?.status, isWaitingAuth, cleanup]);
|
|
70
|
+
|
|
71
|
+
const startFallbackPolling = useCallback(() => {
|
|
72
|
+
if (pollIntervalRef.current) return;
|
|
73
|
+
|
|
74
|
+
pollIntervalRef.current = setInterval(async () => {
|
|
75
|
+
try {
|
|
76
|
+
await checkStatus(provider);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error('[LobehubSkill] Failed to check status:', error);
|
|
79
|
+
}
|
|
80
|
+
}, POLL_INTERVAL_MS);
|
|
81
|
+
|
|
82
|
+
pollTimeoutRef.current = setTimeout(() => {
|
|
83
|
+
if (pollIntervalRef.current) {
|
|
84
|
+
clearInterval(pollIntervalRef.current);
|
|
85
|
+
pollIntervalRef.current = null;
|
|
86
|
+
}
|
|
87
|
+
setIsWaitingAuth(false);
|
|
88
|
+
}, POLL_TIMEOUT_MS);
|
|
89
|
+
}, [checkStatus, provider]);
|
|
90
|
+
|
|
91
|
+
const startWindowMonitor = useCallback(
|
|
92
|
+
(oauthWindow: Window) => {
|
|
93
|
+
windowCheckIntervalRef.current = setInterval(() => {
|
|
94
|
+
try {
|
|
95
|
+
if (oauthWindow.closed) {
|
|
96
|
+
if (windowCheckIntervalRef.current) {
|
|
97
|
+
clearInterval(windowCheckIntervalRef.current);
|
|
98
|
+
windowCheckIntervalRef.current = null;
|
|
99
|
+
}
|
|
100
|
+
oauthWindowRef.current = null;
|
|
101
|
+
checkStatus(provider);
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
console.log('[LobehubSkill] COOP blocked window.closed access, falling back to polling');
|
|
105
|
+
if (windowCheckIntervalRef.current) {
|
|
106
|
+
clearInterval(windowCheckIntervalRef.current);
|
|
107
|
+
windowCheckIntervalRef.current = null;
|
|
108
|
+
}
|
|
109
|
+
startFallbackPolling();
|
|
110
|
+
}
|
|
111
|
+
}, 500);
|
|
112
|
+
},
|
|
113
|
+
[checkStatus, provider, startFallbackPolling],
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const openOAuthWindow = useCallback(
|
|
117
|
+
(authorizeUrl: string) => {
|
|
118
|
+
cleanup();
|
|
119
|
+
setIsWaitingAuth(true);
|
|
120
|
+
|
|
121
|
+
const oauthWindow = window.open(authorizeUrl, '_blank', 'width=600,height=700');
|
|
122
|
+
if (oauthWindow) {
|
|
123
|
+
oauthWindowRef.current = oauthWindow;
|
|
124
|
+
startWindowMonitor(oauthWindow);
|
|
125
|
+
} else {
|
|
126
|
+
startFallbackPolling();
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
[cleanup, startWindowMonitor, startFallbackPolling],
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const pluginId = server ? server.identifier : '';
|
|
133
|
+
const [checked, togglePlugin] = useAgentStore((s) => [
|
|
134
|
+
agentSelectors.currentAgentPlugins(s).includes(pluginId),
|
|
135
|
+
s.togglePlugin,
|
|
136
|
+
]);
|
|
137
|
+
|
|
138
|
+
const handleConnect = async () => {
|
|
139
|
+
// 只有已连接状态才阻止重新连接
|
|
140
|
+
if (server?.isConnected) return;
|
|
141
|
+
|
|
142
|
+
setIsConnecting(true);
|
|
143
|
+
try {
|
|
144
|
+
const { authorizeUrl } = await getAuthorizeUrl(provider);
|
|
145
|
+
openOAuthWindow(authorizeUrl);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
console.error('[LobehubSkill] Failed to get authorize URL:', error);
|
|
148
|
+
} finally {
|
|
149
|
+
setIsConnecting(false);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const handleToggle = async () => {
|
|
154
|
+
if (!server) return;
|
|
155
|
+
setIsToggling(true);
|
|
156
|
+
await togglePlugin(pluginId);
|
|
157
|
+
setIsToggling(false);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const handleDisconnect = async () => {
|
|
161
|
+
if (!server) return;
|
|
162
|
+
setIsToggling(true);
|
|
163
|
+
if (checked) {
|
|
164
|
+
await togglePlugin(pluginId);
|
|
165
|
+
}
|
|
166
|
+
await revokeConnect(server.identifier);
|
|
167
|
+
setIsToggling(false);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const renderRightControl = () => {
|
|
171
|
+
if (isConnecting) {
|
|
172
|
+
return (
|
|
173
|
+
<Flexbox align="center" gap={4} horizontal onClick={(e) => e.stopPropagation()}>
|
|
174
|
+
<Icon icon={Loader2} spin />
|
|
175
|
+
</Flexbox>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!server) {
|
|
180
|
+
return (
|
|
181
|
+
<Flexbox
|
|
182
|
+
align="center"
|
|
183
|
+
gap={4}
|
|
184
|
+
horizontal
|
|
185
|
+
onClick={(e) => {
|
|
186
|
+
e.stopPropagation();
|
|
187
|
+
handleConnect();
|
|
188
|
+
}}
|
|
189
|
+
style={{ cursor: 'pointer', opacity: 0.65 }}
|
|
190
|
+
>
|
|
191
|
+
{t('tools.lobehubSkill.connect', { defaultValue: 'Connect' })}
|
|
192
|
+
<Icon icon={SquareArrowOutUpRight} size="small" />
|
|
193
|
+
</Flexbox>
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
switch (server.status) {
|
|
198
|
+
case LobehubSkillStatus.CONNECTED: {
|
|
199
|
+
if (isToggling) {
|
|
200
|
+
return <Icon icon={Loader2} spin />;
|
|
201
|
+
}
|
|
202
|
+
return (
|
|
203
|
+
<Flexbox align="center" gap={8} horizontal>
|
|
204
|
+
<Icon
|
|
205
|
+
icon={Unplug}
|
|
206
|
+
onClick={(e) => {
|
|
207
|
+
e.stopPropagation();
|
|
208
|
+
handleDisconnect();
|
|
209
|
+
}}
|
|
210
|
+
size="small"
|
|
211
|
+
style={{ cursor: 'pointer', opacity: 0.5 }}
|
|
212
|
+
/>
|
|
213
|
+
<Checkbox
|
|
214
|
+
checked={checked}
|
|
215
|
+
onClick={(e) => {
|
|
216
|
+
e.stopPropagation();
|
|
217
|
+
handleToggle();
|
|
218
|
+
}}
|
|
219
|
+
/>
|
|
220
|
+
</Flexbox>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
case LobehubSkillStatus.CONNECTING: {
|
|
224
|
+
if (isWaitingAuth) {
|
|
225
|
+
return (
|
|
226
|
+
<Flexbox align="center" gap={4} horizontal onClick={(e) => e.stopPropagation()}>
|
|
227
|
+
<Icon icon={Loader2} spin />
|
|
228
|
+
</Flexbox>
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
return (
|
|
232
|
+
<Flexbox
|
|
233
|
+
align="center"
|
|
234
|
+
gap={4}
|
|
235
|
+
horizontal
|
|
236
|
+
onClick={async (e) => {
|
|
237
|
+
e.stopPropagation();
|
|
238
|
+
try {
|
|
239
|
+
const { authorizeUrl } = await getAuthorizeUrl(provider);
|
|
240
|
+
openOAuthWindow(authorizeUrl);
|
|
241
|
+
} catch (error) {
|
|
242
|
+
console.error('[LobehubSkill] Failed to get authorize URL:', error);
|
|
243
|
+
}
|
|
244
|
+
}}
|
|
245
|
+
style={{ cursor: 'pointer', opacity: 0.65 }}
|
|
246
|
+
>
|
|
247
|
+
{t('tools.lobehubSkill.authorize', { defaultValue: 'Authorize' })}
|
|
248
|
+
<Icon icon={SquareArrowOutUpRight} size="small" />
|
|
249
|
+
</Flexbox>
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
case LobehubSkillStatus.NOT_CONNECTED: {
|
|
253
|
+
return (
|
|
254
|
+
<Flexbox
|
|
255
|
+
align="center"
|
|
256
|
+
gap={4}
|
|
257
|
+
horizontal
|
|
258
|
+
onClick={(e) => {
|
|
259
|
+
e.stopPropagation();
|
|
260
|
+
handleConnect();
|
|
261
|
+
}}
|
|
262
|
+
style={{ cursor: 'pointer', opacity: 0.65 }}
|
|
263
|
+
>
|
|
264
|
+
{t('tools.lobehubSkill.connect', { defaultValue: 'Connect' })}
|
|
265
|
+
<Icon icon={SquareArrowOutUpRight} size="small" />
|
|
266
|
+
</Flexbox>
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
case LobehubSkillStatus.ERROR: {
|
|
270
|
+
return (
|
|
271
|
+
<span style={{ color: 'red', fontSize: 12 }}>
|
|
272
|
+
{t('tools.lobehubSkill.error', { defaultValue: 'Error' })}
|
|
273
|
+
</span>
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
default: {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
<Flexbox
|
|
284
|
+
align={'center'}
|
|
285
|
+
gap={24}
|
|
286
|
+
horizontal
|
|
287
|
+
justify={'space-between'}
|
|
288
|
+
onClick={(e) => {
|
|
289
|
+
e.stopPropagation();
|
|
290
|
+
if (server?.status === LobehubSkillStatus.CONNECTED) {
|
|
291
|
+
handleToggle();
|
|
292
|
+
}
|
|
293
|
+
}}
|
|
294
|
+
style={{ paddingLeft: 8 }}
|
|
295
|
+
>
|
|
296
|
+
<Flexbox align={'center'} gap={8} horizontal>
|
|
297
|
+
{label}
|
|
298
|
+
</Flexbox>
|
|
299
|
+
{renderRightControl()}
|
|
300
|
+
</Flexbox>
|
|
301
|
+
);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
export default LobehubSkillServerItem;
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
KLAVIS_SERVER_TYPES,
|
|
3
|
+
type KlavisServerType,
|
|
4
|
+
LOBEHUB_SKILL_PROVIDERS,
|
|
5
|
+
type LobehubSkillProviderType,
|
|
6
|
+
} from '@lobechat/const';
|
|
2
7
|
import { Avatar, Flexbox, Icon, Image, type ItemType } from '@lobehub/ui';
|
|
3
8
|
import { cssVar } from 'antd-style';
|
|
4
9
|
import isEqual from 'fast-deep-equal';
|
|
@@ -16,11 +21,13 @@ import { useToolStore } from '@/store/tool';
|
|
|
16
21
|
import {
|
|
17
22
|
builtinToolSelectors,
|
|
18
23
|
klavisStoreSelectors,
|
|
24
|
+
lobehubSkillStoreSelectors,
|
|
19
25
|
pluginSelectors,
|
|
20
26
|
} from '@/store/tool/selectors';
|
|
21
27
|
|
|
22
28
|
import { useAgentId } from '../../hooks/useAgentId';
|
|
23
29
|
import KlavisServerItem from './KlavisServerItem';
|
|
30
|
+
import LobehubSkillServerItem from './LobehubSkillServerItem';
|
|
24
31
|
import ToolItem from './ToolItem';
|
|
25
32
|
|
|
26
33
|
/**
|
|
@@ -39,6 +46,21 @@ const KlavisIcon = memo<Pick<KlavisServerType, 'icon' | 'label'>>(({ icon, label
|
|
|
39
46
|
|
|
40
47
|
KlavisIcon.displayName = 'KlavisIcon';
|
|
41
48
|
|
|
49
|
+
/**
|
|
50
|
+
* LobeHub Skill Provider 图标组件
|
|
51
|
+
*/
|
|
52
|
+
const LobehubSkillIcon = memo<Pick<LobehubSkillProviderType, 'icon' | 'label'>>(
|
|
53
|
+
({ icon, label }) => {
|
|
54
|
+
if (typeof icon === 'string') {
|
|
55
|
+
return <Image alt={label} height={18} src={icon} style={{ flex: 'none' }} width={18} />;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return <Icon fill={cssVar.colorText} icon={icon} size={18} />;
|
|
59
|
+
},
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
LobehubSkillIcon.displayName = 'LobehubSkillIcon';
|
|
63
|
+
|
|
42
64
|
export const useControls = ({
|
|
43
65
|
setModalOpen,
|
|
44
66
|
setUpdating,
|
|
@@ -66,10 +88,16 @@ export const useControls = ({
|
|
|
66
88
|
const allKlavisServers = useToolStore(klavisStoreSelectors.getServers, isEqual);
|
|
67
89
|
const isKlavisEnabledInEnv = useServerConfigStore(serverConfigSelectors.enableKlavis);
|
|
68
90
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
91
|
+
// LobeHub Skill 相关状态
|
|
92
|
+
const allLobehubSkillServers = useToolStore(lobehubSkillStoreSelectors.getServers, isEqual);
|
|
93
|
+
const isLobehubSkillEnabled = useServerConfigStore(serverConfigSelectors.enableLobehubSkill);
|
|
94
|
+
|
|
95
|
+
const [useFetchPluginStore, useFetchUserKlavisServers, useFetchLobehubSkillConnections] =
|
|
96
|
+
useToolStore((s) => [
|
|
97
|
+
s.useFetchPluginStore,
|
|
98
|
+
s.useFetchUserKlavisServers,
|
|
99
|
+
s.useFetchLobehubSkillConnections,
|
|
100
|
+
]);
|
|
73
101
|
|
|
74
102
|
useFetchPluginStore();
|
|
75
103
|
useFetchInstalledPlugins();
|
|
@@ -78,6 +106,9 @@ export const useControls = ({
|
|
|
78
106
|
// 使用 SWR 加载用户的 Klavis 集成(从数据库)
|
|
79
107
|
useFetchUserKlavisServers(isKlavisEnabledInEnv);
|
|
80
108
|
|
|
109
|
+
// 使用 SWR 加载用户的 LobeHub Skill 连接
|
|
110
|
+
useFetchLobehubSkillConnections(isLobehubSkillEnabled);
|
|
111
|
+
|
|
81
112
|
// 根据 identifier 获取已连接的服务器
|
|
82
113
|
const getServerByName = (identifier: string) => {
|
|
83
114
|
return allKlavisServers.find((server) => server.identifier === identifier);
|
|
@@ -118,7 +149,20 @@ export const useControls = ({
|
|
|
118
149
|
[isKlavisEnabledInEnv, allKlavisServers],
|
|
119
150
|
);
|
|
120
151
|
|
|
121
|
-
//
|
|
152
|
+
// LobeHub Skill Provider 列表项
|
|
153
|
+
const lobehubSkillItems = useMemo(
|
|
154
|
+
() =>
|
|
155
|
+
isLobehubSkillEnabled
|
|
156
|
+
? LOBEHUB_SKILL_PROVIDERS.map((provider) => ({
|
|
157
|
+
icon: <LobehubSkillIcon icon={provider.icon} label={provider.label} />,
|
|
158
|
+
key: provider.id, // 使用 provider.id 作为 key,与 pluginId 保持一致
|
|
159
|
+
label: <LobehubSkillServerItem label={provider.label} provider={provider.id} />,
|
|
160
|
+
}))
|
|
161
|
+
: [],
|
|
162
|
+
[isLobehubSkillEnabled, allLobehubSkillServers],
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
// 合并 builtin 工具、Klavis 服务器和 LobeHub Skill Provider
|
|
122
166
|
const builtinItems = useMemo(
|
|
123
167
|
() => [
|
|
124
168
|
// 原有的 builtin 工具
|
|
@@ -140,10 +184,12 @@ export const useControls = ({
|
|
|
140
184
|
/>
|
|
141
185
|
),
|
|
142
186
|
})),
|
|
187
|
+
// LobeHub Skill Providers
|
|
188
|
+
...lobehubSkillItems,
|
|
143
189
|
// Klavis 服务器
|
|
144
190
|
...klavisServerItems,
|
|
145
191
|
],
|
|
146
|
-
[filteredBuiltinList, klavisServerItems, checked, togglePlugin, setUpdating],
|
|
192
|
+
[filteredBuiltinList, klavisServerItems, lobehubSkillItems, checked, togglePlugin, setUpdating],
|
|
147
193
|
);
|
|
148
194
|
|
|
149
195
|
// 市场 tab 的 items
|
|
@@ -233,8 +279,17 @@ export const useControls = ({
|
|
|
233
279
|
checked.includes(item.key as string),
|
|
234
280
|
);
|
|
235
281
|
|
|
236
|
-
//
|
|
237
|
-
const
|
|
282
|
+
// 已连接的 LobeHub Skill Providers
|
|
283
|
+
const connectedLobehubSkillItems = lobehubSkillItems.filter((item) =>
|
|
284
|
+
checked.includes(item.key as string),
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
// 合并 builtin、Klavis 和 LobeHub Skill
|
|
288
|
+
const allBuiltinItems = [
|
|
289
|
+
...enabledBuiltinItems,
|
|
290
|
+
...connectedKlavisItems,
|
|
291
|
+
...connectedLobehubSkillItems,
|
|
292
|
+
];
|
|
238
293
|
|
|
239
294
|
if (allBuiltinItems.length > 0) {
|
|
240
295
|
installedItems.push({
|
|
@@ -279,7 +334,16 @@ export const useControls = ({
|
|
|
279
334
|
}
|
|
280
335
|
|
|
281
336
|
return installedItems;
|
|
282
|
-
}, [
|
|
337
|
+
}, [
|
|
338
|
+
filteredBuiltinList,
|
|
339
|
+
list,
|
|
340
|
+
klavisServerItems,
|
|
341
|
+
lobehubSkillItems,
|
|
342
|
+
checked,
|
|
343
|
+
togglePlugin,
|
|
344
|
+
setUpdating,
|
|
345
|
+
t,
|
|
346
|
+
]);
|
|
283
347
|
|
|
284
348
|
return { installedPluginItems, marketItems };
|
|
285
349
|
};
|
|
@@ -38,6 +38,15 @@ const ToolTitle = memo<ToolTitleProps>(({ identifier, apiName, isLoading, isAbor
|
|
|
38
38
|
const isBuiltinPlugin = builtinToolIdentifiers.includes(identifier);
|
|
39
39
|
const pluginTitle = pluginHelpers.getPluginTitle(pluginMeta) ?? t('unknownPlugin');
|
|
40
40
|
|
|
41
|
+
// Debug logging for LobeHub Skill title issue
|
|
42
|
+
console.log('[ToolTitle Debug]', {
|
|
43
|
+
apiName,
|
|
44
|
+
identifier,
|
|
45
|
+
isBuiltinPlugin,
|
|
46
|
+
pluginMeta,
|
|
47
|
+
pluginTitle,
|
|
48
|
+
});
|
|
49
|
+
|
|
41
50
|
return (
|
|
42
51
|
<div
|
|
43
52
|
className={cx(
|