@lobehub/chat 0.162.23 → 0.162.25

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.
Files changed (54) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +8 -8
  3. package/README.zh-CN.md +8 -8
  4. package/docs/self-hosting/platform/zeabur.mdx +1 -1
  5. package/docs/self-hosting/platform/zeabur.zh-CN.mdx +1 -1
  6. package/locales/ar/common.json +28 -1
  7. package/locales/bg-BG/common.json +28 -1
  8. package/locales/de-DE/common.json +28 -1
  9. package/locales/en-US/common.json +28 -1
  10. package/locales/es-ES/common.json +28 -1
  11. package/locales/fr-FR/common.json +28 -1
  12. package/locales/it-IT/common.json +28 -1
  13. package/locales/ja-JP/common.json +28 -1
  14. package/locales/ko-KR/common.json +28 -1
  15. package/locales/nl-NL/common.json +28 -1
  16. package/locales/pl-PL/common.json +28 -1
  17. package/locales/pt-BR/common.json +28 -1
  18. package/locales/ru-RU/common.json +28 -1
  19. package/locales/tr-TR/common.json +28 -1
  20. package/locales/vi-VN/common.json +28 -1
  21. package/locales/zh-CN/common.json +28 -1
  22. package/locales/zh-TW/common.json +28 -1
  23. package/package.json +2 -2
  24. package/src/app/(main)/settings/@category/default.tsx +1 -2
  25. package/src/app/(main)/settings/_layout/Desktop/index.tsx +4 -1
  26. package/src/app/(main)/settings/_layout/Mobile/index.tsx +7 -1
  27. package/src/app/(main)/settings/about/features/AboutList.tsx +13 -120
  28. package/src/app/(main)/settings/about/features/Analytics.tsx +1 -1
  29. package/src/app/(main)/settings/about/features/ItemCard.tsx +45 -0
  30. package/src/app/(main)/settings/about/features/ItemLink.tsx +32 -0
  31. package/src/app/(main)/settings/about/features/Version.tsx +75 -0
  32. package/src/app/(main)/settings/about/index.tsx +118 -25
  33. package/src/app/(main)/settings/features/Footer.tsx +80 -10
  34. package/src/app/(main)/settings/llm/components/Footer.tsx +14 -5
  35. package/src/app/(main)/settings/llm/components/ProviderConfig/index.tsx +11 -1
  36. package/src/app/(main)/settings/llm/index.tsx +3 -0
  37. package/src/app/(main)/settings/system-agent/page.tsx +11 -11
  38. package/src/app/@modal/_layout/SettingModalLayout.tsx +4 -2
  39. package/src/app/api/middleware/auth/index.ts +1 -1
  40. package/src/components/GuideModal/index.tsx +77 -0
  41. package/src/components/GuideVideo/index.tsx +30 -0
  42. package/src/const/url.ts +6 -1
  43. package/src/features/AgentSetting/AgentModal/index.tsx +6 -7
  44. package/src/features/Conversation/Error/OllamaBizError/SetupGuide.tsx +177 -173
  45. package/src/features/Conversation/Error/style.tsx +56 -31
  46. package/src/features/User/UserPanel/useMenu.tsx +2 -2
  47. package/src/locales/default/common.ts +27 -2
  48. package/src/services/ollama.ts +2 -2
  49. package/src/store/user/slices/modelList/selectors/keyVaults.test.ts +201 -0
  50. package/src/store/user/slices/modelList/selectors/keyVaults.ts +15 -3
  51. package/src/store/user/slices/modelList/selectors/modelConfig.test.ts +29 -1
  52. package/src/store/user/slices/modelList/selectors/modelConfig.ts +21 -1
  53. package/src/types/user/settings/keyVaults.ts +1 -1
  54. package/src/app/(main)/settings/about/features/Item.tsx +0 -50
@@ -1,7 +1,7 @@
1
1
  import { Avatar } from '@lobehub/ui';
2
2
  import { createStyles } from 'antd-style';
3
3
  import { ReactNode, memo } from 'react';
4
- import { Center, Flexbox } from 'react-layout-kit';
4
+ import { Center, CenterProps, Flexbox } from 'react-layout-kit';
5
5
 
6
6
  export const useStyles = createStyles(({ css, token }) => ({
7
7
  container: css`
@@ -14,38 +14,63 @@ export const useStyles = createStyles(({ css, token }) => ({
14
14
  color: ${token.colorTextTertiary};
15
15
  text-align: center;
16
16
  `,
17
+ form: css`
18
+ width: 100%;
19
+ max-width: 300px;
20
+ `,
17
21
  }));
18
22
 
19
- export const ErrorActionContainer = memo<{ children: ReactNode }>(({ children }) => {
20
- const { styles } = useStyles();
23
+ export const ErrorActionContainer = memo<CenterProps>(
24
+ ({ children, className, gap = 24, padding = 24, ...rest }) => {
25
+ const { cx, styles } = useStyles();
21
26
 
22
- return (
23
- <Center className={styles.container} gap={24} padding={24}>
24
- {children}
25
- </Center>
26
- );
27
- });
27
+ return (
28
+ <Center className={cx(styles.container, className)} gap={gap} padding={padding} {...rest}>
29
+ {children}
30
+ </Center>
31
+ );
32
+ },
33
+ );
28
34
 
29
- export const FormAction = memo<{
30
- avatar: ReactNode;
31
- background?: string;
32
- children: ReactNode;
33
- description: string;
34
- title: string;
35
- }>(({ children, background, title, description, avatar }) => {
36
- const { styles, theme } = useStyles();
35
+ export const FormAction = memo<
36
+ {
37
+ animation?: boolean;
38
+ avatar: ReactNode;
39
+ background?: string;
40
+ description: string;
41
+ title: string;
42
+ } & CenterProps
43
+ >(
44
+ ({
45
+ children,
46
+ background,
47
+ title,
48
+ description,
49
+ avatar,
50
+ animation,
51
+ className,
52
+ gap = 16,
53
+ ...rest
54
+ }) => {
55
+ const { cx, styles, theme } = useStyles();
37
56
 
38
- return (
39
- <Center gap={16} style={{ maxWidth: 300, width: '100%' }}>
40
- <Avatar
41
- avatar={avatar}
42
- background={background ?? theme.colorFillContent}
43
- gap={12}
44
- size={80}
45
- />
46
- <Flexbox style={{ fontSize: 20, textAlign: 'center' }}>{title}</Flexbox>
47
- <Flexbox className={styles.desc}>{description}</Flexbox>
48
- {children}
49
- </Center>
50
- );
51
- });
57
+ return (
58
+ <Center className={cx(styles.form, className)} gap={gap} {...rest}>
59
+ <Avatar
60
+ animation={animation}
61
+ avatar={avatar}
62
+ background={background ?? theme.colorFillContent}
63
+ gap={12}
64
+ size={80}
65
+ />
66
+ <Flexbox gap={8} width={'100%'}>
67
+ <Flexbox style={{ fontSize: 18, fontWeight: 'bold', textAlign: 'center' }}>
68
+ {title}
69
+ </Flexbox>
70
+ <Flexbox className={styles.desc}>{description}</Flexbox>
71
+ </Flexbox>
72
+ {children}
73
+ </Center>
74
+ );
75
+ },
76
+ );
@@ -20,7 +20,7 @@ import { Flexbox } from 'react-layout-kit';
20
20
  import urlJoin from 'url-join';
21
21
 
22
22
  import type { MenuProps } from '@/components/Menu';
23
- import { DISCORD, DOCUMENTS, EMAIL_SUPPORT, GITHUB_ISSUES } from '@/const/url';
23
+ import { DISCORD, DOCUMENTS, EMAIL_SUPPORT, GITHUB_ISSUES, mailTo } from '@/const/url';
24
24
  import DataImporter from '@/features/DataImporter';
25
25
  import { useOpenSettings } from '@/hooks/useInterceptingRoutes';
26
26
  import { usePWAInstall } from '@/hooks/usePWAInstall';
@@ -190,7 +190,7 @@ export const useMenu = () => {
190
190
  icon: <Icon icon={Mail} />,
191
191
  key: 'email',
192
192
  label: (
193
- <Link href={`mailto:${EMAIL_SUPPORT}`} target={'_blank'}>
193
+ <Link href={mailTo(EMAIL_SUPPORT)} target={'_blank'}>
194
194
  {t('userPanel.email')}
195
195
  </Link>
196
196
  ),
@@ -9,6 +9,7 @@ export default {
9
9
  cancel: '取消',
10
10
  changelog: '更新日志',
11
11
  close: '关闭',
12
+ contact: '联系我们',
12
13
  copy: '复制',
13
14
  copyFail: '复制失败',
14
15
  copySuccess: '复制成功',
@@ -35,8 +36,26 @@ export default {
35
36
  },
36
37
  feedback: '反馈与建议',
37
38
  follow: '在 {{name}} 上关注我们',
39
+ footer: {
40
+ action: {
41
+ feedback: '分享您宝贵的建议',
42
+ star: '在 GitHub 给添加星标',
43
+ },
44
+ and: '并',
45
+ feedback: {
46
+ action: '分享反馈',
47
+ desc: '您的每一个想法和建议对我们来说都弥足珍贵,我们迫不及待地想知道您的看法!欢迎联系我们提供产品功能和使用体验反馈,帮助我们将 LobeChat 建设得更好。',
48
+ title: '在 GitHub 分享您宝贵的反馈',
49
+ },
50
+ later: '稍后',
51
+ star: {
52
+ action: '点亮星标',
53
+ desc: '如果您喜爱我们的产品,并希望支持我们,可以去 GitHub 给我们点一颗星吗?这个小小的动作对我们来说意义重大,能激励我们为您持续提供特性体验。',
54
+ title: '在 GitHub 为我们点亮星标',
55
+ },
56
+ title: '喜欢我们的产品?',
57
+ },
38
58
  fullscreen: '全屏模式',
39
-
40
59
  historyRange: '历史范围',
41
60
  import: '导入配置',
42
61
  importModal: {
@@ -69,6 +88,7 @@ export default {
69
88
  speed: '上传速度',
70
89
  },
71
90
  },
91
+ information: '社区与资讯',
72
92
  installPWA: '安装浏览器应用 (PWA)',
73
93
  lang: {
74
94
  'ar': '阿拉伯语',
@@ -108,6 +128,11 @@ export default {
108
128
  'zh-TW': '繁体中文',
109
129
  },
110
130
  layoutInitializing: '正在加载布局...',
131
+ legal: '法律声明',
132
+ mail: {
133
+ business: '商务合作',
134
+ support: '邮件支持',
135
+ },
111
136
  noDescription: '暂无描述',
112
137
  oauth: 'SSO 登录',
113
138
  officialSite: '官方网站',
@@ -149,7 +174,6 @@ export default {
149
174
  title: '同步状态',
150
175
  unconnected: { tip: '信令服务器连接失败,将无法建立点对点通信频道,请检查网络后重试' },
151
176
  },
152
-
153
177
  tab: {
154
178
  chat: '会话',
155
179
  market: '发现',
@@ -188,4 +212,5 @@ export default {
188
212
  setting: '应用设置',
189
213
  usages: '用量统计',
190
214
  },
215
+ version: '版本',
191
216
  };
@@ -42,8 +42,8 @@ export class OllamaService {
42
42
  this._client.abort();
43
43
  };
44
44
 
45
- pullModel = async (model: string): Promise<AsyncGenerator<ProgressResponse>> => {
46
- let response: Response | AsyncGenerator<ProgressResponse>;
45
+ pullModel = async (model: string): Promise<AsyncIterable<ProgressResponse>> => {
46
+ let response: Response | AsyncIterable<ProgressResponse>;
47
47
  try {
48
48
  response = await this.getOllamaClient().pull({ insecure: true, model, stream: true });
49
49
  return response;
@@ -0,0 +1,201 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { UserStore } from '@/store/user';
4
+ import {
5
+ AWSBedrockKeyVault,
6
+ AzureOpenAIKeyVault,
7
+ OpenAICompatibleKeyVault,
8
+ } from '@/types/user/settings';
9
+ import { merge } from '@/utils/merge';
10
+
11
+ import { initialSettingsState } from '../../settings/initialState';
12
+ import { keyVaultsConfigSelectors } from './keyVaults';
13
+
14
+ describe('keyVaultsConfigSelectors', () => {
15
+ describe('isProviderEndpointNotEmpty', () => {
16
+ describe('OpenAICompatibleKeyVault', () => {
17
+ it('should return true if provider endpoint is not empty', () => {
18
+ const s = merge(initialSettingsState, {
19
+ settings: {
20
+ keyVaults: {
21
+ openai: {
22
+ endpoint: 'endpoint',
23
+ } as OpenAICompatibleKeyVault,
24
+ },
25
+ },
26
+ }) as unknown as UserStore;
27
+ expect(keyVaultsConfigSelectors.isProviderEndpointNotEmpty('openai')(s)).toBe(true);
28
+ });
29
+
30
+ it('should return false if provider endpoint is empty', () => {
31
+ const s = merge(initialSettingsState, {
32
+ settings: {
33
+ keyVaults: {
34
+ openai: {
35
+ endpoint: undefined,
36
+ } as OpenAICompatibleKeyVault,
37
+ },
38
+ },
39
+ }) as unknown as UserStore;
40
+ expect(keyVaultsConfigSelectors.isProviderEndpointNotEmpty('openai')(s)).toBe(false);
41
+ });
42
+ });
43
+
44
+ describe('AzureOpenAIKeyVault', () => {
45
+ it('should return true if provider endpoint is not empty', () => {
46
+ const s = merge(initialSettingsState, {
47
+ settings: {
48
+ keyVaults: {
49
+ azure: {
50
+ baseURL: 'baseURL',
51
+ } as AzureOpenAIKeyVault,
52
+ },
53
+ },
54
+ }) as unknown as UserStore;
55
+ expect(keyVaultsConfigSelectors.isProviderEndpointNotEmpty('azure')(s)).toBe(true);
56
+ });
57
+
58
+ it('should return false if provider endpoint is empty', () => {
59
+ const s = merge(initialSettingsState, {
60
+ settings: {
61
+ keyVaults: {
62
+ azure: {
63
+ baseURL: undefined,
64
+ } as AzureOpenAIKeyVault,
65
+ },
66
+ },
67
+ }) as unknown as UserStore;
68
+ expect(keyVaultsConfigSelectors.isProviderEndpointNotEmpty('azure')(s)).toBe(false);
69
+ });
70
+ });
71
+
72
+ // Always return false for AWSBedrockKeyVault
73
+ describe('AWSBedrockKeyVault', () => {
74
+ it('should return false if provider region is not empty for AWSBedrockKeyVault', () => {
75
+ const s = merge(initialSettingsState, {
76
+ settings: {
77
+ keyVaults: {
78
+ bedrock: {
79
+ region: 'region',
80
+ } as AWSBedrockKeyVault,
81
+ },
82
+ },
83
+ }) as unknown as UserStore;
84
+ expect(keyVaultsConfigSelectors.isProviderEndpointNotEmpty('bedrock')(s)).toBe(false);
85
+ });
86
+
87
+ it('should return false if provider region is empty for AWSBedrockKeyVault', () => {
88
+ const s = merge(initialSettingsState, {
89
+ settings: {
90
+ keyVaults: {
91
+ bedrock: {
92
+ region: undefined,
93
+ } as AWSBedrockKeyVault,
94
+ },
95
+ },
96
+ }) as unknown as UserStore;
97
+ expect(keyVaultsConfigSelectors.isProviderEndpointNotEmpty('bedrock')(s)).toBe(false);
98
+ });
99
+ });
100
+ });
101
+
102
+ describe('isProviderApiKeyNotEmpty', () => {
103
+ describe('OpenAICompatibleKeyVault', () => {
104
+ it('should return true if provider apikey is not empty', () => {
105
+ const s = merge(initialSettingsState, {
106
+ settings: {
107
+ keyVaults: {
108
+ openai: {
109
+ apiKey: 'apikey',
110
+ } as OpenAICompatibleKeyVault,
111
+ },
112
+ },
113
+ }) as unknown as UserStore;
114
+ expect(keyVaultsConfigSelectors.isProviderApiKeyNotEmpty('openai')(s)).toBe(true);
115
+ });
116
+
117
+ it('should return false if provider apikey is empty', () => {
118
+ const s = merge(initialSettingsState, {
119
+ settings: {
120
+ keyVaults: {
121
+ openai: {
122
+ apiKey: undefined,
123
+ } as OpenAICompatibleKeyVault,
124
+ },
125
+ },
126
+ }) as unknown as UserStore;
127
+ expect(keyVaultsConfigSelectors.isProviderApiKeyNotEmpty('openai')(s)).toBe(false);
128
+ });
129
+ });
130
+
131
+ describe('AzureOpenAIKeyVault', () => {
132
+ it('should return true if provider apikey is not empty', () => {
133
+ const s = merge(initialSettingsState, {
134
+ settings: {
135
+ keyVaults: {
136
+ azure: {
137
+ apiKey: 'apikey',
138
+ } as AzureOpenAIKeyVault,
139
+ },
140
+ },
141
+ }) as unknown as UserStore;
142
+ expect(keyVaultsConfigSelectors.isProviderApiKeyNotEmpty('azure')(s)).toBe(true);
143
+ });
144
+
145
+ it('should return false if provider apikey is empty', () => {
146
+ const s = merge(initialSettingsState, {
147
+ settings: {
148
+ keyVaults: {
149
+ azure: {
150
+ apiKey: undefined,
151
+ } as AzureOpenAIKeyVault,
152
+ },
153
+ },
154
+ }) as unknown as UserStore;
155
+ expect(keyVaultsConfigSelectors.isProviderApiKeyNotEmpty('azure')(s)).toBe(false);
156
+ });
157
+ });
158
+
159
+ describe('AWSBedrockKeyVault', () => {
160
+ it('should return true if provider accessKeyId is not empty for AWSBedrockKeyVault', () => {
161
+ const s = merge(initialSettingsState, {
162
+ settings: {
163
+ keyVaults: {
164
+ bedrock: {
165
+ accessKeyId: 'accessKeyId',
166
+ } as AWSBedrockKeyVault,
167
+ },
168
+ },
169
+ }) as unknown as UserStore;
170
+ expect(keyVaultsConfigSelectors.isProviderApiKeyNotEmpty('bedrock')(s)).toBe(true);
171
+ });
172
+
173
+ it('should return true if provider secretAccessKey is not empty for AWSBedrockKeyVault', () => {
174
+ const s = merge(initialSettingsState, {
175
+ settings: {
176
+ keyVaults: {
177
+ bedrock: {
178
+ secretAccessKey: 'secretAccessKey',
179
+ } as AWSBedrockKeyVault,
180
+ },
181
+ },
182
+ }) as unknown as UserStore;
183
+ expect(keyVaultsConfigSelectors.isProviderApiKeyNotEmpty('bedrock')(s)).toBe(true);
184
+ });
185
+
186
+ it('should return false if provider accessKeyId and secretAccessKey are both empty for AWSBedrockKeyVault', () => {
187
+ const s = merge(initialSettingsState, {
188
+ settings: {
189
+ keyVaults: {
190
+ bedrock: {
191
+ accessKeyId: undefined,
192
+ secretAccessKey: undefined,
193
+ } as AWSBedrockKeyVault,
194
+ },
195
+ },
196
+ }) as unknown as UserStore;
197
+ expect(keyVaultsConfigSelectors.isProviderApiKeyNotEmpty('bedrock')(s)).toBe(false);
198
+ });
199
+ });
200
+ });
201
+ });
@@ -1,5 +1,7 @@
1
1
  import { UserStore } from '@/store/user';
2
2
  import {
3
+ AWSBedrockKeyVault,
4
+ AzureOpenAIKeyVault,
3
5
  GlobalLLMProviderKey,
4
6
  OpenAICompatibleKeyVault,
5
7
  UserKeyVaults,
@@ -15,10 +17,19 @@ const bedrockConfig = (s: UserStore) => keyVaultsSettings(s).bedrock || {};
15
17
  const ollamaConfig = (s: UserStore) => keyVaultsSettings(s).ollama || {};
16
18
  const azureConfig = (s: UserStore) => keyVaultsSettings(s).azure || {};
17
19
  const getVaultByProvider = (provider: GlobalLLMProviderKey) => (s: UserStore) =>
18
- (keyVaultsSettings(s)[provider] || {}) as OpenAICompatibleKeyVault;
20
+ (keyVaultsSettings(s)[provider] || {}) as OpenAICompatibleKeyVault &
21
+ AzureOpenAIKeyVault &
22
+ AWSBedrockKeyVault;
19
23
 
20
- const isProviderEndpointNotEmpty = (provider: string) => (s: UserStore) =>
21
- !!getVaultByProvider(provider as GlobalLLMProviderKey)(s)?.baseURL;
24
+ const isProviderEndpointNotEmpty = (provider: string) => (s: UserStore) => {
25
+ const vault = getVaultByProvider(provider as GlobalLLMProviderKey)(s);
26
+ return !!vault?.baseURL || !!vault?.endpoint;
27
+ };
28
+
29
+ const isProviderApiKeyNotEmpty = (provider: string) => (s: UserStore) => {
30
+ const vault = getVaultByProvider(provider as GlobalLLMProviderKey)(s);
31
+ return !!vault?.apiKey || !!vault?.accessKeyId || !!vault?.secretAccessKey;
32
+ };
22
33
 
23
34
  const password = (s: UserStore) => keyVaultsSettings(s).password || '';
24
35
 
@@ -26,6 +37,7 @@ export const keyVaultsConfigSelectors = {
26
37
  azureConfig,
27
38
  bedrockConfig,
28
39
  getVaultByProvider,
40
+ isProviderApiKeyNotEmpty,
29
41
  isProviderEndpointNotEmpty,
30
42
  ollamaConfig,
31
43
  openAIConfig,
@@ -35,6 +35,7 @@ describe('modelConfigSelectors', () => {
35
35
  });
36
36
 
37
37
  describe('isProviderFetchOnClient', () => {
38
+ // The next 4 case are base on the rules on https://github.com/lobehub/lobe-chat/pull/2753
38
39
  it('client fetch should disabled on default', () => {
39
40
  const s = merge(initialSettingsState, {
40
41
  settings: {
@@ -46,16 +47,43 @@ describe('modelConfigSelectors', () => {
46
47
  },
47
48
  },
48
49
  } as UserSettingsState) as unknown as UserStore;
50
+ expect(modelConfigSelectors.isProviderFetchOnClient('azure')(s)).toBe(false);
51
+ });
49
52
 
53
+ it('client fetch should disabled if no apikey or endpoint provided even user set it enabled', () => {
54
+ const s = merge(initialSettingsState, {
55
+ settings: {
56
+ languageModel: {
57
+ azure: { fetchOnClient: true },
58
+ },
59
+ },
60
+ } as UserSettingsState) as unknown as UserStore;
50
61
  expect(modelConfigSelectors.isProviderFetchOnClient('azure')(s)).toBe(false);
51
62
  });
52
63
 
53
- it('client fetch should enabled if user set it enabled', () => {
64
+ it('client fetch should enable if only endpoint provided', () => {
65
+ const s = merge(initialSettingsState, {
66
+ settings: {
67
+ languageModel: {
68
+ azure: { fetchOnClient: false },
69
+ },
70
+ keyVaults: {
71
+ azure: { endpoint: 'https://example.com' },
72
+ },
73
+ },
74
+ } as UserSettingsState) as unknown as UserStore;
75
+ expect(modelConfigSelectors.isProviderFetchOnClient('azure')(s)).toBe(true);
76
+ });
77
+
78
+ it('client fetch should control by user when a apikey or endpoint provided', () => {
54
79
  const s = merge(initialSettingsState, {
55
80
  settings: {
56
81
  languageModel: {
57
82
  azure: { fetchOnClient: true },
58
83
  },
84
+ keyVaults: {
85
+ azure: { apiKey: 'some-key' },
86
+ },
59
87
  },
60
88
  } as UserSettingsState) as unknown as UserStore;
61
89
  expect(modelConfigSelectors.isProviderFetchOnClient('azure')(s)).toBe(true);
@@ -1,15 +1,35 @@
1
+ import { UserStore } from '@/store/user';
1
2
  import { GlobalLLMProviderKey } from '@/types/user/settings';
2
3
 
3
- import { UserStore } from '../../../store';
4
4
  import { currentLLMSettings, getProviderConfigById } from '../../settings/selectors/settings';
5
+ import { keyVaultsConfigSelectors } from './keyVaults';
5
6
 
6
7
  const isProviderEnabled = (provider: GlobalLLMProviderKey) => (s: UserStore) =>
7
8
  getProviderConfigById(provider)(s)?.enabled || false;
8
9
 
10
+ /**
11
+ * @description The conditions to enable client fetch
12
+ * 1. If no baseUrl and apikey input, force on Server.
13
+ * 2. If only contains baseUrl, force on Client
14
+ * 3. Follow the user settings.
15
+ * 4. On Server, by default.
16
+ */
9
17
  const isProviderFetchOnClient = (provider: GlobalLLMProviderKey | string) => (s: UserStore) => {
10
18
  const config = getProviderConfigById(provider)(s);
19
+
20
+ // 1. If no baseUrl and apikey input, force on Server.
21
+ const isProviderEndpointNotEmpty =
22
+ keyVaultsConfigSelectors.isProviderEndpointNotEmpty(provider)(s);
23
+ const isProviderApiKeyNotEmpty = keyVaultsConfigSelectors.isProviderApiKeyNotEmpty(provider)(s);
24
+ if (!isProviderEndpointNotEmpty && !isProviderApiKeyNotEmpty) return false;
25
+
26
+ // 2. If only contains baseUrl, force on Client
27
+ if (isProviderEndpointNotEmpty && !isProviderApiKeyNotEmpty) return true;
28
+
29
+ // 3. Follow the user settings.
11
30
  if (typeof config?.fetchOnClient !== 'undefined') return config?.fetchOnClient;
12
31
 
32
+ // 4. On Server, by default.
13
33
  return false;
14
34
  };
15
35
 
@@ -3,7 +3,7 @@ export interface OpenAICompatibleKeyVault {
3
3
  baseURL?: string;
4
4
  }
5
5
 
6
- interface AzureOpenAIKeyVault {
6
+ export interface AzureOpenAIKeyVault {
7
7
  apiKey?: string;
8
8
  apiVersion?: string;
9
9
  endpoint?: string;
@@ -1,50 +0,0 @@
1
- import { Icon, List } from '@lobehub/ui';
2
- import { createStyles, useResponsive } from 'antd-style';
3
- import { ChevronRight, type LucideIcon } from 'lucide-react';
4
- import { CSSProperties, ReactNode, memo } from 'react';
5
-
6
- const { Item } = List;
7
-
8
- const useStyles = createStyles(({ css, token, responsive }) => ({
9
- container: css`
10
- position: relative;
11
- padding-top: 16px;
12
- padding-bottom: 16px;
13
- border-radius: ${token.borderRadius}px;
14
- ${responsive.mobile} {
15
- border-radius: 0;
16
- }
17
- `,
18
- noHover: css`
19
- pointer-events: none;
20
- `,
21
- }));
22
-
23
- export interface ItemProps {
24
- active?: boolean;
25
- className?: string;
26
- hoverable?: boolean;
27
- icon: LucideIcon;
28
- label: ReactNode;
29
- style?: CSSProperties;
30
- }
31
-
32
- const SettingItem = memo<ItemProps>(
33
- ({ label, icon, hoverable = true, active = false, style, className }) => {
34
- const { cx, styles } = useStyles();
35
- const { mobile } = useResponsive();
36
- return (
37
- <Item
38
- active={active}
39
- avatar={<Icon icon={icon} size={{ fontSize: 20 }} />}
40
- className={cx(styles.container, !hoverable && styles.noHover, className)}
41
- style={style}
42
- title={label as string}
43
- >
44
- {mobile && <Icon icon={ChevronRight} size={{ fontSize: 16 }} />}
45
- </Item>
46
- );
47
- },
48
- );
49
-
50
- export default SettingItem;