@lobehub/lobehub 2.0.0-next.15 → 2.0.0-next.17

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 (111) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +2 -45
  3. package/README.zh-CN.md +2 -45
  4. package/changelog/v1.json +18 -0
  5. package/docs/self-hosting/advanced/feature-flags.mdx +0 -1
  6. package/docs/self-hosting/advanced/feature-flags.zh-CN.mdx +0 -1
  7. package/e2e/src/features/discover/smoke.feature +34 -1
  8. package/e2e/src/steps/discover/smoke.steps.ts +116 -4
  9. package/package.json +1 -1
  10. package/packages/model-runtime/src/utils/googleErrorParser.test.ts +125 -0
  11. package/packages/model-runtime/src/utils/googleErrorParser.ts +103 -77
  12. package/packages/types/src/serverConfig.ts +2 -6
  13. package/src/app/[variants]/(auth)/signup/[[...signup]]/page.tsx +1 -8
  14. package/src/app/[variants]/(main)/(mobile)/me/(home)/features/UserBanner.tsx +3 -6
  15. package/src/app/[variants]/(main)/discover/(list)/features/Pagination.tsx +1 -0
  16. package/src/app/[variants]/(main)/discover/(list)/features/SortButton/index.tsx +1 -1
  17. package/src/app/[variants]/(main)/discover/(list)/mcp/features/List/Item.tsx +1 -0
  18. package/src/app/[variants]/(main)/discover/(list)/model/features/List/Item.tsx +1 -0
  19. package/src/app/[variants]/(main)/discover/(list)/provider/features/List/Item.tsx +1 -0
  20. package/src/app/[variants]/(main)/discover/components/CategoryMenu.tsx +9 -1
  21. package/src/app/[variants]/(main)/labs/components/LabCard.tsx +3 -1
  22. package/src/app/[variants]/(main)/settings/provider/detail/azure/index.tsx +5 -7
  23. package/src/components/InvalidAPIKey/APIKeyForm/Bedrock.tsx +8 -13
  24. package/src/config/featureFlags/schema.test.ts +0 -2
  25. package/src/config/featureFlags/schema.ts +0 -6
  26. package/src/config/modelProviders/ai21.ts +1 -16
  27. package/src/config/modelProviders/ai302.ts +1 -128
  28. package/src/config/modelProviders/ai360.ts +1 -32
  29. package/src/config/modelProviders/anthropic.ts +1 -71
  30. package/src/config/modelProviders/azure.ts +1 -51
  31. package/src/config/modelProviders/baichuan.ts +1 -57
  32. package/src/config/modelProviders/bedrock.ts +1 -276
  33. package/src/config/modelProviders/cloudflare.ts +1 -64
  34. package/src/config/modelProviders/deepseek.ts +1 -19
  35. package/src/config/modelProviders/fireworksai.ts +1 -174
  36. package/src/config/modelProviders/giteeai.ts +1 -135
  37. package/src/config/modelProviders/github.ts +1 -254
  38. package/src/config/modelProviders/google.ts +1 -130
  39. package/src/config/modelProviders/groq.ts +1 -119
  40. package/src/config/modelProviders/higress.ts +1 -1713
  41. package/src/config/modelProviders/huggingface.ts +1 -54
  42. package/src/config/modelProviders/hunyuan.ts +1 -83
  43. package/src/config/modelProviders/infiniai.ts +1 -74
  44. package/src/config/modelProviders/internlm.ts +1 -20
  45. package/src/config/modelProviders/mistral.ts +1 -95
  46. package/src/config/modelProviders/modelscope.ts +1 -27
  47. package/src/config/modelProviders/moonshot.ts +1 -29
  48. package/src/config/modelProviders/novita.ts +1 -105
  49. package/src/config/modelProviders/ollama.ts +1 -325
  50. package/src/config/modelProviders/openai.ts +1 -242
  51. package/src/config/modelProviders/openrouter.ts +1 -240
  52. package/src/config/modelProviders/perplexity.ts +1 -45
  53. package/src/config/modelProviders/ppio.ts +1 -152
  54. package/src/config/modelProviders/qiniu.ts +1 -18
  55. package/src/config/modelProviders/qwen.ts +1 -245
  56. package/src/config/modelProviders/search1api.ts +1 -34
  57. package/src/config/modelProviders/sensenova.ts +1 -69
  58. package/src/config/modelProviders/siliconcloud.ts +1 -417
  59. package/src/config/modelProviders/spark.ts +1 -59
  60. package/src/config/modelProviders/stepfun.ts +1 -98
  61. package/src/config/modelProviders/taichu.ts +1 -18
  62. package/src/config/modelProviders/togetherai.ts +1 -274
  63. package/src/config/modelProviders/upstage.ts +1 -28
  64. package/src/config/modelProviders/wenxin.ts +1 -140
  65. package/src/config/modelProviders/xai.ts +1 -38
  66. package/src/config/modelProviders/zeroone.ts +1 -81
  67. package/src/config/modelProviders/zhipu.ts +1 -108
  68. package/src/helpers/isCanUseFC.ts +0 -8
  69. package/src/hooks/useEnabledChatModels.ts +0 -8
  70. package/src/hooks/useModelContextWindowTokens.ts +0 -8
  71. package/src/hooks/useModelHasContextWindowToken.ts +1 -10
  72. package/src/hooks/useModelSupportFiles.ts +1 -11
  73. package/src/hooks/useModelSupportReasoning.ts +1 -11
  74. package/src/hooks/useModelSupportToolUse.ts +1 -11
  75. package/src/hooks/useModelSupportVision.ts +1 -11
  76. package/src/layout/AuthProvider/Clerk/index.tsx +2 -16
  77. package/src/server/globalConfig/index.ts +0 -23
  78. package/src/server/routers/lambda/config/__snapshots__/index.test.ts.snap +175 -12
  79. package/src/server/routers/lambda/config/index.test.ts +36 -28
  80. package/src/services/chat/chat.test.ts +12 -0
  81. package/src/services/chat/helper.ts +7 -31
  82. package/src/services/models.ts +2 -11
  83. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +41 -14
  84. package/src/store/global/store.ts +1 -7
  85. package/src/store/user/initialState.ts +1 -7
  86. package/src/store/user/selectors.ts +1 -5
  87. package/src/store/user/slices/common/action.ts +5 -4
  88. package/src/store/user/slices/settings/selectors/index.ts +1 -0
  89. package/src/store/user/slices/settings/selectors/keyVaults.ts +21 -0
  90. package/src/store/user/store.ts +0 -3
  91. package/src/tools/web-browsing/Render/Search/ConfigForm/Form.tsx +1 -1
  92. package/packages/utils/src/_deprecated/__snapshots__/parseModels.test.ts.snap +0 -104
  93. package/packages/utils/src/_deprecated/parseModels.test.ts +0 -287
  94. package/packages/utils/src/_deprecated/parseModels.ts +0 -165
  95. package/src/hooks/_header.ts +0 -23
  96. package/src/server/globalConfig/_deprecated.test.ts +0 -92
  97. package/src/server/globalConfig/_deprecated.ts +0 -41
  98. package/src/store/global/actions/clientDb.ts +0 -67
  99. package/src/store/user/slices/modelList/__snapshots__/action.test.ts.snap +0 -12
  100. package/src/store/user/slices/modelList/action.test.ts +0 -359
  101. package/src/store/user/slices/modelList/action.ts +0 -223
  102. package/src/store/user/slices/modelList/initialState.ts +0 -15
  103. package/src/store/user/slices/modelList/reducers/customModelCard.test.ts +0 -204
  104. package/src/store/user/slices/modelList/reducers/customModelCard.ts +0 -64
  105. package/src/store/user/slices/modelList/selectors/index.ts +0 -3
  106. package/src/store/user/slices/modelList/selectors/keyVaults.test.ts +0 -201
  107. package/src/store/user/slices/modelList/selectors/keyVaults.ts +0 -50
  108. package/src/store/user/slices/modelList/selectors/modelConfig.test.ts +0 -219
  109. package/src/store/user/slices/modelList/selectors/modelConfig.ts +0 -95
  110. package/src/store/user/slices/modelList/selectors/modelProvider.test.ts +0 -138
  111. package/src/store/user/slices/modelList/selectors/modelProvider.ts +0 -170
@@ -17,64 +17,81 @@ export interface GoogleChatError {
17
17
  export type GoogleChatErrors = GoogleChatError[];
18
18
 
19
19
  /**
20
- * 清理错误消息,移除格式化字符和多余的空格
21
- * @param message - 原始错误消息
22
- * @returns 清理后的错误消息
20
+ * Clean error message by removing formatting characters and extra spaces
21
+ * @param message - Original error message
22
+ * @returns Cleaned error message
23
23
  */
24
24
  export function cleanErrorMessage(message: string): string {
25
25
  return message
26
- .replaceAll(/^\*\s*/g, '') // 移除开头的星号和空格
27
- .replaceAll('\\n', '\n') // 转换转义的换行符
28
- .replaceAll(/\n+/g, ' ') // 将多个换行符替换为单个空格
29
- .trim(); // 去除首尾空格
26
+ .replaceAll(/^\*\s*/g, '') // Remove leading asterisks and spaces
27
+ .replaceAll('\\n', '\n') // Convert escaped newlines
28
+ .replaceAll(/\n+/g, ' ') // Replace multiple newlines with single space
29
+ .trim(); // Trim leading/trailing spaces
30
30
  }
31
31
 
32
32
  /**
33
- * 从错误消息中提取状态码信息
34
- * @param message - 错误消息
35
- * @returns 提取的错误详情和前缀
33
+ * Extract status code information from error message
34
+ * @param message - Error message
35
+ * @returns Extracted error details and prefix
36
36
  */
37
37
  export function extractStatusCodeFromError(message: string): {
38
38
  errorDetails: any;
39
39
  prefix: string;
40
40
  } {
41
- // 使用正则表达式匹配状态码部分 [数字 描述文本]
42
- const regex = /^(.*?)(\[\d+ [^\]]+])(.*)$/;
43
- const match = message.match(regex);
44
-
45
- if (match) {
46
- const prefix = match[1].trim();
47
- const statusCodeWithBrackets = match[2].trim();
48
- const messageContent = match[3].trim();
49
-
50
- // 提取状态码数字
51
- const statusCodeMatch = statusCodeWithBrackets.match(/\[(\d+)/);
52
- const statusCode = statusCodeMatch ? parseInt(statusCodeMatch[1]) : null;
53
-
54
- // 创建包含状态码和消息的JSON
55
- const resultJson = {
56
- message: messageContent,
57
- statusCode: statusCode,
58
- statusCodeText: statusCodeWithBrackets,
59
- };
41
+ // Match status code pattern [number description text]
42
+ // Use string methods instead of regex to avoid ReDoS attacks
43
+ // We need to find a bracket that contains a status code (3-digit number followed by space and text)
44
+
45
+ let searchStart = 0;
46
+ // eslint-disable-next-line no-constant-condition
47
+ while (true) {
48
+ const openBracketIndex = message.indexOf('[', searchStart);
49
+ if (openBracketIndex === -1) {
50
+ return { errorDetails: null, prefix: message };
51
+ }
60
52
 
61
- return {
62
- errorDetails: resultJson,
63
- prefix: prefix,
64
- };
65
- }
53
+ const closeBracketIndex = message.indexOf(']', openBracketIndex);
54
+ if (closeBracketIndex === -1) {
55
+ return { errorDetails: null, prefix: message };
56
+ }
66
57
 
67
- // 如果无法匹配,返回原始消息
68
- return {
69
- errorDetails: null,
70
- prefix: message,
71
- };
58
+ const bracketContent = message.slice(openBracketIndex + 1, closeBracketIndex).trim();
59
+
60
+ // Find the first space to separate status code from description
61
+ const spaceIndex = bracketContent.indexOf(' ');
62
+ if (spaceIndex !== -1) {
63
+ const statusCodeStr = bracketContent.slice(0, spaceIndex);
64
+ const statusCode = parseInt(statusCodeStr, 10);
65
+
66
+ // Validate that statusCode is a valid HTTP status code (3 digits)
67
+ if (!isNaN(statusCode) && statusCode >= 100 && statusCode < 600) {
68
+ const statusText = bracketContent.slice(spaceIndex + 1).trim();
69
+ const prefix = message.slice(0, openBracketIndex).trim();
70
+ const messageContent = message.slice(closeBracketIndex + 1).trim();
71
+
72
+ // Create JSON containing status code and message
73
+ const resultJson = {
74
+ message: messageContent,
75
+ statusCode: statusCode,
76
+ statusCodeText: `[${statusCode} ${statusText}]`,
77
+ };
78
+
79
+ return {
80
+ errorDetails: resultJson,
81
+ prefix: prefix,
82
+ };
83
+ }
84
+ }
85
+
86
+ // Move to next bracket
87
+ searchStart = openBracketIndex + 1;
88
+ }
72
89
  }
73
90
 
74
91
  /**
75
- * 解析Google AI API返回的错误消息
76
- * @param message - 原始错误消息
77
- * @returns 解析后的错误对象和错误类型
92
+ * Parse error message from Google AI API
93
+ * @param message - Original error message
94
+ * @returns Parsed error object and error type
78
95
  */
79
96
  export function parseGoogleErrorMessage(message: string): ParsedError {
80
97
  const defaultError = {
@@ -82,12 +99,12 @@ export function parseGoogleErrorMessage(message: string): ParsedError {
82
99
  errorType: AgentRuntimeErrorType.ProviderBizError,
83
100
  };
84
101
 
85
- // 快速识别特殊错误
102
+ // Quick identification of special errors
86
103
  if (message.includes('location is not supported')) {
87
104
  return { error: { message }, errorType: AgentRuntimeErrorType.LocationNotSupportError };
88
105
  }
89
106
 
90
- // 统一的错误类型判断函数
107
+ // Unified error type determination function
91
108
  const getErrorType = (code: number | null, message: string): ILobeAgentRuntimeErrorType => {
92
109
  if (code === 400 && message.includes('API key not valid')) {
93
110
  return AgentRuntimeErrorType.InvalidProviderAPIKey;
@@ -97,30 +114,30 @@ export function parseGoogleErrorMessage(message: string): ParsedError {
97
114
  return AgentRuntimeErrorType.ProviderBizError;
98
115
  };
99
116
 
100
- // 递归解析JSON,处理嵌套的JSON字符串
117
+ // Recursively parse JSON, handling nested JSON strings
101
118
  const parseJsonRecursively = (str: string, maxDepth: number = 5): any => {
102
119
  if (maxDepth <= 0) return null;
103
120
 
104
121
  try {
105
122
  const parsed = JSON.parse(str);
106
123
 
107
- // 如果解析出的对象包含error字段
124
+ // If parsed object contains error field
108
125
  if (parsed && typeof parsed === 'object' && parsed.error) {
109
126
  const errorInfo = parsed.error;
110
127
 
111
- // 清理错误消息
128
+ // Clean error message
112
129
  if (typeof errorInfo.message === 'string') {
113
130
  errorInfo.message = cleanErrorMessage(errorInfo.message);
114
131
 
115
- // 如果error.message还是一个JSON字符串,继续递归解析
132
+ // If error.message is still a JSON string, continue recursive parsing
116
133
  try {
117
134
  const nestedResult = parseJsonRecursively(errorInfo.message, maxDepth - 1);
118
- // 只有当深层结果包含带有 code error 对象时,才优先返回深层结果
135
+ // Only return deeper result if it contains an error object with code
119
136
  if (nestedResult && nestedResult.error && nestedResult.error.code) {
120
137
  return nestedResult;
121
138
  }
122
139
  } catch {
123
- // 如果嵌套解析失败,使用当前层的信息
140
+ // If nested parsing fails, use current layer info
124
141
  }
125
142
  }
126
143
 
@@ -133,31 +150,40 @@ export function parseGoogleErrorMessage(message: string): ParsedError {
133
150
  }
134
151
  };
135
152
 
136
- // 1. 处理 "got status: UNAVAILABLE. {JSON}" 格式
137
- const statusJsonMatch = message.match(/got status: (\w+)\.\s*({.*})$/);
138
- if (statusJsonMatch) {
139
- const statusFromMessage = statusJsonMatch[1];
140
- const jsonPart = statusJsonMatch[2];
141
-
142
- const parsedError = parseJsonRecursively(jsonPart);
143
- if (parsedError && parsedError.error) {
144
- const errorInfo = parsedError.error;
145
- const finalMessage = errorInfo.message || message;
146
- const finalCode = errorInfo.code || null;
147
- const finalStatus = errorInfo.status || statusFromMessage;
148
-
149
- return {
150
- error: {
151
- code: finalCode,
152
- message: finalMessage,
153
- status: finalStatus,
154
- },
155
- errorType: getErrorType(finalCode, finalMessage),
156
- };
153
+ // 1. Handle "got status: UNAVAILABLE. {JSON}" format
154
+ const statusPrefix = 'got status: ';
155
+ const statusPrefixIndex = message.indexOf(statusPrefix);
156
+ if (statusPrefixIndex !== -1) {
157
+ const afterPrefix = message.slice(statusPrefixIndex + statusPrefix.length);
158
+ const dotIndex = afterPrefix.indexOf('.');
159
+ if (dotIndex !== -1) {
160
+ const statusFromMessage = afterPrefix.slice(0, dotIndex).trim();
161
+ const afterDot = afterPrefix.slice(dotIndex + 1).trim();
162
+ const braceIndex = afterDot.indexOf('{');
163
+ if (braceIndex !== -1) {
164
+ const jsonPart = afterDot.slice(braceIndex);
165
+
166
+ const parsedError = parseJsonRecursively(jsonPart);
167
+ if (parsedError && parsedError.error) {
168
+ const errorInfo = parsedError.error;
169
+ const finalMessage = errorInfo.message || message;
170
+ const finalCode = errorInfo.code || null;
171
+ const finalStatus = errorInfo.status || statusFromMessage;
172
+
173
+ return {
174
+ error: {
175
+ code: finalCode,
176
+ message: finalMessage,
177
+ status: finalStatus,
178
+ },
179
+ errorType: getErrorType(finalCode, finalMessage),
180
+ };
181
+ }
182
+ }
157
183
  }
158
184
  }
159
185
 
160
- // 2. 尝试直接解析整个消息作为JSON
186
+ // 2. Try to parse entire message as JSON directly
161
187
  const directParsed = parseJsonRecursively(message);
162
188
  if (directParsed && directParsed.error) {
163
189
  const errorInfo = directParsed.error;
@@ -175,7 +201,7 @@ export function parseGoogleErrorMessage(message: string): ParsedError {
175
201
  };
176
202
  }
177
203
 
178
- // 3. 处理嵌套JSON格式,特别是message字段包含JSON的情况
204
+ // 3. Handle nested JSON format, especially when message field contains JSON
179
205
  try {
180
206
  const firstLevelParsed = JSON.parse(message);
181
207
  if (firstLevelParsed && firstLevelParsed.error && firstLevelParsed.error.message) {
@@ -197,10 +223,10 @@ export function parseGoogleErrorMessage(message: string): ParsedError {
197
223
  }
198
224
  }
199
225
  } catch {
200
- // 继续其他解析方式
226
+ // Continue with other parsing methods
201
227
  }
202
228
 
203
- // 4. 原有的数组格式解析逻辑
229
+ // 4. Original array format parsing logic
204
230
  const startIndex = message.lastIndexOf('[');
205
231
  if (startIndex !== -1) {
206
232
  try {
@@ -214,11 +240,11 @@ export function parseGoogleErrorMessage(message: string): ParsedError {
214
240
 
215
241
  return { error: json, errorType: AgentRuntimeErrorType.ProviderBizError };
216
242
  } catch {
217
- // 忽略解析错误
243
+ // Ignore parsing errors
218
244
  }
219
245
  }
220
246
 
221
- // 5. 使用状态码提取逻辑作为最后的后备方案
247
+ // 5. Use status code extraction logic as last fallback
222
248
  const errorObj = extractStatusCodeFromError(message);
223
249
  if (errorObj.errorDetails) {
224
250
  return { error: errorObj.errorDetails, errorType: AgentRuntimeErrorType.ProviderBizError };
@@ -15,9 +15,9 @@ export interface ServerModelProviderConfig {
15
15
  enabledModels?: string[];
16
16
  fetchOnClient?: boolean;
17
17
  /**
18
- * the model cards defined in server
18
+ * the model lists defined in server
19
19
  */
20
- serverModelCards?: ChatModelCard[];
20
+ serverModelLists?: ChatModelCard[];
21
21
  }
22
22
 
23
23
  export type ServerLanguageModel = Partial<Record<GlobalLLMProviderKey, ServerModelProviderConfig>>;
@@ -32,10 +32,6 @@ export interface GlobalServerConfig {
32
32
  */
33
33
  enabledOAuthSSO?: boolean;
34
34
  image?: PartialDeep<UserImageConfig>;
35
- /**
36
- * @deprecated
37
- */
38
- languageModel?: ServerLanguageModel;
39
35
  oAuthSSOProviders?: string[];
40
36
  systemAgent?: PartialDeep<UserSystemAgentConfig>;
41
37
  telemetry: {
@@ -1,7 +1,6 @@
1
1
  import { SignUp } from '@clerk/nextjs';
2
- import { notFound, redirect } from 'next/navigation';
2
+ import { notFound } from 'next/navigation';
3
3
 
4
- import { serverFeatureFlags } from '@/config/featureFlags';
5
4
  import { enableClerk } from '@/const/auth';
6
5
  import { metadataModule } from '@/server/metadata';
7
6
  import { translation } from '@/server/translation';
@@ -21,12 +20,6 @@ export const generateMetadata = async (props: DynamicLayoutProps) => {
21
20
  const Page = () => {
22
21
  if (!enableClerk) return notFound();
23
22
 
24
- const enableClerkSignUp = serverFeatureFlags().enableClerkSignUp;
25
-
26
- if (!enableClerkSignUp) {
27
- redirect('/login');
28
- }
29
-
30
23
  return <SignUp path="/signup" />;
31
24
  };
32
25
 
@@ -6,7 +6,6 @@ import { memo } from 'react';
6
6
  import { Flexbox } from 'react-layout-kit';
7
7
 
8
8
  import { enableAuth, enableNextAuth } from '@/const/auth';
9
- import { isDeprecatedEdition } from '@/const/version';
10
9
  import DataStatistics from '@/features/User/DataStatistics';
11
10
  import UserInfo from '@/features/User/UserInfo';
12
11
  import UserLoginOrSignup from '@/features/User/UserLoginOrSignup/Community';
@@ -25,11 +24,9 @@ const UserBanner = memo(() => {
25
24
  <Link href={'/profile'} style={{ color: 'inherit' }}>
26
25
  <UserInfo />
27
26
  </Link>
28
- {!isDeprecatedEdition && (
29
- <Link href={'/profile/stats'} style={{ color: 'inherit' }}>
30
- <DataStatistics paddingInline={12} />
31
- </Link>
32
- )}
27
+ <Link href={'/profile/stats'} style={{ color: 'inherit' }}>
28
+ <DataStatistics paddingInline={12} />
29
+ </Link>
33
30
  </>
34
31
  ) : (
35
32
  <UserLoginOrSignup
@@ -52,6 +52,7 @@ const Pagination = memo<PaginationProps>(({ tab, currentPage, total, pageSize })
52
52
  <Page
53
53
  className={styles.page}
54
54
  current={page ? Number(page) : currentPage}
55
+ data-testid="pagination"
55
56
  onChange={handlePageChange}
56
57
  pageSize={pageSize}
57
58
  showSizeChanger={false}
@@ -174,7 +174,7 @@ const SortButton = memo(() => {
174
174
  }}
175
175
  trigger={['click', 'hover']}
176
176
  >
177
- <Button icon={<Icon icon={ArrowDownWideNarrow} />} type={'text'}>
177
+ <Button data-testid="sort-dropdown" icon={<Icon icon={ArrowDownWideNarrow} />} type={'text'}>
178
178
  {activeItem.label}
179
179
  <Icon icon={ChevronDown} />
180
180
  </Button>
@@ -82,6 +82,7 @@ const McpItem = memo<DiscoverMcpItem>(
82
82
  return (
83
83
  <Block
84
84
  clickable
85
+ data-testid="mcp-item"
85
86
  height={'100%'}
86
87
  onClick={() => {
87
88
  navigate(link);
@@ -61,6 +61,7 @@ const ModelItem = memo<DiscoverModelItem>(
61
61
  return (
62
62
  <Block
63
63
  clickable
64
+ data-testid="model-item"
64
65
  height={'100%'}
65
66
  onClick={() => {
66
67
  navigate(link);
@@ -54,6 +54,7 @@ const ProviderItem = memo<DiscoverProviderItem>(
54
54
  return (
55
55
  <Block
56
56
  clickable
57
+ data-testid="provider-item"
57
58
  height={'100%'}
58
59
  onClick={() => {
59
60
  navigate(link);
@@ -31,7 +31,15 @@ const useStyles = createStyles(({ css, prefixCls }) => {
31
31
  const CategoryMenu = memo<MenuProps>(({ style, ...rest }) => {
32
32
  const { styles } = useStyles();
33
33
 
34
- return <Menu className={styles.menu} mode="inline" style={style} {...rest} />;
34
+ return (
35
+ <Menu
36
+ className={styles.menu}
37
+ data-testid="category-menu"
38
+ mode="inline"
39
+ style={style}
40
+ {...rest}
41
+ />
42
+ );
35
43
  });
36
44
 
37
45
  export default CategoryMenu;
@@ -71,7 +71,9 @@ const LabCard = memo<PropsWithChildren<LabCardProps>>(
71
71
  <div className={styles.card}>
72
72
  <div className={styles.row}>
73
73
  <div className={styles.thumb}>
74
- {cover && <Image alt={title} fill src={cover} style={{ objectFit: 'cover' }} />}
74
+ {cover && (
75
+ <Image alt={title} fill src={cover} style={{ objectFit: 'cover' }} unoptimized />
76
+ )}
75
77
  </div>
76
78
  <Flexbox gap={6}>
77
79
  <div className={styles.title}>{title}</div>
@@ -7,9 +7,7 @@ import { useTranslation } from 'react-i18next';
7
7
 
8
8
  import { FormInput, FormPassword } from '@/components/FormInput';
9
9
  import { AzureProviderCard } from '@/config/modelProviders';
10
- import { aiProviderSelectors, useAiInfraStore } from '@/store/aiInfra';
11
- import { useUserStore } from '@/store/user';
12
- import { modelProviderSelectors } from '@/store/user/selectors';
10
+ import { aiModelSelectors, aiProviderSelectors, useAiInfraStore } from '@/store/aiInfra';
13
11
 
14
12
  import { KeyVaultsConfigKey, LLMProviderApiTokenKey, LLMProviderBaseUrlKey } from '../../const';
15
13
  import { SkeletonInput } from '../../features/ProviderConfig';
@@ -35,11 +33,11 @@ const useProviderCard = (): ProviderItem => {
35
33
  const { styles } = useStyles();
36
34
 
37
35
  // Get the first model card's deployment name as the check model
38
- const checkModel = useUserStore((s) => {
39
- const chatModelCards = modelProviderSelectors.getModelCardsById(providerKey)(s);
36
+ const checkModel = useAiInfraStore((s) => {
37
+ const modelList = aiModelSelectors.enabledAiProviderModelList(s);
40
38
 
41
- if (chatModelCards.length > 0) {
42
- return chatModelCards[0].deploymentName;
39
+ if (modelList.length > 0) {
40
+ return modelList[0].id;
43
41
  }
44
42
 
45
43
  return 'gpt-35-turbo';
@@ -7,21 +7,16 @@ import { memo, useState } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
 
9
9
  import { FormAction } from '@/features/Conversation/Error/style';
10
- import { useUserStore } from '@/store/user';
11
- import { keyVaultsConfigSelectors } from '@/store/user/selectors';
10
+ import { aiProviderSelectors, useAiInfraStore } from '@/store/aiInfra';
12
11
 
13
12
  const BedrockForm = memo<{ description: string }>(({ description }) => {
14
13
  const { t } = useTranslation('modelProvider');
15
14
  const [showRegion, setShow] = useState(false);
16
15
  const [showSessionToken, setShowSessionToken] = useState(false);
17
16
 
18
- const [accessKeyId, secretAccessKey, sessionToken, region, setConfig] = useUserStore((s) => [
19
- keyVaultsConfigSelectors.bedrockConfig(s).accessKeyId,
20
- keyVaultsConfigSelectors.bedrockConfig(s).secretAccessKey,
21
- keyVaultsConfigSelectors.bedrockConfig(s).sessionToken,
22
- keyVaultsConfigSelectors.bedrockConfig(s).region,
23
- s.updateKeyVaultConfig,
24
- ]);
17
+ const config = useAiInfraStore(aiProviderSelectors.providerKeyVaults(ModelProvider.Bedrock));
18
+ const setConfig = useAiInfraStore((s) => s.updateAiProviderConfig);
19
+ const { accessKeyId, secretAccessKey, sessionToken, region } = config || {};
25
20
 
26
21
  const theme = useTheme();
27
22
  return (
@@ -33,7 +28,7 @@ const BedrockForm = memo<{ description: string }>(({ description }) => {
33
28
  <InputPassword
34
29
  autoComplete={'new-password'}
35
30
  onChange={(e) => {
36
- setConfig(ModelProvider.Bedrock, { accessKeyId: e.target.value });
31
+ setConfig(ModelProvider.Bedrock, { keyVaults: { accessKeyId: e.target.value } });
37
32
  }}
38
33
  placeholder={'Aws Access Key Id'}
39
34
  value={accessKeyId}
@@ -42,7 +37,7 @@ const BedrockForm = memo<{ description: string }>(({ description }) => {
42
37
  <InputPassword
43
38
  autoComplete={'new-password'}
44
39
  onChange={(e) => {
45
- setConfig(ModelProvider.Bedrock, { secretAccessKey: e.target.value });
40
+ setConfig(ModelProvider.Bedrock, { keyVaults: { secretAccessKey: e.target.value } });
46
41
  }}
47
42
  placeholder={'Aws Secret Access Key'}
48
43
  value={secretAccessKey}
@@ -52,7 +47,7 @@ const BedrockForm = memo<{ description: string }>(({ description }) => {
52
47
  <InputPassword
53
48
  autoComplete={'new-password'}
54
49
  onChange={(e) => {
55
- setConfig(ModelProvider.Bedrock, { sessionToken: e.target.value });
50
+ setConfig(ModelProvider.Bedrock, { keyVaults: { sessionToken: e.target.value } });
56
51
  }}
57
52
  placeholder={'Aws Session Token'}
58
53
  value={sessionToken}
@@ -73,7 +68,7 @@ const BedrockForm = memo<{ description: string }>(({ description }) => {
73
68
  {showRegion ? (
74
69
  <Select
75
70
  onChange={(region) => {
76
- setConfig('bedrock', { region });
71
+ setConfig('bedrock', { keyVaults: { region } });
77
72
  }}
78
73
  options={['us-east-1', 'us-west-2', 'ap-southeast-1', 'eu-central-1'].map((i) => ({
79
74
  label: i,
@@ -111,7 +111,6 @@ describe('mapFeatureFlagsEnvToState', () => {
111
111
  plugins: true,
112
112
  knowledge_base: false,
113
113
  rag_eval: true,
114
- clerk_sign_up: false,
115
114
  market: true,
116
115
  speech_to_text: true,
117
116
  changelog: false,
@@ -140,7 +139,6 @@ describe('mapFeatureFlagsEnvToState', () => {
140
139
  showWelcomeSuggest: true,
141
140
  enableKnowledgeBase: false,
142
141
  enableRAGEval: true,
143
- enableClerkSignUp: false,
144
142
  showMarket: true,
145
143
  enableSTT: true,
146
144
  showPinList: true,
@@ -30,8 +30,6 @@ export const FeatureFlagsSchema = z.object({
30
30
  welcome_suggest: FeatureFlagValue.optional(),
31
31
  changelog: FeatureFlagValue.optional(),
32
32
 
33
- clerk_sign_up: FeatureFlagValue.optional(),
34
-
35
33
  market: FeatureFlagValue.optional(),
36
34
  knowledge_base: FeatureFlagValue.optional(),
37
35
 
@@ -93,8 +91,6 @@ export const DEFAULT_FEATURE_FLAGS: IFeatureFlags = {
93
91
  knowledge_base: true,
94
92
  rag_eval: false,
95
93
 
96
- clerk_sign_up: true,
97
-
98
94
  cloud_promotion: false,
99
95
 
100
96
  market: true,
@@ -133,8 +129,6 @@ export const mapFeatureFlagsEnvToState = (config: IFeatureFlags, userId?: string
133
129
  enableCheckUpdates: evaluateFeatureFlag(config.check_updates, userId),
134
130
  showWelcomeSuggest: evaluateFeatureFlag(config.welcome_suggest, userId),
135
131
 
136
- enableClerkSignUp: evaluateFeatureFlag(config.clerk_sign_up, userId),
137
-
138
132
  enableKnowledgeBase: evaluateFeatureFlag(config.knowledge_base, userId),
139
133
  enableRAGEval: evaluateFeatureFlag(config.rag_eval, userId),
140
134
 
@@ -2,22 +2,7 @@ import { ModelProviderCard } from '@/types/llm';
2
2
 
3
3
  // ref https://docs.ai21.com/reference/jamba-15-api-ref
4
4
  const Ai21: ModelProviderCard = {
5
- chatModels: [
6
- {
7
- contextWindowTokens: 256_000,
8
- displayName: 'Jamba 1.5 Mini',
9
- enabled: true,
10
- functionCall: true,
11
- id: 'jamba-1.5-mini',
12
- },
13
- {
14
- contextWindowTokens: 256_000,
15
- displayName: 'Jamba 1.5 Large',
16
- enabled: true,
17
- functionCall: true,
18
- id: 'jamba-1.5-large',
19
- },
20
- ],
5
+ chatModels: [],
21
6
  checkModel: 'jamba-mini',
22
7
  description: 'AI21 Labs 为企业构建基础模型和人工智能系统,加速生成性人工智能在生产中的应用。',
23
8
  id: 'ai21',