@lobehub/lobehub 2.0.0-next.232 → 2.0.0-next.234
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/bundle-analyzer.yml +1 -1
- package/.github/workflows/e2e.yml +62 -53
- package/.github/workflows/manual-build-desktop.yml +5 -5
- package/.github/workflows/pr-build-desktop.yml +4 -4
- package/.github/workflows/pr-build-docker.yml +2 -2
- package/.github/workflows/release-desktop-beta.yml +4 -4
- package/.github/workflows/release-docker.yml +2 -2
- package/.github/workflows/test.yml +44 -7
- package/CHANGELOG.md +59 -0
- package/CLAUDE.md +1 -1
- package/changelog/v1.json +14 -0
- package/docs/development/basic/feature-development.mdx +4 -5
- package/docs/development/basic/feature-development.zh-CN.mdx +4 -5
- package/docs/self-hosting/environment-variables/auth.mdx +7 -0
- package/docs/self-hosting/environment-variables/auth.zh-CN.mdx +7 -0
- 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/en-US/setting.json +3 -0
- package/locales/zh-CN/file.json +4 -0
- package/locales/zh-CN/setting.json +3 -0
- package/package.json +5 -5
- package/packages/business/config/src/llm.ts +6 -1
- package/packages/const/src/index.ts +1 -0
- package/packages/const/src/lobehubSkill.ts +55 -0
- package/packages/const/src/settings/image.ts +1 -1
- package/packages/model-bank/src/aiModels/azure.ts +2 -2
- package/packages/model-bank/src/aiModels/google.ts +1 -0
- package/packages/model-bank/src/aiModels/lobehub.ts +33 -13
- package/packages/model-bank/src/aiModels/openai.ts +21 -4
- package/packages/model-runtime/src/core/openaiCompatibleFactory/createImage.ts +4 -1
- package/packages/model-runtime/src/providers/openai/__snapshots__/index.test.ts.snap +1 -1
- package/packages/ssrf-safe-fetch/index.test.ts +5 -34
- package/packages/ssrf-safe-fetch/index.ts +12 -2
- 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)/image/_layout/ConfigPanel/components/MultiImagesUpload/index.tsx +3 -3
- package/src/app/[variants]/(main)/image/features/GenerationFeed/index.tsx +3 -10
- package/src/app/[variants]/(main)/image/index.tsx +1 -1
- 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/envs/auth.ts +15 -0
- 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/hooks/useFetchAiImageConfig.ts +54 -10
- package/src/libs/trpc/utils/internalJwt.ts +2 -2
- 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/document.ts +44 -0
- 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/image/slices/generationConfig/initialState.ts +5 -5
- package/src/store/image/slices/generationConfig/selectors.test.ts +11 -4
- 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 +11 -6
- package/src/features/FileViewer/Renderer/JavaScript/index.tsx +0 -66
- package/src/features/FileViewer/Renderer/TXT/index.tsx +0 -50
package/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/MultiImagesUpload/index.tsx
CHANGED
|
@@ -93,8 +93,8 @@ const styles = createStaticStyles(({ css }) => {
|
|
|
93
93
|
|
|
94
94
|
overflow: hidden;
|
|
95
95
|
|
|
96
|
-
width: ${thumbnailSize};
|
|
97
|
-
height: ${thumbnailSize};
|
|
96
|
+
width: ${thumbnailSize}px;
|
|
97
|
+
height: ${thumbnailSize}px;
|
|
98
98
|
border-radius: ${cssVar.borderRadius};
|
|
99
99
|
|
|
100
100
|
background: ${cssVar.colorBgContainer};
|
|
@@ -112,7 +112,7 @@ const styles = createStaticStyles(({ css }) => {
|
|
|
112
112
|
gap: 8px;
|
|
113
113
|
|
|
114
114
|
width: 100%;
|
|
115
|
-
height: ${thumbnailSize};
|
|
115
|
+
height: ${thumbnailSize}px;
|
|
116
116
|
padding: 0;
|
|
117
117
|
border-radius: ${cssVar.borderRadiusLG};
|
|
118
118
|
|
|
@@ -77,15 +77,8 @@ const GenerationFeed = memo(() => {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
return (
|
|
80
|
-
|
|
81
|
-
<Flexbox
|
|
82
|
-
gap={16}
|
|
83
|
-
ref={parent}
|
|
84
|
-
style={{
|
|
85
|
-
minHeight: 'calc(100vh - 180px)',
|
|
86
|
-
}}
|
|
87
|
-
width="100%"
|
|
88
|
-
>
|
|
80
|
+
<Flexbox flex={1}>
|
|
81
|
+
<Flexbox gap={16} ref={parent} width="100%">
|
|
89
82
|
{currentGenerationBatches.map((batch, index) => (
|
|
90
83
|
<Fragment key={batch.id}>
|
|
91
84
|
{Boolean(index !== 0) && <Divider dashed style={{ margin: 0 }} />}
|
|
@@ -95,7 +88,7 @@ const GenerationFeed = memo(() => {
|
|
|
95
88
|
</Flexbox>
|
|
96
89
|
{/* Invisible element for scroll target */}
|
|
97
90
|
<div ref={containerRef} style={{ height: 1 }} />
|
|
98
|
-
|
|
91
|
+
</Flexbox>
|
|
99
92
|
);
|
|
100
93
|
});
|
|
101
94
|
|
|
@@ -15,7 +15,7 @@ const DesktopImagePage = memo(() => {
|
|
|
15
15
|
<>
|
|
16
16
|
<NavHeader right={<WideScreenButton />} />
|
|
17
17
|
<Flexbox height={'100%'} style={{ overflowY: 'auto', position: 'relative' }} width={'100%'}>
|
|
18
|
-
<WideScreenContainer>
|
|
18
|
+
<WideScreenContainer height={'100%'} wrapperStyle={{ height: '100%' }}>
|
|
19
19
|
<Suspense fallback={<SkeletonList />}>
|
|
20
20
|
<ImageWorkspace />
|
|
21
21
|
</Suspense>
|
|
@@ -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 }[] = [];
|
package/src/envs/auth.ts
CHANGED
|
@@ -158,6 +158,15 @@ declare global {
|
|
|
158
158
|
* Can be generated using `node scripts/generate-oidc-jwk.mjs`.
|
|
159
159
|
*/
|
|
160
160
|
JWKS_KEY?: string;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Internal JWT expiration time for lambda → async calls.
|
|
164
|
+
* Format: number followed by unit (s=seconds, m=minutes, h=hours)
|
|
165
|
+
* Examples: '10s', '1m', '1h'
|
|
166
|
+
* Should be as short as possible for security, but long enough to account for network latency and server processing time.
|
|
167
|
+
* @default '30s'
|
|
168
|
+
*/
|
|
169
|
+
INTERNAL_JWT_EXPIRATION?: string;
|
|
161
170
|
}
|
|
162
171
|
}
|
|
163
172
|
}
|
|
@@ -285,6 +294,9 @@ export const getAuthConfig = () => {
|
|
|
285
294
|
// Generic JWKS key for signing/verifying JWTs
|
|
286
295
|
JWKS_KEY: z.string().optional(),
|
|
287
296
|
ENABLE_OIDC: z.boolean(),
|
|
297
|
+
|
|
298
|
+
// Internal JWT expiration time (e.g., '10s', '1m', '1h')
|
|
299
|
+
INTERNAL_JWT_EXPIRATION: z.string().default('30s'),
|
|
288
300
|
},
|
|
289
301
|
|
|
290
302
|
runtimeEnv: {
|
|
@@ -415,6 +427,9 @@ export const getAuthConfig = () => {
|
|
|
415
427
|
// Generic JWKS key (fallback to OIDC_JWKS_KEY for backward compatibility)
|
|
416
428
|
JWKS_KEY: process.env.JWKS_KEY || process.env.OIDC_JWKS_KEY,
|
|
417
429
|
ENABLE_OIDC: !!(process.env.JWKS_KEY || process.env.OIDC_JWKS_KEY),
|
|
430
|
+
|
|
431
|
+
// Internal JWT expiration time
|
|
432
|
+
INTERNAL_JWT_EXPIRATION: process.env.INTERNAL_JWT_EXPIRATION,
|
|
418
433
|
},
|
|
419
434
|
});
|
|
420
435
|
};
|
|
@@ -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;
|