@lobehub/lobehub 2.0.0-next.270 → 2.0.0-next.271
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/.eslintrc.js +1 -0
- package/CHANGELOG.md +25 -0
- package/changelog/v1.json +9 -0
- package/e2e/src/steps/community/interactions.steps.ts +37 -14
- package/package.json +1 -1
- package/src/app/[variants]/(main)/agent/features/Conversation/Header/HeaderActions/useMenu.tsx +7 -17
- package/src/app/[variants]/(main)/agent/profile/features/ProfileEditor/AgentTool.tsx +8 -487
- package/src/app/[variants]/(main)/group/profile/features/ProfileEditor/AgentTool.tsx +6 -388
- package/src/features/ChatInput/ActionBar/Tools/KlavisServerItem.tsx +1 -2
- package/src/features/ChatInput/ActionBar/Tools/LobehubSkillServerItem.tsx +0 -1
- package/src/features/ProfileEditor/AgentTool.tsx +549 -0
- package/src/features/ProfileEditor/PluginTag.tsx +213 -0
- package/src/features/ProfileEditor/index.ts +2 -0
- package/src/libs/better-auth/define-config.ts +7 -1
- package/src/app/[variants]/(main)/agent/profile/features/ProfileEditor/PluginTag.tsx +0 -195
- package/src/app/[variants]/(main)/group/profile/features/ProfileEditor/PluginTag.tsx +0 -180
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { KLAVIS_SERVER_TYPES, type KlavisServerType } from '@lobechat/const';
|
|
4
|
+
import { Avatar, Icon, Tag } from '@lobehub/ui';
|
|
5
|
+
import { createStaticStyles, cssVar } from 'antd-style';
|
|
6
|
+
import isEqual from 'fast-deep-equal';
|
|
7
|
+
import { AlertCircle, X } from 'lucide-react';
|
|
8
|
+
import React, { memo, useMemo } from 'react';
|
|
9
|
+
import { useTranslation } from 'react-i18next';
|
|
10
|
+
|
|
11
|
+
import PluginAvatar from '@/components/Plugins/PluginAvatar';
|
|
12
|
+
import { useIsDark } from '@/hooks/useIsDark';
|
|
13
|
+
import { useDiscoverStore } from '@/store/discover';
|
|
14
|
+
import { serverConfigSelectors, useServerConfigStore } from '@/store/serverConfig';
|
|
15
|
+
import { useToolStore } from '@/store/tool';
|
|
16
|
+
import {
|
|
17
|
+
builtinToolSelectors,
|
|
18
|
+
klavisStoreSelectors,
|
|
19
|
+
pluginSelectors,
|
|
20
|
+
} from '@/store/tool/selectors';
|
|
21
|
+
import { type LobeToolMetaWithAvailability } from '@/store/tool/slices/builtin/selectors';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Klavis 服务器图标组件
|
|
25
|
+
*/
|
|
26
|
+
const KlavisIcon = memo<Pick<KlavisServerType, 'icon' | 'label'>>(({ icon, label }) => {
|
|
27
|
+
if (typeof icon === 'string') {
|
|
28
|
+
return <img alt={label} height={16} src={icon} style={{ flexShrink: 0 }} width={16} />;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return <Icon fill={cssVar.colorText} icon={icon} size={16} />;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const styles = createStaticStyles(({ css, cssVar }) => ({
|
|
35
|
+
notInstalledTag: css`
|
|
36
|
+
border-color: ${cssVar.colorWarningBorder};
|
|
37
|
+
background: ${cssVar.colorWarningBg};
|
|
38
|
+
`,
|
|
39
|
+
tag: css`
|
|
40
|
+
height: 28px !important;
|
|
41
|
+
border-radius: ${cssVar.borderRadiusSM} !important;
|
|
42
|
+
`,
|
|
43
|
+
warningIcon: css`
|
|
44
|
+
flex-shrink: 0;
|
|
45
|
+
color: ${cssVar.colorWarning};
|
|
46
|
+
`,
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
export interface PluginTagProps {
|
|
50
|
+
onRemove: (e: React.MouseEvent) => void;
|
|
51
|
+
pluginId: string | { enabled: boolean; identifier: string; settings: Record<string, any> };
|
|
52
|
+
/**
|
|
53
|
+
* Whether to show "Desktop Only" label for tools not available in web
|
|
54
|
+
* @default false
|
|
55
|
+
*/
|
|
56
|
+
showDesktopOnlyLabel?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Whether to use allMetaList (includes hidden tools) or metaList
|
|
59
|
+
* @default false
|
|
60
|
+
*/
|
|
61
|
+
useAllMetaList?: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const PluginTag = memo<PluginTagProps>(
|
|
65
|
+
({ pluginId, onRemove, showDesktopOnlyLabel = false, useAllMetaList = false }) => {
|
|
66
|
+
const isDarkMode = useIsDark();
|
|
67
|
+
const { t } = useTranslation('setting');
|
|
68
|
+
|
|
69
|
+
// Extract identifier
|
|
70
|
+
const identifier = typeof pluginId === 'string' ? pluginId : pluginId?.identifier;
|
|
71
|
+
|
|
72
|
+
// Get local plugin lists - use allMetaList or metaList based on prop
|
|
73
|
+
const builtinList = useToolStore(
|
|
74
|
+
useAllMetaList ? builtinToolSelectors.allMetaList : builtinToolSelectors.metaList,
|
|
75
|
+
isEqual,
|
|
76
|
+
);
|
|
77
|
+
const installedPluginList = useToolStore(pluginSelectors.installedPluginMetaList, isEqual);
|
|
78
|
+
|
|
79
|
+
// Klavis 相关状态
|
|
80
|
+
const allKlavisServers = useToolStore(klavisStoreSelectors.getServers, isEqual);
|
|
81
|
+
const isKlavisEnabledInEnv = useServerConfigStore(serverConfigSelectors.enableKlavis);
|
|
82
|
+
|
|
83
|
+
// Check if plugin is installed
|
|
84
|
+
const isInstalled = useToolStore(pluginSelectors.isPluginInstalled(identifier));
|
|
85
|
+
|
|
86
|
+
// Try to find in local lists first (including Klavis)
|
|
87
|
+
const localMeta = useMemo(() => {
|
|
88
|
+
// Check if it's a Klavis server type
|
|
89
|
+
if (isKlavisEnabledInEnv) {
|
|
90
|
+
const klavisType = KLAVIS_SERVER_TYPES.find((type) => type.identifier === identifier);
|
|
91
|
+
if (klavisType) {
|
|
92
|
+
// Check if this Klavis server is connected
|
|
93
|
+
const connectedServer = allKlavisServers.find((s) => s.identifier === identifier);
|
|
94
|
+
return {
|
|
95
|
+
availableInWeb: true,
|
|
96
|
+
icon: klavisType.icon,
|
|
97
|
+
isInstalled: !!connectedServer,
|
|
98
|
+
label: klavisType.label,
|
|
99
|
+
title: klavisType.label,
|
|
100
|
+
type: 'klavis' as const,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const builtinMeta = builtinList.find((p) => p.identifier === identifier);
|
|
106
|
+
if (builtinMeta) {
|
|
107
|
+
// availableInWeb is only present when using allMetaList
|
|
108
|
+
const availableInWeb =
|
|
109
|
+
useAllMetaList && 'availableInWeb' in builtinMeta
|
|
110
|
+
? (builtinMeta as LobeToolMetaWithAvailability).availableInWeb
|
|
111
|
+
: true;
|
|
112
|
+
return {
|
|
113
|
+
availableInWeb,
|
|
114
|
+
avatar: builtinMeta.meta.avatar,
|
|
115
|
+
isInstalled: true,
|
|
116
|
+
title: builtinMeta.meta.title,
|
|
117
|
+
type: 'builtin' as const,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const installedMeta = installedPluginList.find((p) => p.identifier === identifier);
|
|
122
|
+
if (installedMeta) {
|
|
123
|
+
return {
|
|
124
|
+
availableInWeb: true,
|
|
125
|
+
avatar: installedMeta.avatar,
|
|
126
|
+
isInstalled: true,
|
|
127
|
+
title: installedMeta.title,
|
|
128
|
+
type: 'plugin' as const,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return null;
|
|
133
|
+
}, [identifier, builtinList, installedPluginList, isKlavisEnabledInEnv, allKlavisServers]);
|
|
134
|
+
|
|
135
|
+
// Fetch from remote if not found locally
|
|
136
|
+
const usePluginDetail = useDiscoverStore((s) => s.usePluginDetail);
|
|
137
|
+
const { data: remoteData, isLoading } = usePluginDetail({
|
|
138
|
+
identifier: !localMeta && !isInstalled ? identifier : undefined,
|
|
139
|
+
withManifest: false,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Determine final metadata
|
|
143
|
+
const meta = localMeta || {
|
|
144
|
+
availableInWeb: true,
|
|
145
|
+
avatar: remoteData?.avatar,
|
|
146
|
+
isInstalled: false,
|
|
147
|
+
title: remoteData?.title || identifier,
|
|
148
|
+
type: 'plugin' as const,
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const displayTitle = isLoading ? 'Loading...' : meta.title;
|
|
152
|
+
const isDesktopOnly = showDesktopOnlyLabel && !meta.availableInWeb;
|
|
153
|
+
|
|
154
|
+
// Render icon based on type
|
|
155
|
+
const renderIcon = () => {
|
|
156
|
+
if (!meta.isInstalled) {
|
|
157
|
+
return <AlertCircle className={styles.warningIcon} size={14} />;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Klavis type has icon property
|
|
161
|
+
if (meta.type === 'klavis' && 'icon' in meta && 'label' in meta) {
|
|
162
|
+
return <KlavisIcon icon={meta.icon} label={meta.label} />;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Builtin type has avatar
|
|
166
|
+
if (meta.type === 'builtin' && 'avatar' in meta && meta.avatar) {
|
|
167
|
+
return <Avatar avatar={meta.avatar} shape={'square'} size={16} style={{ flexShrink: 0 }} />;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Plugin type
|
|
171
|
+
if ('avatar' in meta) {
|
|
172
|
+
return <PluginAvatar avatar={meta.avatar} size={16} />;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return null;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// Build display text
|
|
179
|
+
const getDisplayText = () => {
|
|
180
|
+
let text = displayTitle;
|
|
181
|
+
if (isDesktopOnly) {
|
|
182
|
+
text += ` (${t('tools.desktopOnly', { defaultValue: 'Desktop Only' })})`;
|
|
183
|
+
}
|
|
184
|
+
if (!meta.isInstalled) {
|
|
185
|
+
text += ` (${t('tools.notInstalled', { defaultValue: 'Not Installed' })})`;
|
|
186
|
+
}
|
|
187
|
+
return text;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<Tag
|
|
192
|
+
className={styles.tag}
|
|
193
|
+
closable
|
|
194
|
+
closeIcon={<X size={12} />}
|
|
195
|
+
color={meta.isInstalled ? undefined : 'error'}
|
|
196
|
+
icon={renderIcon()}
|
|
197
|
+
onClose={onRemove}
|
|
198
|
+
title={
|
|
199
|
+
meta.isInstalled
|
|
200
|
+
? undefined
|
|
201
|
+
: t('tools.notInstalledWarning', { defaultValue: 'This tool is not installed' })
|
|
202
|
+
}
|
|
203
|
+
variant={isDarkMode ? 'filled' : 'outlined'}
|
|
204
|
+
>
|
|
205
|
+
{getDisplayText()}
|
|
206
|
+
</Tag>
|
|
207
|
+
);
|
|
208
|
+
},
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
PluginTag.displayName = 'PluginTag';
|
|
212
|
+
|
|
213
|
+
export default PluginTag;
|
|
@@ -122,7 +122,13 @@ export function defineConfig(customOptions: CustomBetterAuthOptions) {
|
|
|
122
122
|
emailVerification: {
|
|
123
123
|
autoSignInAfterVerification: true,
|
|
124
124
|
expiresIn: VERIFICATION_LINK_EXPIRES_IN,
|
|
125
|
-
sendVerificationEmail: async ({ user, url }) => {
|
|
125
|
+
sendVerificationEmail: async ({ user, url }, request) => {
|
|
126
|
+
// Skip sending verification link email for mobile clients (Expo/React Native)
|
|
127
|
+
// Mobile clients use OTP verification instead, triggered manually via emailOTP plugin
|
|
128
|
+
if (request?.headers?.get?.('x-client-type') === 'mobile') {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
126
132
|
const template = getVerificationEmailTemplate({
|
|
127
133
|
expiresInSeconds: VERIFICATION_LINK_EXPIRES_IN,
|
|
128
134
|
url,
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { KLAVIS_SERVER_TYPES, type KlavisServerType } from '@lobechat/const';
|
|
4
|
-
import { Avatar, Icon, Tag } from '@lobehub/ui';
|
|
5
|
-
import { createStaticStyles, cssVar } from 'antd-style';
|
|
6
|
-
import isEqual from 'fast-deep-equal';
|
|
7
|
-
import { AlertCircle, X } from 'lucide-react';
|
|
8
|
-
import Image from 'next/image';
|
|
9
|
-
import React, { memo, useMemo } from 'react';
|
|
10
|
-
import { useTranslation } from 'react-i18next';
|
|
11
|
-
|
|
12
|
-
import PluginAvatar from '@/components/Plugins/PluginAvatar';
|
|
13
|
-
import { useIsDark } from '@/hooks/useIsDark';
|
|
14
|
-
import { useDiscoverStore } from '@/store/discover';
|
|
15
|
-
import { serverConfigSelectors, useServerConfigStore } from '@/store/serverConfig';
|
|
16
|
-
import { useToolStore } from '@/store/tool';
|
|
17
|
-
import {
|
|
18
|
-
builtinToolSelectors,
|
|
19
|
-
klavisStoreSelectors,
|
|
20
|
-
pluginSelectors,
|
|
21
|
-
} from '@/store/tool/selectors';
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Klavis 服务器图标组件
|
|
25
|
-
*/
|
|
26
|
-
const KlavisIcon = memo<Pick<KlavisServerType, 'icon' | 'label'>>(({ icon, label }) => {
|
|
27
|
-
if (typeof icon === 'string') {
|
|
28
|
-
return (
|
|
29
|
-
<Image alt={label} height={16} src={icon} style={{ flexShrink: 0 }} unoptimized width={16} />
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return <Icon fill={cssVar.colorText} icon={icon} size={16} />;
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
const styles = createStaticStyles(({ css, cssVar }) => ({
|
|
37
|
-
notInstalledTag: css`
|
|
38
|
-
border-color: ${cssVar.colorWarningBorder};
|
|
39
|
-
background: ${cssVar.colorWarningBg};
|
|
40
|
-
`,
|
|
41
|
-
tag: css`
|
|
42
|
-
height: 28px !important;
|
|
43
|
-
border-radius: ${cssVar.borderRadiusSM} !important;
|
|
44
|
-
`,
|
|
45
|
-
warningIcon: css`
|
|
46
|
-
flex-shrink: 0;
|
|
47
|
-
color: ${cssVar.colorWarning};
|
|
48
|
-
`,
|
|
49
|
-
}));
|
|
50
|
-
|
|
51
|
-
interface PluginTagProps {
|
|
52
|
-
onRemove: (e: React.MouseEvent) => void;
|
|
53
|
-
pluginId: string | { enabled: boolean; identifier: string; settings: Record<string, any> };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const PluginTag = memo<PluginTagProps>(({ pluginId, onRemove }) => {
|
|
57
|
-
const isDarkMode = useIsDark();
|
|
58
|
-
const { t } = useTranslation('setting');
|
|
59
|
-
|
|
60
|
-
// Extract identifier
|
|
61
|
-
const identifier = typeof pluginId === 'string' ? pluginId : pluginId?.identifier;
|
|
62
|
-
|
|
63
|
-
// Get local plugin lists (use allMetaList to include hidden tools)
|
|
64
|
-
const builtinList = useToolStore(builtinToolSelectors.allMetaList, isEqual);
|
|
65
|
-
const installedPluginList = useToolStore(pluginSelectors.installedPluginMetaList, isEqual);
|
|
66
|
-
|
|
67
|
-
// Klavis 相关状态
|
|
68
|
-
const allKlavisServers = useToolStore(klavisStoreSelectors.getServers, isEqual);
|
|
69
|
-
const isKlavisEnabledInEnv = useServerConfigStore(serverConfigSelectors.enableKlavis);
|
|
70
|
-
|
|
71
|
-
// Check if plugin is installed
|
|
72
|
-
const isInstalled = useToolStore(pluginSelectors.isPluginInstalled(identifier));
|
|
73
|
-
|
|
74
|
-
// Try to find in local lists first (including Klavis)
|
|
75
|
-
const localMeta = useMemo(() => {
|
|
76
|
-
// Check if it's a Klavis server type
|
|
77
|
-
if (isKlavisEnabledInEnv) {
|
|
78
|
-
const klavisType = KLAVIS_SERVER_TYPES.find((type) => type.identifier === identifier);
|
|
79
|
-
if (klavisType) {
|
|
80
|
-
// Check if this Klavis server is connected
|
|
81
|
-
const connectedServer = allKlavisServers.find((s) => s.identifier === identifier);
|
|
82
|
-
return {
|
|
83
|
-
availableInWeb: true,
|
|
84
|
-
icon: klavisType.icon,
|
|
85
|
-
isInstalled: !!connectedServer,
|
|
86
|
-
label: klavisType.label,
|
|
87
|
-
title: klavisType.label,
|
|
88
|
-
type: 'klavis' as const,
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const builtinMeta = builtinList.find((p) => p.identifier === identifier);
|
|
94
|
-
if (builtinMeta) {
|
|
95
|
-
return {
|
|
96
|
-
availableInWeb: builtinMeta.availableInWeb,
|
|
97
|
-
avatar: builtinMeta.meta.avatar,
|
|
98
|
-
isInstalled: true,
|
|
99
|
-
title: builtinMeta.meta.title,
|
|
100
|
-
type: 'builtin' as const,
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const installedMeta = installedPluginList.find((p) => p.identifier === identifier);
|
|
105
|
-
if (installedMeta) {
|
|
106
|
-
return {
|
|
107
|
-
availableInWeb: true,
|
|
108
|
-
avatar: installedMeta.avatar,
|
|
109
|
-
isInstalled: true,
|
|
110
|
-
title: installedMeta.title,
|
|
111
|
-
type: 'plugin' as const,
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return null;
|
|
116
|
-
}, [identifier, builtinList, installedPluginList, isKlavisEnabledInEnv, allKlavisServers]);
|
|
117
|
-
|
|
118
|
-
// Fetch from remote if not found locally
|
|
119
|
-
const usePluginDetail = useDiscoverStore((s) => s.usePluginDetail);
|
|
120
|
-
const { data: remoteData, isLoading } = usePluginDetail({
|
|
121
|
-
identifier: !localMeta && !isInstalled ? identifier : undefined,
|
|
122
|
-
withManifest: false,
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
// Determine final metadata
|
|
126
|
-
const meta = localMeta || {
|
|
127
|
-
availableInWeb: true,
|
|
128
|
-
avatar: remoteData?.avatar,
|
|
129
|
-
isInstalled: false,
|
|
130
|
-
title: remoteData?.title || identifier,
|
|
131
|
-
type: 'plugin' as const,
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
const displayTitle = isLoading ? 'Loading...' : meta.title;
|
|
135
|
-
const isDesktopOnly = !meta.availableInWeb;
|
|
136
|
-
|
|
137
|
-
// Render icon based on type
|
|
138
|
-
const renderIcon = () => {
|
|
139
|
-
if (!meta.isInstalled) {
|
|
140
|
-
return <AlertCircle className={styles.warningIcon} size={14} />;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Klavis type has icon property
|
|
144
|
-
if (meta.type === 'klavis' && 'icon' in meta && 'label' in meta) {
|
|
145
|
-
return <KlavisIcon icon={meta.icon} label={meta.label} />;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Builtin type has avatar
|
|
149
|
-
if (meta.type === 'builtin' && 'avatar' in meta && meta.avatar) {
|
|
150
|
-
return <Avatar avatar={meta.avatar} shape={'square'} size={16} style={{ flexShrink: 0 }} />;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Plugin type
|
|
154
|
-
if ('avatar' in meta) {
|
|
155
|
-
return <PluginAvatar avatar={meta.avatar} size={16} />;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return null;
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
// Build display text
|
|
162
|
-
const getDisplayText = () => {
|
|
163
|
-
let text = displayTitle;
|
|
164
|
-
if (isDesktopOnly) {
|
|
165
|
-
text += ` (${t('tools.desktopOnly', { defaultValue: 'Desktop Only' })})`;
|
|
166
|
-
}
|
|
167
|
-
if (!meta.isInstalled) {
|
|
168
|
-
text += ` (${t('tools.notInstalled', { defaultValue: 'Not Installed' })})`;
|
|
169
|
-
}
|
|
170
|
-
return text;
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
return (
|
|
174
|
-
<Tag
|
|
175
|
-
className={styles.tag}
|
|
176
|
-
closable
|
|
177
|
-
closeIcon={<X size={12} />}
|
|
178
|
-
color={meta.isInstalled ? undefined : 'error'}
|
|
179
|
-
icon={renderIcon()}
|
|
180
|
-
onClose={onRemove}
|
|
181
|
-
title={
|
|
182
|
-
meta.isInstalled
|
|
183
|
-
? undefined
|
|
184
|
-
: t('tools.notInstalledWarning', { defaultValue: 'This tool is not installed' })
|
|
185
|
-
}
|
|
186
|
-
variant={isDarkMode ? 'filled' : 'outlined'}
|
|
187
|
-
>
|
|
188
|
-
{getDisplayText()}
|
|
189
|
-
</Tag>
|
|
190
|
-
);
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
PluginTag.displayName = 'PluginTag';
|
|
194
|
-
|
|
195
|
-
export default PluginTag;
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { KLAVIS_SERVER_TYPES, type KlavisServerType } from '@lobechat/const';
|
|
4
|
-
import { Avatar, Icon, Tag } from '@lobehub/ui';
|
|
5
|
-
import { createStaticStyles, cssVar } from 'antd-style';
|
|
6
|
-
import isEqual from 'fast-deep-equal';
|
|
7
|
-
import { AlertCircle, X } from 'lucide-react';
|
|
8
|
-
import Image from 'next/image';
|
|
9
|
-
import React, { memo, useMemo } from 'react';
|
|
10
|
-
import { useTranslation } from 'react-i18next';
|
|
11
|
-
|
|
12
|
-
import PluginAvatar from '@/components/Plugins/PluginAvatar';
|
|
13
|
-
import { useIsDark } from '@/hooks/useIsDark';
|
|
14
|
-
import { useDiscoverStore } from '@/store/discover';
|
|
15
|
-
import { serverConfigSelectors, useServerConfigStore } from '@/store/serverConfig';
|
|
16
|
-
import { useToolStore } from '@/store/tool';
|
|
17
|
-
import {
|
|
18
|
-
builtinToolSelectors,
|
|
19
|
-
klavisStoreSelectors,
|
|
20
|
-
pluginSelectors,
|
|
21
|
-
} from '@/store/tool/selectors';
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Klavis 服务器图标组件
|
|
25
|
-
*/
|
|
26
|
-
const KlavisIcon = memo<Pick<KlavisServerType, 'icon' | 'label'>>(({ icon, label }) => {
|
|
27
|
-
if (typeof icon === 'string') {
|
|
28
|
-
return (
|
|
29
|
-
<Image alt={label} height={16} src={icon} style={{ flexShrink: 0 }} unoptimized width={16} />
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return <Icon fill={cssVar.colorText} icon={icon} size={16} />;
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
const styles = createStaticStyles(({ css, cssVar }) => ({
|
|
37
|
-
notInstalledTag: css`
|
|
38
|
-
border-color: ${cssVar.colorWarningBorder};
|
|
39
|
-
background: ${cssVar.colorWarningBg};
|
|
40
|
-
`,
|
|
41
|
-
tag: css`
|
|
42
|
-
height: 28px !important;
|
|
43
|
-
border-radius: ${cssVar.borderRadiusSM}px !important;
|
|
44
|
-
`,
|
|
45
|
-
warningIcon: css`
|
|
46
|
-
flex-shrink: 0;
|
|
47
|
-
color: ${cssVar.colorWarning};
|
|
48
|
-
`,
|
|
49
|
-
}));
|
|
50
|
-
|
|
51
|
-
interface PluginTagProps {
|
|
52
|
-
onRemove: (e: React.MouseEvent) => void;
|
|
53
|
-
pluginId: string | { enabled: boolean; identifier: string; settings: Record<string, any> };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const PluginTag = memo<PluginTagProps>(({ pluginId, onRemove }) => {
|
|
57
|
-
const isDarkMode = useIsDark();
|
|
58
|
-
const { t } = useTranslation('setting');
|
|
59
|
-
|
|
60
|
-
// Extract identifier
|
|
61
|
-
const identifier = typeof pluginId === 'string' ? pluginId : pluginId?.identifier;
|
|
62
|
-
|
|
63
|
-
// Get local plugin lists
|
|
64
|
-
const builtinList = useToolStore(builtinToolSelectors.metaList, isEqual);
|
|
65
|
-
const installedPluginList = useToolStore(pluginSelectors.installedPluginMetaList, isEqual);
|
|
66
|
-
|
|
67
|
-
// Klavis 相关状态
|
|
68
|
-
const allKlavisServers = useToolStore(klavisStoreSelectors.getServers, isEqual);
|
|
69
|
-
const isKlavisEnabledInEnv = useServerConfigStore(serverConfigSelectors.enableKlavis);
|
|
70
|
-
|
|
71
|
-
// Check if plugin is installed
|
|
72
|
-
const isInstalled = useToolStore(pluginSelectors.isPluginInstalled(identifier));
|
|
73
|
-
|
|
74
|
-
// Try to find in local lists first (including Klavis)
|
|
75
|
-
const localMeta = useMemo(() => {
|
|
76
|
-
// Check if it's a Klavis server type
|
|
77
|
-
if (isKlavisEnabledInEnv) {
|
|
78
|
-
const klavisType = KLAVIS_SERVER_TYPES.find((type) => type.identifier === identifier);
|
|
79
|
-
if (klavisType) {
|
|
80
|
-
// Check if this Klavis server is connected
|
|
81
|
-
const connectedServer = allKlavisServers.find((s) => s.identifier === identifier);
|
|
82
|
-
return {
|
|
83
|
-
icon: klavisType.icon,
|
|
84
|
-
isInstalled: !!connectedServer,
|
|
85
|
-
label: klavisType.label,
|
|
86
|
-
title: klavisType.label,
|
|
87
|
-
type: 'klavis' as const,
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const builtinMeta = builtinList.find((p) => p.identifier === identifier);
|
|
93
|
-
if (builtinMeta) {
|
|
94
|
-
return {
|
|
95
|
-
avatar: builtinMeta.meta.avatar,
|
|
96
|
-
isInstalled: true,
|
|
97
|
-
title: builtinMeta.meta.title,
|
|
98
|
-
type: 'builtin' as const,
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const installedMeta = installedPluginList.find((p) => p.identifier === identifier);
|
|
103
|
-
if (installedMeta) {
|
|
104
|
-
return {
|
|
105
|
-
avatar: installedMeta.avatar,
|
|
106
|
-
isInstalled: true,
|
|
107
|
-
title: installedMeta.title,
|
|
108
|
-
type: 'plugin' as const,
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return null;
|
|
113
|
-
}, [identifier, builtinList, installedPluginList, isKlavisEnabledInEnv, allKlavisServers]);
|
|
114
|
-
|
|
115
|
-
// Fetch from remote if not found locally
|
|
116
|
-
const usePluginDetail = useDiscoverStore((s) => s.usePluginDetail);
|
|
117
|
-
const { data: remoteData, isLoading } = usePluginDetail({
|
|
118
|
-
identifier: !localMeta && !isInstalled ? identifier : undefined,
|
|
119
|
-
withManifest: false,
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
// Determine final metadata
|
|
123
|
-
const meta = localMeta || {
|
|
124
|
-
avatar: remoteData?.avatar,
|
|
125
|
-
isInstalled: false,
|
|
126
|
-
title: remoteData?.title || identifier,
|
|
127
|
-
type: 'plugin' as const,
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
const displayTitle = isLoading ? 'Loading...' : meta.title;
|
|
131
|
-
|
|
132
|
-
// Render icon based on type
|
|
133
|
-
const renderIcon = () => {
|
|
134
|
-
if (!meta.isInstalled) {
|
|
135
|
-
return <AlertCircle className={styles.warningIcon} size={14} />;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Klavis type has icon property
|
|
139
|
-
if (meta.type === 'klavis' && 'icon' in meta && 'label' in meta) {
|
|
140
|
-
return <KlavisIcon icon={meta.icon} label={meta.label} />;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Builtin type has avatar
|
|
144
|
-
if (meta.type === 'builtin' && 'avatar' in meta && meta.avatar) {
|
|
145
|
-
return <Avatar avatar={meta.avatar} shape={'square'} size={16} style={{ flexShrink: 0 }} />;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Plugin type
|
|
149
|
-
if ('avatar' in meta) {
|
|
150
|
-
return <PluginAvatar avatar={meta.avatar} size={16} />;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return null;
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
return (
|
|
157
|
-
<Tag
|
|
158
|
-
className={styles.tag}
|
|
159
|
-
closable
|
|
160
|
-
closeIcon={<X size={12} />}
|
|
161
|
-
color={meta.isInstalled ? undefined : 'error'}
|
|
162
|
-
icon={renderIcon()}
|
|
163
|
-
onClose={onRemove}
|
|
164
|
-
title={
|
|
165
|
-
meta.isInstalled
|
|
166
|
-
? undefined
|
|
167
|
-
: t('tools.notInstalledWarning', { defaultValue: 'This tool is not installed' })
|
|
168
|
-
}
|
|
169
|
-
variant={isDarkMode ? 'filled' : 'outlined'}
|
|
170
|
-
>
|
|
171
|
-
{!meta.isInstalled
|
|
172
|
-
? `${displayTitle} (${t('tools.notInstalled', { defaultValue: 'Not Installed' })})`
|
|
173
|
-
: displayTitle}
|
|
174
|
-
</Tag>
|
|
175
|
-
);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
PluginTag.displayName = 'PluginTag';
|
|
179
|
-
|
|
180
|
-
export default PluginTag;
|