@lobehub/chat 0.162.16 → 0.162.18

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 (173) hide show
  1. package/.env.example +4 -0
  2. package/CHANGELOG.md +50 -0
  3. package/Dockerfile +3 -0
  4. package/README.md +1 -0
  5. package/README.zh-CN.md +1 -0
  6. package/docs/self-hosting/advanced/settings-url-share.mdx +89 -57
  7. package/docs/self-hosting/advanced/settings-url-share.zh-CN.mdx +87 -56
  8. package/docs/self-hosting/advanced/upstream-sync.mdx +1 -1
  9. package/docs/self-hosting/environment-variables/model-provider.mdx +10 -1
  10. package/docs/self-hosting/environment-variables/model-provider.zh-CN.mdx +9 -0
  11. package/docs/usage/features/multi-ai-providers.mdx +1 -0
  12. package/docs/usage/features/multi-ai-providers.zh-CN.mdx +1 -0
  13. package/locales/ar/error.json +7 -37
  14. package/locales/ar/modelProvider.json +15 -171
  15. package/locales/ar/setting.json +5 -8
  16. package/locales/bg-BG/error.json +6 -36
  17. package/locales/bg-BG/modelProvider.json +15 -171
  18. package/locales/bg-BG/setting.json +5 -8
  19. package/locales/de-DE/error.json +6 -36
  20. package/locales/de-DE/modelProvider.json +15 -171
  21. package/locales/de-DE/setting.json +5 -8
  22. package/locales/en-US/error.json +6 -36
  23. package/locales/en-US/modelProvider.json +15 -171
  24. package/locales/en-US/setting.json +6 -9
  25. package/locales/es-ES/error.json +6 -36
  26. package/locales/es-ES/modelProvider.json +15 -171
  27. package/locales/es-ES/setting.json +5 -8
  28. package/locales/fr-FR/error.json +6 -36
  29. package/locales/fr-FR/modelProvider.json +15 -171
  30. package/locales/fr-FR/setting.json +9 -12
  31. package/locales/it-IT/error.json +6 -36
  32. package/locales/it-IT/modelProvider.json +15 -171
  33. package/locales/it-IT/setting.json +5 -8
  34. package/locales/ja-JP/error.json +6 -36
  35. package/locales/ja-JP/modelProvider.json +15 -171
  36. package/locales/ja-JP/setting.json +5 -8
  37. package/locales/ko-KR/error.json +6 -36
  38. package/locales/ko-KR/modelProvider.json +15 -171
  39. package/locales/ko-KR/setting.json +5 -8
  40. package/locales/nl-NL/error.json +6 -36
  41. package/locales/nl-NL/modelProvider.json +15 -171
  42. package/locales/nl-NL/setting.json +5 -8
  43. package/locales/pl-PL/error.json +6 -36
  44. package/locales/pl-PL/modelProvider.json +15 -171
  45. package/locales/pl-PL/setting.json +6 -9
  46. package/locales/pt-BR/error.json +6 -36
  47. package/locales/pt-BR/modelProvider.json +15 -171
  48. package/locales/pt-BR/setting.json +5 -8
  49. package/locales/ru-RU/error.json +6 -36
  50. package/locales/ru-RU/modelProvider.json +15 -171
  51. package/locales/ru-RU/setting.json +5 -8
  52. package/locales/tr-TR/error.json +6 -36
  53. package/locales/tr-TR/modelProvider.json +15 -171
  54. package/locales/tr-TR/setting.json +5 -8
  55. package/locales/vi-VN/error.json +6 -36
  56. package/locales/vi-VN/modelProvider.json +15 -171
  57. package/locales/vi-VN/setting.json +5 -8
  58. package/locales/zh-CN/error.json +7 -37
  59. package/locales/zh-CN/modelProvider.json +15 -171
  60. package/locales/zh-CN/setting.json +5 -8
  61. package/locales/zh-TW/error.json +7 -37
  62. package/locales/zh-TW/modelProvider.json +15 -171
  63. package/locales/zh-TW/setting.json +5 -8
  64. package/package.json +1 -2
  65. package/src/app/(main)/settings/llm/ProviderList/Azure/index.tsx +107 -0
  66. package/src/app/(main)/settings/llm/ProviderList/Bedrock/index.tsx +68 -0
  67. package/src/app/(main)/settings/llm/ProviderList/Ollama/index.tsx +29 -0
  68. package/src/app/(main)/settings/llm/ProviderList/OpenAI/index.tsx +20 -0
  69. package/src/app/(main)/settings/llm/ProviderList/providers.tsx +141 -0
  70. package/src/app/(main)/settings/llm/components/ProviderConfig/index.tsx +18 -27
  71. package/src/app/(main)/settings/llm/index.tsx +7 -34
  72. package/src/app/(main)/settings/llm/type.ts +5 -0
  73. package/src/app/api/chat/[provider]/route.ts +8 -3
  74. package/src/app/api/chat/agentRuntime.test.ts +17 -0
  75. package/src/app/api/chat/agentRuntime.ts +7 -0
  76. package/src/app/api/errorResponse.test.ts +7 -89
  77. package/src/app/api/errorResponse.ts +6 -43
  78. package/src/app/api/middleware/auth/index.ts +7 -3
  79. package/src/components/ModelProviderIcon/index.tsx +5 -0
  80. package/src/components/ModelSelect/index.tsx +7 -10
  81. package/src/config/llm.ts +6 -0
  82. package/src/config/modelProviders/anthropic.ts +5 -0
  83. package/src/config/modelProviders/azure.ts +2 -0
  84. package/src/config/modelProviders/bedrock.ts +2 -0
  85. package/src/config/modelProviders/deepseek.ts +4 -1
  86. package/src/config/modelProviders/google.ts +5 -0
  87. package/src/config/modelProviders/groq.ts +5 -0
  88. package/src/config/modelProviders/index.ts +4 -0
  89. package/src/config/modelProviders/minimax.ts +2 -0
  90. package/src/config/modelProviders/mistral.ts +2 -0
  91. package/src/config/modelProviders/moonshot.ts +2 -0
  92. package/src/config/modelProviders/ollama.ts +4 -0
  93. package/src/config/modelProviders/openai.ts +3 -0
  94. package/src/config/modelProviders/openrouter.ts +3 -0
  95. package/src/config/modelProviders/perplexity.ts +5 -0
  96. package/src/config/modelProviders/qwen.ts +35 -0
  97. package/src/config/modelProviders/togetherai.ts +3 -0
  98. package/src/config/modelProviders/zeroone.ts +4 -2
  99. package/src/config/modelProviders/zhipu.ts +2 -0
  100. package/src/const/settings/llm.ts +5 -0
  101. package/src/features/Conversation/Error/APIKeyForm/ProviderApiKeyForm.tsx +6 -3
  102. package/src/features/Conversation/Error/APIKeyForm/ProviderAvatar.tsx +5 -0
  103. package/src/features/Conversation/Error/APIKeyForm/index.tsx +4 -0
  104. package/src/features/Conversation/Error/OllamaBizError/InvalidOllamaModel/index.tsx +11 -14
  105. package/src/features/Conversation/Error/index.tsx +29 -19
  106. package/src/features/Conversation/components/ChatItem/index.tsx +4 -13
  107. package/src/features/ModelSelect/index.tsx +1 -1
  108. package/src/features/ModelSwitchPanel/index.tsx +1 -1
  109. package/src/hooks/useProviderName.ts +8 -0
  110. package/src/libs/agent-runtime/AgentRuntime.ts +8 -1
  111. package/src/libs/agent-runtime/anthropic/index.test.ts +8 -5
  112. package/src/libs/agent-runtime/anthropic/index.ts +3 -3
  113. package/src/libs/agent-runtime/azureOpenai/index.test.ts +5 -2
  114. package/src/libs/agent-runtime/azureOpenai/index.ts +2 -2
  115. package/src/libs/agent-runtime/bedrock/index.test.ts +2 -2
  116. package/src/libs/agent-runtime/bedrock/index.ts +2 -2
  117. package/src/libs/agent-runtime/deepseek/index.test.ts +3 -2
  118. package/src/libs/agent-runtime/deepseek/index.ts +0 -5
  119. package/src/libs/agent-runtime/error.ts +11 -43
  120. package/src/libs/agent-runtime/google/index.test.ts +3 -4
  121. package/src/libs/agent-runtime/google/index.ts +4 -4
  122. package/src/libs/agent-runtime/groq/index.test.ts +3 -2
  123. package/src/libs/agent-runtime/groq/index.ts +0 -4
  124. package/src/libs/agent-runtime/index.ts +1 -0
  125. package/src/libs/agent-runtime/minimax/index.test.ts +3 -2
  126. package/src/libs/agent-runtime/minimax/index.ts +5 -5
  127. package/src/libs/agent-runtime/mistral/index.test.ts +2 -2
  128. package/src/libs/agent-runtime/mistral/index.ts +0 -5
  129. package/src/libs/agent-runtime/moonshot/index.test.ts +3 -2
  130. package/src/libs/agent-runtime/moonshot/index.ts +0 -5
  131. package/src/libs/agent-runtime/openrouter/index.test.ts +3 -2
  132. package/src/libs/agent-runtime/openrouter/index.ts +0 -6
  133. package/src/libs/agent-runtime/perplexity/index.test.ts +2 -2
  134. package/src/libs/agent-runtime/perplexity/index.ts +0 -5
  135. package/src/libs/agent-runtime/qwen/index.test.ts +251 -0
  136. package/src/libs/agent-runtime/qwen/index.ts +28 -0
  137. package/src/libs/agent-runtime/togetherai/index.test.ts +3 -2
  138. package/src/libs/agent-runtime/togetherai/index.ts +0 -5
  139. package/src/libs/agent-runtime/types/type.ts +1 -1
  140. package/src/libs/agent-runtime/utils/openaiCompatibleFactory/index.test.ts +123 -6
  141. package/src/libs/agent-runtime/utils/openaiCompatibleFactory/index.ts +12 -6
  142. package/src/libs/agent-runtime/utils/streams/openai.ts +1 -1
  143. package/src/libs/agent-runtime/zeroone/index.test.ts +3 -2
  144. package/src/libs/agent-runtime/zeroone/index.ts +1 -5
  145. package/src/libs/agent-runtime/zhipu/index.test.ts +8 -5
  146. package/src/libs/agent-runtime/zhipu/index.ts +2 -2
  147. package/src/locales/default/error.ts +10 -51
  148. package/src/locales/default/modelProvider.ts +12 -169
  149. package/src/locales/default/setting.ts +5 -8
  150. package/src/server/globalConfig/index.ts +2 -0
  151. package/src/services/__tests__/chat.test.ts +16 -0
  152. package/src/services/chat.ts +3 -0
  153. package/src/services/message/client.test.ts +4 -1
  154. package/src/types/fetch.ts +3 -1
  155. package/src/types/llm.ts +22 -0
  156. package/src/types/user/settings/keyVaults.ts +1 -0
  157. package/src/app/(main)/settings/llm/Anthropic/index.tsx +0 -25
  158. package/src/app/(main)/settings/llm/Azure/index.tsx +0 -110
  159. package/src/app/(main)/settings/llm/Bedrock/index.tsx +0 -74
  160. package/src/app/(main)/settings/llm/DeepSeek/index.tsx +0 -21
  161. package/src/app/(main)/settings/llm/Google/index.tsx +0 -31
  162. package/src/app/(main)/settings/llm/Groq/index.tsx +0 -26
  163. package/src/app/(main)/settings/llm/Minimax/index.tsx +0 -20
  164. package/src/app/(main)/settings/llm/Mistral/index.tsx +0 -20
  165. package/src/app/(main)/settings/llm/Moonshot/index.tsx +0 -28
  166. package/src/app/(main)/settings/llm/Ollama/index.tsx +0 -37
  167. package/src/app/(main)/settings/llm/OpenAI/index.tsx +0 -28
  168. package/src/app/(main)/settings/llm/OpenRouter/index.tsx +0 -21
  169. package/src/app/(main)/settings/llm/Perplexity/index.tsx +0 -23
  170. package/src/app/(main)/settings/llm/TogetherAI/index.tsx +0 -19
  171. package/src/app/(main)/settings/llm/ZeroOne/index.tsx +0 -20
  172. package/src/app/(main)/settings/llm/Zhipu/index.tsx +0 -18
  173. /package/src/app/(main)/settings/llm/{Ollama → ProviderList/Ollama}/Checker.tsx +0 -0
@@ -0,0 +1,141 @@
1
+ import {
2
+ Anthropic,
3
+ Claude,
4
+ DeepSeek,
5
+ Gemini,
6
+ Google,
7
+ Groq,
8
+ Minimax,
9
+ Mistral,
10
+ Moonshot,
11
+ OpenRouter,
12
+ Perplexity,
13
+ Together,
14
+ Tongyi,
15
+ ZeroOne,
16
+ Zhipu,
17
+ } from '@lobehub/icons';
18
+ import { Divider } from 'antd';
19
+ import { useTheme } from 'antd-style';
20
+ import { useMemo } from 'react';
21
+ import { Flexbox } from 'react-layout-kit';
22
+
23
+ import {
24
+ AnthropicProviderCard,
25
+ DeepSeekProviderCard,
26
+ GoogleProviderCard,
27
+ GroqProviderCard,
28
+ MinimaxProviderCard,
29
+ MistralProviderCard,
30
+ MoonshotProviderCard,
31
+ OpenRouterProviderCard,
32
+ PerplexityProviderCard,
33
+ QwenProviderCard,
34
+ TogetherAIProviderCard,
35
+ ZeroOneProviderCard,
36
+ ZhiPuProviderCard,
37
+ } from '@/config/modelProviders';
38
+
39
+ import { ProviderItem } from '../type';
40
+ import { useAzureProvider } from './Azure';
41
+ import { useBedrockProvider } from './Bedrock';
42
+ import { useOllamaProvider } from './Ollama';
43
+ import { useOpenAIProvider } from './OpenAI';
44
+
45
+ const AnthropicBrand = () => {
46
+ const { isDarkMode } = useTheme();
47
+ return <Anthropic.Text color={isDarkMode ? undefined : Claude.colorPrimary} size={15} />;
48
+ };
49
+
50
+ const MoonshotBrand = () => {
51
+ const theme = useTheme();
52
+ return (
53
+ <Moonshot.Combine
54
+ color={theme.isDarkMode ? theme.colorText : Moonshot.colorPrimary}
55
+ size={22}
56
+ />
57
+ );
58
+ };
59
+
60
+ const GroqBrand = () => {
61
+ const theme = useTheme();
62
+
63
+ return <Groq.Text color={theme.isDarkMode ? theme.colorText : Groq.colorPrimary} size={20} />;
64
+ };
65
+
66
+ const GoogleBrand = () => (
67
+ <Flexbox align={'center'} gap={8} horizontal>
68
+ <Google.BrandColor size={22} />
69
+ <Divider style={{ margin: '0 4px' }} type={'vertical'} />
70
+ <Gemini.Combine size={22} type={'color'} />
71
+ </Flexbox>
72
+ );
73
+
74
+ export const useProviderList = (): ProviderItem[] => {
75
+ const azureProvider = useAzureProvider();
76
+ const ollamaProvider = useOllamaProvider();
77
+ const openAIProvider = useOpenAIProvider();
78
+ const bedrockProvider = useBedrockProvider();
79
+
80
+ return useMemo(
81
+ () => [
82
+ openAIProvider,
83
+ ollamaProvider,
84
+ azureProvider,
85
+ {
86
+ ...GoogleProviderCard,
87
+ title: <GoogleBrand />,
88
+ },
89
+ {
90
+ ...AnthropicProviderCard,
91
+ title: <AnthropicBrand />,
92
+ },
93
+ bedrockProvider,
94
+ {
95
+ ...GroqProviderCard,
96
+ title: <GroqBrand />,
97
+ },
98
+ {
99
+ ...OpenRouterProviderCard,
100
+ title: <OpenRouter.Combine iconProps={{ color: OpenRouter.colorPrimary }} size={20} />,
101
+ },
102
+ {
103
+ ...TogetherAIProviderCard,
104
+ title: <Together.Combine size={26} type={'color'} />,
105
+ },
106
+ {
107
+ ...QwenProviderCard,
108
+ title: <Tongyi.Combine extra={'千问'} size={26} type={'color'} />,
109
+ },
110
+ {
111
+ ...DeepSeekProviderCard,
112
+ title: <DeepSeek.Combine size={28} type={'color'} />,
113
+ },
114
+ {
115
+ ...MinimaxProviderCard,
116
+ title: <Minimax.Combine size={32} type={'color'} />,
117
+ },
118
+ {
119
+ ...MistralProviderCard,
120
+ title: <Mistral.Combine size={26} type={'color'} />,
121
+ },
122
+ {
123
+ ...MoonshotProviderCard,
124
+ title: <MoonshotBrand />,
125
+ },
126
+ {
127
+ ...PerplexityProviderCard,
128
+ title: <Perplexity.Combine size={24} type={'color'} />,
129
+ },
130
+ {
131
+ ...ZhiPuProviderCard,
132
+ title: <Zhipu.Combine size={32} type={'color'} />,
133
+ },
134
+ {
135
+ ...ZeroOneProviderCard,
136
+ title: <ZeroOne.Text size={20} />,
137
+ },
138
+ ],
139
+ [azureProvider, ollamaProvider, ollamaProvider, bedrockProvider],
140
+ );
141
+ };
@@ -19,6 +19,7 @@ import {
19
19
  import { FORM_STYLE } from '@/const/layoutTokens';
20
20
  import { useUserStore } from '@/store/user';
21
21
  import { keyVaultsConfigSelectors, modelConfigSelectors } from '@/store/user/selectors';
22
+ import { ModelProviderCard } from '@/types/llm';
22
23
  import { GlobalLLMProviderKey } from '@/types/user/settings';
23
24
 
24
25
  import Checker from '../Checker';
@@ -46,36 +47,26 @@ const useStyles = createStyles(({ css, prefixCls, responsive }) => ({
46
47
  `,
47
48
  }));
48
49
 
49
- interface ProviderConfigProps {
50
+ export interface ProviderConfigProps extends Omit<ModelProviderCard, 'id'> {
50
51
  apiKeyItems?: FormItemProps[];
51
52
  canDeactivate?: boolean;
52
- checkModel?: string;
53
53
  checkerItem?: FormItemProps;
54
54
  className?: string;
55
55
  hideSwitch?: boolean;
56
+ id: GlobalLLMProviderKey;
56
57
  modelList?: {
57
58
  azureDeployName?: boolean;
58
59
  notFoundContent?: ReactNode;
59
60
  placeholder?: string;
60
61
  showModelFetcher?: boolean;
61
62
  };
62
- provider: GlobalLLMProviderKey;
63
- proxyUrl?:
64
- | {
65
- desc?: string;
66
- placeholder: string;
67
- title?: string;
68
- }
69
- | false;
70
- showApiKey?: boolean;
71
- showBrowserRequest?: boolean;
72
63
  title: ReactNode;
73
64
  }
74
65
 
75
66
  const ProviderConfig = memo<ProviderConfigProps>(
76
67
  ({
77
68
  apiKeyItems,
78
- provider,
69
+ id,
79
70
  proxyUrl,
80
71
  showApiKey = true,
81
72
  checkModel,
@@ -85,9 +76,9 @@ const ProviderConfig = memo<ProviderConfigProps>(
85
76
  modelList,
86
77
  showBrowserRequest,
87
78
  className,
79
+ name,
88
80
  }) => {
89
81
  const { t } = useTranslation('setting');
90
- const { t: modelT } = useTranslation('modelProvider');
91
82
  const [form] = Form.useForm();
92
83
  const { cx, styles } = useStyles();
93
84
  const [
@@ -99,9 +90,9 @@ const ProviderConfig = memo<ProviderConfigProps>(
99
90
  ] = useUserStore((s) => [
100
91
  s.toggleProviderEnabled,
101
92
  s.setSettings,
102
- modelConfigSelectors.isProviderEnabled(provider)(s),
103
- modelConfigSelectors.isProviderFetchOnClient(provider)(s),
104
- keyVaultsConfigSelectors.isProviderEndpointNotEmpty(provider)(s),
93
+ modelConfigSelectors.isProviderEnabled(id)(s),
94
+ modelConfigSelectors.isProviderFetchOnClient(id)(s),
95
+ keyVaultsConfigSelectors.isProviderEndpointNotEmpty(id)(s),
105
96
  ]);
106
97
 
107
98
  useSyncSettings(form);
@@ -113,12 +104,12 @@ const ProviderConfig = memo<ProviderConfigProps>(
113
104
  children: (
114
105
  <Input.Password
115
106
  autoComplete={'new-password'}
116
- placeholder={modelT(`${provider}.token.placeholder` as any)}
107
+ placeholder={t(`llm.apiKey.placeholder`, { name })}
117
108
  />
118
109
  ),
119
- desc: modelT(`${provider}.token.desc` as any),
120
- label: modelT(`${provider}.token.title` as any),
121
- name: [KeyVaultsConfigKey, provider, LLMProviderApiTokenKey],
110
+ desc: t(`llm.apiKey.desc`, { name }),
111
+ label: t(`llm.apiKey.title`),
112
+ name: [KeyVaultsConfigKey, id, LLMProviderApiTokenKey],
122
113
  },
123
114
  ];
124
115
 
@@ -129,13 +120,13 @@ const ProviderConfig = memo<ProviderConfigProps>(
129
120
  children: <Input allowClear placeholder={proxyUrl?.placeholder} />,
130
121
  desc: proxyUrl?.desc || t('llm.proxyUrl.desc'),
131
122
  label: proxyUrl?.title || t('llm.proxyUrl.title'),
132
- name: [KeyVaultsConfigKey, provider, LLMProviderBaseUrlKey],
123
+ name: [KeyVaultsConfigKey, id, LLMProviderBaseUrlKey],
133
124
  },
134
125
  (showBrowserRequest || (showEndpoint && isProviderEndpointNotEmpty)) && {
135
126
  children: (
136
127
  <Switch
137
128
  onChange={(enabled) => {
138
- setSettings({ [LLMProviderConfigKey]: { [provider]: { fetchOnClient: enabled } } });
129
+ setSettings({ [LLMProviderConfigKey]: { [id]: { fetchOnClient: enabled } } });
139
130
  }}
140
131
  value={isFetchOnClient}
141
132
  />
@@ -149,17 +140,17 @@ const ProviderConfig = memo<ProviderConfigProps>(
149
140
  <ProviderModelListSelect
150
141
  notFoundContent={modelList?.notFoundContent}
151
142
  placeholder={modelList?.placeholder ?? t('llm.modelList.placeholder')}
152
- provider={provider}
143
+ provider={id}
153
144
  showAzureDeployName={modelList?.azureDeployName}
154
145
  showModelFetcher={modelList?.showModelFetcher}
155
146
  />
156
147
  ),
157
148
  desc: t('llm.modelList.desc'),
158
149
  label: t('llm.modelList.title'),
159
- name: [LLMProviderConfigKey, provider, LLMProviderModelListKey],
150
+ name: [LLMProviderConfigKey, id, LLMProviderModelListKey],
160
151
  },
161
152
  checkerItem ?? {
162
- children: <Checker model={checkModel!} provider={provider} />,
153
+ children: <Checker model={checkModel!} provider={id} />,
163
154
  desc: t('llm.checker.desc'),
164
155
  label: t('llm.checker.title'),
165
156
  minWidth: undefined,
@@ -173,7 +164,7 @@ const ProviderConfig = memo<ProviderConfigProps>(
173
164
  extra: canDeactivate ? (
174
165
  <Switch
175
166
  onChange={(enabled) => {
176
- toggleProviderEnabled(provider, enabled);
167
+ toggleProviderEnabled(id, enabled);
177
168
  }}
178
169
  value={enabled}
179
170
  />
@@ -2,44 +2,17 @@
2
2
 
3
3
  import { Flexbox } from 'react-layout-kit';
4
4
 
5
- import Anthropic from './Anthropic';
6
- import Azure from './Azure';
7
- import Bedrock from './Bedrock';
8
- import DeepSeek from './DeepSeek';
9
- import Google from './Google';
10
- import Groq from './Groq';
11
- import Minimax from './Minimax';
12
- import Mistral from './Mistral';
13
- import Moonshot from './Moonshot';
14
- import Ollama from './Ollama';
15
- import OpenAI from './OpenAI';
16
- import OpenRouter from './OpenRouter';
17
- import Perplexity from './Perplexity';
18
- import TogetherAI from './TogetherAI';
19
- import ZeroOne from './ZeroOne';
20
- import Zhipu from './Zhipu';
21
- import Footer from './components/Footer';
5
+ import { useProviderList } from './ProviderList/providers';
6
+ import ProviderConfig from './components/ProviderConfig';
22
7
 
23
8
  const Page = () => {
9
+ const list = useProviderList();
10
+
24
11
  return (
25
12
  <Flexbox gap={24} width={'100%'}>
26
- <OpenAI />
27
- <Ollama />
28
- <Azure />
29
- <Google />
30
- <Anthropic />
31
- <Bedrock />
32
- <DeepSeek />
33
- <OpenRouter />
34
- <TogetherAI />
35
- <Groq />
36
- <Perplexity />
37
- <Minimax />
38
- <Mistral />
39
- <Moonshot />
40
- <Zhipu />
41
- <ZeroOne />
42
- <Footer />
13
+ {list.map(({ id, ...res }) => (
14
+ <ProviderConfig id={id as any} key={id} {...res} />
15
+ ))}
43
16
  </Flexbox>
44
17
  );
45
18
  };
@@ -0,0 +1,5 @@
1
+ import { ProviderConfigProps } from './components/ProviderConfig';
2
+
3
+ export interface ProviderItem extends Omit<ProviderConfigProps, 'id'> {
4
+ id: string;
5
+ }
@@ -1,6 +1,6 @@
1
1
  import { getPreferredRegion } from '@/app/api/config';
2
2
  import { createErrorResponse } from '@/app/api/errorResponse';
3
- import { ChatCompletionErrorPayload } from '@/libs/agent-runtime';
3
+ import { AgentRuntime, ChatCompletionErrorPayload } from '@/libs/agent-runtime';
4
4
  import { ChatErrorType } from '@/types/fetch';
5
5
  import { ChatStreamPayload } from '@/types/openai/chat';
6
6
  import { getTracePayload } from '@/utils/trace';
@@ -12,12 +12,17 @@ export const runtime = 'edge';
12
12
 
13
13
  export const preferredRegion = getPreferredRegion();
14
14
 
15
- export const POST = checkAuth(async (req: Request, { params, jwtPayload }) => {
15
+ export const POST = checkAuth(async (req: Request, { params, jwtPayload, createRuntime }) => {
16
16
  const { provider } = params;
17
17
 
18
18
  try {
19
19
  // ============ 1. init chat model ============ //
20
- const agentRuntime = await initAgentRuntimeWithUserPayload(provider, jwtPayload);
20
+ let agentRuntime: AgentRuntime;
21
+ if (createRuntime) {
22
+ agentRuntime = createRuntime(jwtPayload);
23
+ } else {
24
+ agentRuntime = await initAgentRuntimeWithUserPayload(provider, jwtPayload);
25
+ }
21
26
 
22
27
  // ============ 2. create chat completion ============ //
23
28
 
@@ -16,6 +16,7 @@ import {
16
16
  LobeOpenAI,
17
17
  LobeOpenRouterAI,
18
18
  LobePerplexityAI,
19
+ LobeQwenAI,
19
20
  LobeRuntimeAI,
20
21
  LobeTogetherAI,
21
22
  LobeZeroOneAI,
@@ -49,6 +50,7 @@ vi.mock('@/config/llm', () => ({
49
50
  MISTRAL_API_KEY: 'test-mistral-key',
50
51
  OPENROUTER_API_KEY: 'test-openrouter-key',
51
52
  TOGETHERAI_API_KEY: 'test-togetherai-key',
53
+ QWEN_API_KEY: 'test-qwen-key',
52
54
  })),
53
55
  }));
54
56
 
@@ -101,6 +103,13 @@ describe('initAgentRuntimeWithUserPayload method', () => {
101
103
  expect(runtime['_runtime']).toBeInstanceOf(LobeMoonshotAI);
102
104
  });
103
105
 
106
+ it('Qwen AI provider: with apikey', async () => {
107
+ const jwtPayload: JWTPayload = { apiKey: 'user-qwen-key' };
108
+ const runtime = await initAgentRuntimeWithUserPayload(ModelProvider.Qwen, jwtPayload);
109
+ expect(runtime).toBeInstanceOf(AgentRuntime);
110
+ expect(runtime['_runtime']).toBeInstanceOf(LobeQwenAI);
111
+ });
112
+
104
113
  it('Bedrock AI provider: with apikey, awsAccessKeyId, awsSecretAccessKey, awsRegion', async () => {
105
114
  const jwtPayload: JWTPayload = {
106
115
  apiKey: 'user-bedrock-key',
@@ -232,6 +241,14 @@ describe('initAgentRuntimeWithUserPayload method', () => {
232
241
  expect(runtime['_runtime']).toBeInstanceOf(LobeMoonshotAI);
233
242
  });
234
243
 
244
+ it('Qwen AI provider: without apikey', async () => {
245
+ const jwtPayload: JWTPayload = {};
246
+ const runtime = await initAgentRuntimeWithUserPayload(ModelProvider.Qwen, jwtPayload);
247
+
248
+ // 假设 LobeQwenAI 是 Qwen 提供者的实现类
249
+ expect(runtime['_runtime']).toBeInstanceOf(LobeQwenAI);
250
+ });
251
+
235
252
  it('Bedrock AI provider: without apikey', async () => {
236
253
  const jwtPayload = {};
237
254
  const runtime = await initAgentRuntimeWithUserPayload(ModelProvider.Bedrock, jwtPayload);
@@ -156,6 +156,13 @@ const getLlmOptionsFromPayload = (provider: string, payload: JWTPayload) => {
156
156
 
157
157
  const apiKey = apiKeyManager.pick(payload?.apiKey || ZEROONE_API_KEY);
158
158
 
159
+ return { apiKey };
160
+ }
161
+ case ModelProvider.Qwen: {
162
+ const { QWEN_API_KEY } = getLLMConfig();
163
+
164
+ const apiKey = apiKeyManager.pick(payload?.apiKey || QWEN_API_KEY);
165
+
159
166
  return { apiKey };
160
167
  }
161
168
  }
@@ -34,7 +34,13 @@ describe('createErrorResponse', () => {
34
34
 
35
35
  describe('Provider Biz Error', () => {
36
36
  it('returns a 471 status for OpenAIBizError error type', () => {
37
- const errorType = ChatErrorType.OpenAIBizError;
37
+ const errorType = AgentRuntimeErrorType.OpenAIBizError;
38
+ const response = createErrorResponse(errorType);
39
+ expect(response.status).toBe(471);
40
+ });
41
+
42
+ it('returns a 471 status for ProviderBizError error type', () => {
43
+ const errorType = AgentRuntimeErrorType.ProviderBizError;
38
44
  const response = createErrorResponse(errorType);
39
45
  expect(response.status).toBe(471);
40
46
  });
@@ -50,94 +56,6 @@ describe('createErrorResponse', () => {
50
56
  const response = createErrorResponse(errorType as any);
51
57
  expect(response.status).toBe(471);
52
58
  });
53
-
54
- // 测试 AzureBizError 错误类型返回472状态码
55
- it('returns a 472 status for AzureBizError error type', () => {
56
- const errorType = AgentRuntimeErrorType.AzureBizError;
57
- const response = createErrorResponse(errorType);
58
- expect(response.status).toBe(472);
59
- });
60
-
61
- // 测试 ZhipuBizError 错误类型返回473状态码
62
- it('returns a 473 status for ZhipuBizError error type', () => {
63
- const errorType = AgentRuntimeErrorType.ZhipuBizError;
64
- const response = createErrorResponse(errorType);
65
- expect(response.status).toBe(473);
66
- });
67
-
68
- // 测试 BedrockBizError 错误类型返回474状态码
69
- it('returns a 474 status for BedrockBizError error type', () => {
70
- const errorType = AgentRuntimeErrorType.BedrockBizError;
71
- const response = createErrorResponse(errorType);
72
- expect(response.status).toBe(474);
73
- });
74
-
75
- // 测试 GoogleBizError 错误类型返回475状态码
76
- it('returns a 475 status for GoogleBizError error type', () => {
77
- const errorType = AgentRuntimeErrorType.GoogleBizError;
78
- const response = createErrorResponse(errorType);
79
- expect(response.status).toBe(475);
80
- });
81
-
82
- // 测试 MoonshotBizError 错误类型返回476状态码
83
- it('returns a 476 status for MoonshotBizError error type', () => {
84
- const errorType = AgentRuntimeErrorType.MoonshotBizError;
85
- const response = createErrorResponse(errorType);
86
- expect(response.status).toBe(476);
87
- });
88
-
89
- // 测试 OpenRouterBizError 错误类型返回477状态码
90
- it('returns a 477 status for OpenRouterBizError error type', () => {
91
- const errorType = AgentRuntimeErrorType.OpenRouterBizError;
92
- const response = createErrorResponse(errorType);
93
- expect(response.status).toBe(477);
94
- });
95
-
96
- // 测试 OllamaBizError 错误类型返回478状态码
97
- it('returns a 478 status for OllamaBizError error type', () => {
98
- const errorType = AgentRuntimeErrorType.OllamaBizError;
99
- const response = createErrorResponse(errorType);
100
- expect(response.status).toBe(478);
101
- });
102
-
103
- // 测试 PerplexityBizError 错误类型返回479状态码
104
- it('returns a 479 status for PerplexityBizError error type', () => {
105
- const errorType = AgentRuntimeErrorType.PerplexityBizError;
106
- const response = createErrorResponse(errorType);
107
- expect(response.status).toBe(479);
108
- });
109
-
110
- // 测试 AnthropicBizError 错误类型返回480状态码
111
- it('returns a 480 status for AnthropicBizError error type', () => {
112
- const errorType = AgentRuntimeErrorType.AnthropicBizError;
113
- const response = createErrorResponse(errorType);
114
- expect(response.status).toBe(480);
115
- });
116
-
117
- // 测试 MistralBizError 错误类型返回481状态码
118
- it('returns a 481 status for MistralBizError error type', () => {
119
- const errorType = AgentRuntimeErrorType.MistralBizError;
120
- const response = createErrorResponse(errorType);
121
- expect(response.status).toBe(481);
122
- });
123
-
124
- it('returns a 484 status for TogetherAIBizError error type', () => {
125
- const errorType = AgentRuntimeErrorType.TogetherAIBizError;
126
- const response = createErrorResponse(errorType);
127
- expect(response.status).toBe(484);
128
- });
129
-
130
- it('returns a 485 status for MinimaxBizError error type', () => {
131
- const errorType = AgentRuntimeErrorType.MinimaxBizError;
132
- const response = createErrorResponse(errorType);
133
- expect(response.status).toBe(485);
134
- });
135
-
136
- it('returns a 486 status for DeepSeekBizError error type', () => {
137
- const errorType = AgentRuntimeErrorType.DeepSeekBizError;
138
- const response = createErrorResponse(errorType);
139
- expect(response.status).toBe(486);
140
- });
141
59
  });
142
60
 
143
61
  // 测试状态码不在200-599范围内的情况
@@ -7,6 +7,7 @@ const getStatus = (errorType: ILobeAgentRuntimeErrorType | ErrorType) => {
7
7
 
8
8
  switch (errorType) {
9
9
  // TODO: Need to refactor to Invalid OpenAI API Key
10
+ case AgentRuntimeErrorType.InvalidProviderAPIKey:
10
11
  case AgentRuntimeErrorType.NoOpenAIAPIKey: {
11
12
  return 401;
12
13
  }
@@ -19,56 +20,18 @@ const getStatus = (errorType: ILobeAgentRuntimeErrorType | ErrorType) => {
19
20
  case AgentRuntimeErrorType.AgentRuntimeError: {
20
21
  return 470;
21
22
  }
23
+
24
+ case AgentRuntimeErrorType.ProviderBizError:
22
25
  case AgentRuntimeErrorType.OpenAIBizError: {
23
26
  return 471;
24
27
  }
25
- case AgentRuntimeErrorType.AzureBizError: {
26
- return 472;
27
- }
28
- case AgentRuntimeErrorType.ZhipuBizError: {
29
- return 473;
30
- }
31
- case AgentRuntimeErrorType.BedrockBizError: {
32
- return 474;
33
- }
34
- case AgentRuntimeErrorType.GoogleBizError: {
35
- return 475;
36
- }
37
- case AgentRuntimeErrorType.MoonshotBizError: {
38
- return 476;
39
- }
40
- case AgentRuntimeErrorType.OpenRouterBizError: {
41
- return 477;
42
- }
28
+
43
29
  case ChatErrorType.OllamaServiceUnavailable:
44
30
  case AgentRuntimeErrorType.OllamaBizError: {
45
- return 478;
46
- }
47
- case AgentRuntimeErrorType.PerplexityBizError: {
48
- return 479;
49
- }
50
- case AgentRuntimeErrorType.AnthropicBizError: {
51
- return 480;
52
- }
53
- case AgentRuntimeErrorType.MistralBizError: {
54
- return 481;
55
- }
56
- case AgentRuntimeErrorType.GroqBizError: {
57
- return 482;
58
- }
59
- case AgentRuntimeErrorType.ZeroOneBizError: {
60
- return 483;
61
- }
62
- case AgentRuntimeErrorType.TogetherAIBizError: {
63
- return 484;
64
- }
65
- case AgentRuntimeErrorType.MinimaxBizError: {
66
- return 485;
67
- }
68
- case AgentRuntimeErrorType.DeepSeekBizError: {
69
- return 486;
31
+ return 472;
70
32
  }
71
33
  }
34
+
72
35
  return errorType as number;
73
36
  };
74
37
 
@@ -4,16 +4,20 @@ import { NextRequest } from 'next/server';
4
4
 
5
5
  import { createErrorResponse } from '@/app/api/errorResponse';
6
6
  import { JWTPayload, LOBE_CHAT_AUTH_HEADER, OAUTH_AUTHORIZED, enableClerk } from '@/const/auth';
7
- import { AgentRuntimeError, ChatCompletionErrorPayload } from '@/libs/agent-runtime';
7
+ import { AgentRuntime, AgentRuntimeError, ChatCompletionErrorPayload } from '@/libs/agent-runtime';
8
8
  import { ChatErrorType } from '@/types/fetch';
9
9
 
10
10
  import { checkAuthMethod, getJWTPayload } from './utils';
11
11
 
12
- type RequestOptions = { params: { provider: string } };
12
+ type CreateRuntime = (jwtPayload: JWTPayload) => AgentRuntime;
13
+ type RequestOptions = { createRuntime?: CreateRuntime, params: { provider: string }; };
13
14
 
14
15
  export type RequestHandler = (
15
16
  req: Request,
16
- options: RequestOptions & { jwtPayload: JWTPayload },
17
+ options: RequestOptions & {
18
+ createRuntime?: CreateRuntime;
19
+ jwtPayload: JWTPayload;
20
+ },
17
21
  ) => Promise<Response>;
18
22
 
19
23
  export const checkAuth =
@@ -14,6 +14,7 @@ import {
14
14
  OpenRouter,
15
15
  Perplexity,
16
16
  Together,
17
+ Tongyi,
17
18
  ZeroOne,
18
19
  Zhipu,
19
20
  } from '@lobehub/icons';
@@ -104,6 +105,10 @@ const ModelProviderIcon = memo<ModelProviderIconProps>(({ provider }) => {
104
105
  return <Together size={20} />;
105
106
  }
106
107
 
108
+ case ModelProvider.Qwen: {
109
+ return <Tongyi size={20} />;
110
+ }
111
+
107
112
  default: {
108
113
  return null;
109
114
  }
@@ -151,16 +151,13 @@ export const ModelItemRender = memo<ModelItemRenderProps>(({ showInfoTag = true,
151
151
  });
152
152
 
153
153
  interface ProviderItemRenderProps {
154
+ name: string;
154
155
  provider: string;
155
156
  }
156
157
 
157
- export const ProviderItemRender = memo<ProviderItemRenderProps>(({ provider }) => {
158
- const { t } = useTranslation('modelProvider');
159
-
160
- return (
161
- <Flexbox align={'center'} gap={4} horizontal>
162
- <ModelProviderIcon provider={provider} />
163
- {t(`${provider}.title` as any)}
164
- </Flexbox>
165
- );
166
- });
158
+ export const ProviderItemRender = memo<ProviderItemRenderProps>(({ provider, name }) => (
159
+ <Flexbox align={'center'} gap={4} horizontal>
160
+ <ModelProviderIcon provider={provider} />
161
+ {name}
162
+ </Flexbox>
163
+ ));