@lobehub/chat 1.23.1 → 1.24.0

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 (78) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/Dockerfile +2 -0
  3. package/Dockerfile.database +2 -0
  4. package/locales/ar/modelProvider.json +16 -0
  5. package/locales/ar/models.json +27 -0
  6. package/locales/ar/providers.json +1 -0
  7. package/locales/bg-BG/modelProvider.json +16 -0
  8. package/locales/bg-BG/models.json +27 -0
  9. package/locales/bg-BG/providers.json +1 -0
  10. package/locales/de-DE/modelProvider.json +16 -0
  11. package/locales/de-DE/models.json +27 -0
  12. package/locales/de-DE/providers.json +1 -0
  13. package/locales/en-US/modelProvider.json +16 -0
  14. package/locales/en-US/models.json +27 -0
  15. package/locales/en-US/providers.json +1 -0
  16. package/locales/es-ES/modelProvider.json +16 -0
  17. package/locales/es-ES/models.json +27 -0
  18. package/locales/es-ES/providers.json +1 -0
  19. package/locales/fr-FR/modelProvider.json +16 -0
  20. package/locales/fr-FR/models.json +27 -0
  21. package/locales/fr-FR/providers.json +1 -0
  22. package/locales/it-IT/modelProvider.json +16 -0
  23. package/locales/it-IT/models.json +27 -0
  24. package/locales/it-IT/providers.json +1 -0
  25. package/locales/ja-JP/modelProvider.json +16 -0
  26. package/locales/ja-JP/models.json +27 -0
  27. package/locales/ja-JP/providers.json +1 -0
  28. package/locales/ko-KR/modelProvider.json +16 -0
  29. package/locales/ko-KR/models.json +27 -0
  30. package/locales/ko-KR/providers.json +1 -0
  31. package/locales/nl-NL/modelProvider.json +16 -0
  32. package/locales/nl-NL/models.json +27 -0
  33. package/locales/nl-NL/providers.json +1 -0
  34. package/locales/pl-PL/modelProvider.json +16 -0
  35. package/locales/pl-PL/models.json +27 -0
  36. package/locales/pl-PL/providers.json +1 -0
  37. package/locales/pt-BR/modelProvider.json +16 -0
  38. package/locales/pt-BR/models.json +27 -0
  39. package/locales/pt-BR/providers.json +1 -0
  40. package/locales/ru-RU/modelProvider.json +16 -0
  41. package/locales/ru-RU/models.json +27 -0
  42. package/locales/ru-RU/providers.json +1 -0
  43. package/locales/tr-TR/modelProvider.json +16 -0
  44. package/locales/tr-TR/models.json +27 -0
  45. package/locales/tr-TR/providers.json +1 -0
  46. package/locales/vi-VN/modelProvider.json +16 -0
  47. package/locales/vi-VN/models.json +27 -0
  48. package/locales/vi-VN/providers.json +1 -0
  49. package/locales/zh-CN/modelProvider.json +16 -0
  50. package/locales/zh-CN/models.json +27 -0
  51. package/locales/zh-CN/providers.json +1 -0
  52. package/locales/zh-TW/modelProvider.json +16 -0
  53. package/locales/zh-TW/models.json +27 -0
  54. package/locales/zh-TW/providers.json +1 -0
  55. package/package.json +3 -3
  56. package/src/app/(main)/settings/llm/ProviderList/SenseNova/index.tsx +44 -0
  57. package/src/app/(main)/settings/llm/ProviderList/providers.tsx +4 -0
  58. package/src/config/llm.ts +10 -0
  59. package/src/config/modelProviders/index.ts +4 -0
  60. package/src/config/modelProviders/sensenova.ts +124 -0
  61. package/src/const/auth.ts +3 -0
  62. package/src/const/settings/llm.ts +5 -0
  63. package/src/features/Conversation/Error/APIKeyForm/SenseNova.tsx +49 -0
  64. package/src/features/Conversation/Error/APIKeyForm/index.tsx +3 -0
  65. package/src/libs/agent-runtime/AgentRuntime.ts +7 -0
  66. package/src/libs/agent-runtime/index.ts +1 -0
  67. package/src/libs/agent-runtime/sensenova/authToken.test.ts +18 -0
  68. package/src/libs/agent-runtime/sensenova/authToken.ts +27 -0
  69. package/src/libs/agent-runtime/sensenova/index.test.ts +321 -0
  70. package/src/libs/agent-runtime/sensenova/index.ts +98 -0
  71. package/src/libs/agent-runtime/types/type.ts +1 -0
  72. package/src/locales/default/modelProvider.ts +17 -0
  73. package/src/server/globalConfig/index.ts +12 -0
  74. package/src/server/modules/AgentRuntime/index.ts +10 -0
  75. package/src/services/_auth.ts +14 -0
  76. package/src/store/user/slices/modelList/selectors/keyVaults.ts +2 -0
  77. package/src/store/user/slices/modelList/selectors/modelConfig.ts +2 -0
  78. package/src/types/user/settings/keyVaults.ts +6 -0
@@ -0,0 +1,44 @@
1
+ 'use client';
2
+
3
+ import { Input } from 'antd';
4
+ import { useTranslation } from 'react-i18next';
5
+
6
+ import { SenseNovaProviderCard } from '@/config/modelProviders';
7
+ import { GlobalLLMProviderKey } from '@/types/user/settings';
8
+
9
+ import { KeyVaultsConfigKey } from '../../const';
10
+ import { ProviderItem } from '../../type';
11
+
12
+ const providerKey: GlobalLLMProviderKey = 'sensenova';
13
+
14
+ export const useSenseNovaProvider = (): ProviderItem => {
15
+ const { t } = useTranslation('modelProvider');
16
+
17
+ return {
18
+ ...SenseNovaProviderCard,
19
+ apiKeyItems: [
20
+ {
21
+ children: (
22
+ <Input.Password
23
+ autoComplete={'new-password'}
24
+ placeholder={t(`${providerKey}.sensenovaAccessKeyID.placeholder`)}
25
+ />
26
+ ),
27
+ desc: t(`${providerKey}.sensenovaAccessKeyID.desc`),
28
+ label: t(`${providerKey}.sensenovaAccessKeyID.title`),
29
+ name: [KeyVaultsConfigKey, providerKey, 'sensenovaAccessKeyID'],
30
+ },
31
+ {
32
+ children: (
33
+ <Input.Password
34
+ autoComplete={'new-password'}
35
+ placeholder={t(`${providerKey}.sensenovaAccessKeySecret.placeholder`)}
36
+ />
37
+ ),
38
+ desc: t(`${providerKey}.sensenovaAccessKeySecret.desc`),
39
+ label: t(`${providerKey}.sensenovaAccessKeySecret.title`),
40
+ name: [KeyVaultsConfigKey, providerKey, 'sensenovaAccessKeySecret'],
41
+ },
42
+ ],
43
+ };
44
+ };
@@ -35,6 +35,7 @@ import { useHuggingFaceProvider } from './HuggingFace';
35
35
  import { useOllamaProvider } from './Ollama';
36
36
  import { useOpenAIProvider } from './OpenAI';
37
37
  import { useWenxinProvider } from './Wenxin';
38
+ import { useSenseNovaProvider } from './SenseNova';
38
39
 
39
40
  export const useProviderList = (): ProviderItem[] => {
40
41
  const AzureProvider = useAzureProvider();
@@ -44,6 +45,7 @@ export const useProviderList = (): ProviderItem[] => {
44
45
  const GithubProvider = useGithubProvider();
45
46
  const HuggingFaceProvider = useHuggingFaceProvider();
46
47
  const WenxinProvider = useWenxinProvider();
48
+ const SenseNovaProvider = useSenseNovaProvider();
47
49
 
48
50
  return useMemo(
49
51
  () => [
@@ -71,6 +73,7 @@ export const useProviderList = (): ProviderItem[] => {
71
73
  SparkProviderCard,
72
74
  ZhiPuProviderCard,
73
75
  ZeroOneProviderCard,
76
+ SenseNovaProvider,
74
77
  StepfunProviderCard,
75
78
  MoonshotProviderCard,
76
79
  BaichuanProviderCard,
@@ -87,6 +90,7 @@ export const useProviderList = (): ProviderItem[] => {
87
90
  GithubProvider,
88
91
  WenxinProvider,
89
92
  HuggingFaceProvider,
93
+ SenseNovaProvider,
90
94
  ],
91
95
  );
92
96
  };
package/src/config/llm.ts CHANGED
@@ -144,6 +144,11 @@ export const getLLMConfig = () => {
144
144
  HUGGINGFACE_API_KEY: z.string().optional(),
145
145
  HUGGINGFACE_PROXY_URL: z.string().optional(),
146
146
  HUGGINGFACE_MODEL_LIST: z.string().optional(),
147
+
148
+ ENABLED_SENSENOVA: z.boolean(),
149
+ SENSENOVA_ACCESS_KEY_ID: z.string().optional(),
150
+ SENSENOVA_ACCESS_KEY_SECRET: z.string().optional(),
151
+ SENSENOVA_MODEL_LIST: z.string().optional(),
147
152
  },
148
153
  runtimeEnv: {
149
154
  API_KEY_SELECT_MODE: process.env.API_KEY_SELECT_MODE,
@@ -285,6 +290,11 @@ export const getLLMConfig = () => {
285
290
  HUGGINGFACE_API_KEY: process.env.HUGGINGFACE_API_KEY,
286
291
  HUGGINGFACE_PROXY_URL: process.env.HUGGINGFACE_PROXY_URL,
287
292
  HUGGINGFACE_MODEL_LIST: process.env.HUGGINGFACE_MODEL_LIST,
293
+
294
+ ENABLED_SENSENOVA: !!process.env.SENSENOVA_ACCESS_KEY_ID && !!process.env.SENSENOVA_ACCESS_KEY_SECRET,
295
+ SENSENOVA_ACCESS_KEY_ID: process.env.SENSENOVA_ACCESS_KEY_ID,
296
+ SENSENOVA_ACCESS_KEY_SECRET: process.env.SENSENOVA_ACCESS_KEY_SECRET,
297
+ SENSENOVA_MODEL_LIST: process.env.SENSENOVA_MODEL_LIST,
288
298
  },
289
299
  });
290
300
  };
@@ -22,6 +22,7 @@ import OpenAIProvider from './openai';
22
22
  import OpenRouterProvider from './openrouter';
23
23
  import PerplexityProvider from './perplexity';
24
24
  import QwenProvider from './qwen';
25
+ import SenseNovaProvider from './sensenova';
25
26
  import SiliconCloudProvider from './siliconcloud';
26
27
  import SparkProvider from './spark';
27
28
  import StepfunProvider from './stepfun';
@@ -63,6 +64,7 @@ export const LOBE_DEFAULT_MODEL_LIST: ChatModelCard[] = [
63
64
  Ai21Provider.chatModels,
64
65
  HunyuanProvider.chatModels,
65
66
  WenxinProvider.chatModels,
67
+ SenseNovaProvider.chatModels,
66
68
  ].flat();
67
69
 
68
70
  export const DEFAULT_MODEL_PROVIDER_LIST = [
@@ -90,6 +92,7 @@ export const DEFAULT_MODEL_PROVIDER_LIST = [
90
92
  SparkProvider,
91
93
  ZhiPuProvider,
92
94
  ZeroOneProvider,
95
+ SenseNovaProvider,
93
96
  StepfunProvider,
94
97
  MoonshotProvider,
95
98
  BaichuanProvider,
@@ -130,6 +133,7 @@ export { default as OpenAIProviderCard } from './openai';
130
133
  export { default as OpenRouterProviderCard } from './openrouter';
131
134
  export { default as PerplexityProviderCard } from './perplexity';
132
135
  export { default as QwenProviderCard } from './qwen';
136
+ export { default as SenseNovaProviderCard } from './sensenova';
133
137
  export { default as SiliconCloudProviderCard } from './siliconcloud';
134
138
  export { default as SparkProviderCard } from './spark';
135
139
  export { default as StepfunProviderCard } from './stepfun';
@@ -0,0 +1,124 @@
1
+ import { ModelProviderCard } from '@/types/llm';
2
+
3
+ // ref https://platform.sensenova.cn/pricing
4
+ // ref https://platform.sensenova.cn/release?path=/release-202409.md
5
+ const SenseNova: ModelProviderCard = {
6
+ chatModels: [
7
+ {
8
+ description: '最新版本模型 (V5.5),128K上下文长度,在数学推理、英文对话、指令跟随以及长文本理解等领域能力显著提升,比肩GPT-4o',
9
+ displayName: 'SenseChat 5.5',
10
+ enabled: true,
11
+ functionCall: true,
12
+ id: 'SenseChat-5',
13
+ pricing: {
14
+ currency: 'CNY',
15
+ input: 40,
16
+ output: 100,
17
+ },
18
+ tokens: 131_072,
19
+ },
20
+ {
21
+ description: '最新版本模型 (V5.5),16K上下文长度,支持多图的输入,全面实现模型基础能力优化,在对象属性识别、空间关系、动作事件识别、场景理解、情感识别、逻辑常识推理和文本理解生成上都实现了较大提升。',
22
+ displayName: 'SenseChat 5.5 Vision',
23
+ enabled: true,
24
+ id: 'SenseChat-Vision',
25
+ pricing: {
26
+ currency: 'CNY',
27
+ input: 100,
28
+ output: 100,
29
+ },
30
+ tokens: 16_384,
31
+ vision: true,
32
+ },
33
+ {
34
+ description: '适用于快速问答、模型微调场景',
35
+ displayName: 'SenseChat 5.0 Turbo',
36
+ enabled: true,
37
+ id: 'SenseChat-Turbo',
38
+ pricing: {
39
+ currency: 'CNY',
40
+ input: 2,
41
+ output: 5,
42
+ },
43
+ tokens: 32_768,
44
+ },
45
+ {
46
+ description: '32K上下文长度,在粤语的对话理解上超越了GPT-4,在知识、推理、数学及代码编写等多个领域均能与GPT-4 Turbo相媲美',
47
+ displayName: 'SenseChat 5.0 Cantonese',
48
+ id: 'SenseChat-5-Cantonese',
49
+ pricing: {
50
+ currency: 'CNY',
51
+ input: 27,
52
+ output: 27,
53
+ },
54
+ tokens: 32_768,
55
+ },
56
+ {
57
+ description: '基础版本模型 (V4),128K上下文长度,在长文本理解及生成等任务中表现出色',
58
+ displayName: 'SenseChat 4.0 128K',
59
+ enabled: true,
60
+ id: 'SenseChat-128K',
61
+ pricing: {
62
+ currency: 'CNY',
63
+ input: 60,
64
+ output: 60,
65
+ },
66
+ tokens: 131_072,
67
+ },
68
+ {
69
+ description: '基础版本模型 (V4),32K上下文长度,灵活应用于各类场景',
70
+ displayName: 'SenseChat 4.0 32K',
71
+ enabled: true,
72
+ id: 'SenseChat-32K',
73
+ pricing: {
74
+ currency: 'CNY',
75
+ input: 36,
76
+ output: 36,
77
+ },
78
+ tokens: 32_768,
79
+ },
80
+ {
81
+ description: '基础版本模型 (V4),4K上下文长度,通用能力强大',
82
+ displayName: 'SenseChat 4.0 4K',
83
+ enabled: true,
84
+ id: 'SenseChat',
85
+ pricing: {
86
+ currency: 'CNY',
87
+ input: 12,
88
+ output: 12,
89
+ },
90
+ tokens: 4096,
91
+ },
92
+ {
93
+ description: '标准版模型,8K上下文长度,高响应速度',
94
+ displayName: 'SenseChat Character',
95
+ id: 'SenseChat-Character',
96
+ pricing: {
97
+ currency: 'CNY',
98
+ input: 12,
99
+ output: 12,
100
+ },
101
+ tokens: 8192,
102
+ },
103
+ {
104
+ description: '高级版模型,32K上下文长度,能力全面提升,支持中/英文对话',
105
+ displayName: 'SenseChat Character Pro',
106
+ id: 'SenseChat-Character-Pro',
107
+ pricing: {
108
+ currency: 'CNY',
109
+ input: 15,
110
+ output: 15,
111
+ },
112
+ tokens: 32_768,
113
+ },
114
+ ],
115
+ checkModel: 'SenseChat-Turbo',
116
+ disableBrowserRequest: true,
117
+ id: 'sensenova',
118
+ modelList: { showModelFetcher: true },
119
+ modelsUrl: 'https://platform.sensenova.cn/pricing',
120
+ name: 'SenseNova',
121
+ url: 'https://platform.sensenova.cn/home',
122
+ };
123
+
124
+ export default SenseNova;
package/src/const/auth.ts CHANGED
@@ -40,6 +40,9 @@ export interface JWTPayload {
40
40
  wenxinAccessKey?: string;
41
41
  wenxinSecretKey?: string;
42
42
 
43
+ sensenovaAccessKeyID?: string;
44
+ sensenovaAccessKeySecret?: string;
45
+
43
46
  /**
44
47
  * user id
45
48
  * in client db mode it's a uuid
@@ -20,6 +20,7 @@ import {
20
20
  OpenRouterProviderCard,
21
21
  PerplexityProviderCard,
22
22
  QwenProviderCard,
23
+ SenseNovaProviderCard,
23
24
  SiliconCloudProviderCard,
24
25
  SparkProviderCard,
25
26
  StepfunProviderCard,
@@ -123,6 +124,10 @@ export const DEFAULT_LLM_CONFIG: UserModelProviderConfig = {
123
124
  enabled: false,
124
125
  enabledModels: filterEnabledModels(QwenProviderCard),
125
126
  },
127
+ sensenova: {
128
+ enabled: false,
129
+ enabledModels: filterEnabledModels(SenseNovaProviderCard),
130
+ },
126
131
  siliconcloud: {
127
132
  enabled: false,
128
133
  enabledModels: filterEnabledModels(SiliconCloudProviderCard),
@@ -0,0 +1,49 @@
1
+ import { SenseNova } from '@lobehub/icons';
2
+ import { Input } from 'antd';
3
+ import { memo } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+
6
+ import { ModelProvider } from '@/libs/agent-runtime';
7
+ import { useUserStore } from '@/store/user';
8
+ import { keyVaultsConfigSelectors } from '@/store/user/selectors';
9
+
10
+ import { FormAction } from '../style';
11
+
12
+ const SenseNovaForm = memo(() => {
13
+ const { t } = useTranslation('modelProvider');
14
+
15
+ const [sensenovaAccessKeyID, sensenovaAccessKeySecret, setConfig] = useUserStore((s) => [
16
+ keyVaultsConfigSelectors.sensenovaConfig(s).sensenovaAccessKeyID,
17
+ keyVaultsConfigSelectors.sensenovaConfig(s).sensenovaAccessKeySecret,
18
+ s.updateKeyVaultConfig,
19
+ ]);
20
+
21
+ return (
22
+ <FormAction
23
+ avatar={<SenseNova color={SenseNova.colorPrimary} size={56} />}
24
+ description={t('sensenova.unlock.description')}
25
+ title={t('sensenova.unlock.title')}
26
+ >
27
+ <Input.Password
28
+ autoComplete={'new-password'}
29
+ onChange={(e) => {
30
+ setConfig(ModelProvider.SenseNova, { sensenovaAccessKeyID: e.target.value });
31
+ }}
32
+ placeholder={t('sensenova.sensenovaAccessKeyID.placeholder')}
33
+ type={'block'}
34
+ value={sensenovaAccessKeyID}
35
+ />
36
+ <Input.Password
37
+ autoComplete={'new-password'}
38
+ onChange={(e) => {
39
+ setConfig(ModelProvider.SenseNova, { sensenovaAccessKeySecret: e.target.value });
40
+ }}
41
+ placeholder={t('sensenova.sensenovaAccessKeySecret.placeholder')}
42
+ type={'block'}
43
+ value={sensenovaAccessKeySecret}
44
+ />
45
+ </FormAction>
46
+ );
47
+ });
48
+
49
+ export default SenseNovaForm;
@@ -10,6 +10,7 @@ import { GlobalLLMProviderKey } from '@/types/user/settings';
10
10
 
11
11
  import BedrockForm from './Bedrock';
12
12
  import ProviderApiKeyForm from './ProviderApiKeyForm';
13
+ import SenseNovaForm from './SenseNova';
13
14
  import WenxinForm from './Wenxin';
14
15
 
15
16
  interface APIKeyFormProps {
@@ -66,6 +67,8 @@ const APIKeyForm = memo<APIKeyFormProps>(({ id, provider }) => {
66
67
  <Center gap={16} style={{ maxWidth: 300 }}>
67
68
  {provider === ModelProvider.Bedrock ? (
68
69
  <BedrockForm />
70
+ ) : provider === ModelProvider.SenseNova ? (
71
+ <SenseNovaForm />
69
72
  ) : provider === ModelProvider.Wenxin ? (
70
73
  <WenxinForm />
71
74
  ) : (
@@ -25,6 +25,7 @@ import { LobeOpenAI } from './openai';
25
25
  import { LobeOpenRouterAI } from './openrouter';
26
26
  import { LobePerplexityAI } from './perplexity';
27
27
  import { LobeQwenAI } from './qwen';
28
+ import { LobeSenseNovaAI } from './sensenova';
28
29
  import { LobeSiliconCloudAI } from './siliconcloud';
29
30
  import { LobeSparkAI } from './spark';
30
31
  import { LobeStepfunAI } from './stepfun';
@@ -146,6 +147,7 @@ class AgentRuntime {
146
147
  openrouter: Partial<ClientOptions>;
147
148
  perplexity: Partial<ClientOptions>;
148
149
  qwen: Partial<ClientOptions>;
150
+ sensenova: Partial<ClientOptions>;
149
151
  siliconcloud: Partial<ClientOptions>;
150
152
  spark: Partial<ClientOptions>;
151
153
  stepfun: Partial<ClientOptions>;
@@ -314,6 +316,11 @@ class AgentRuntime {
314
316
  runtimeModel = new LobeHunyuanAI(params.hunyuan);
315
317
  break;
316
318
  }
319
+
320
+ case ModelProvider.SenseNova: {
321
+ runtimeModel = await LobeSenseNovaAI.fromAPIKey(params.sensenova);
322
+ break;
323
+ }
317
324
  }
318
325
 
319
326
  return new AgentRuntime(runtimeModel);
@@ -15,6 +15,7 @@ export { LobeOpenAI } from './openai';
15
15
  export { LobeOpenRouterAI } from './openrouter';
16
16
  export { LobePerplexityAI } from './perplexity';
17
17
  export { LobeQwenAI } from './qwen';
18
+ export { LobeSenseNovaAI } from './sensenova';
18
19
  export { LobeTogetherAI } from './togetherai';
19
20
  export * from './types';
20
21
  export { AgentRuntimeError } from './utils/createError';
@@ -0,0 +1,18 @@
1
+ // @vitest-environment node
2
+ import { generateApiToken } from './authToken';
3
+
4
+ describe('generateApiToken', () => {
5
+ it('should throw an error if no apiKey is provided', async () => {
6
+ await expect(generateApiToken()).rejects.toThrow('Invalid apiKey');
7
+ });
8
+
9
+ it('should throw an error if apiKey is invalid', async () => {
10
+ await expect(generateApiToken('invalid')).rejects.toThrow('Invalid apiKey');
11
+ });
12
+
13
+ it('should return a token if a valid apiKey is provided', async () => {
14
+ const apiKey = 'id:secret';
15
+ const token = await generateApiToken(apiKey);
16
+ expect(token).toBeDefined();
17
+ });
18
+ });
@@ -0,0 +1,27 @@
1
+ import { SignJWT } from 'jose';
2
+
3
+ // https://console.sensecore.cn/help/docs/model-as-a-service/nova/overview/Authorization
4
+ export const generateApiToken = async (apiKey?: string): Promise<string> => {
5
+ if (!apiKey) {
6
+ throw new Error('Invalid apiKey');
7
+ }
8
+
9
+ const [id, secret] = apiKey.split(':');
10
+ if (!id || !secret) {
11
+ throw new Error('Invalid apiKey');
12
+ }
13
+
14
+ const currentTime = Math.floor(Date.now() / 1000);
15
+
16
+ const payload = {
17
+ exp: currentTime + 1800,
18
+ iss: id,
19
+ nbf: currentTime - 5,
20
+ };
21
+
22
+ const jwt = await new SignJWT(payload)
23
+ .setProtectedHeader({ alg: 'HS256', typ: 'JWT' })
24
+ .sign(new TextEncoder().encode(secret));
25
+
26
+ return jwt;
27
+ };