@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.
@@ -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;
@@ -0,0 +1,2 @@
1
+ export { default as AgentTool, type AgentToolProps } from './AgentTool';
2
+ export { default as PluginTag, type PluginTagProps } from './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;