@lobehub/chat 1.0.2 โ†’ 1.0.4

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 1.0.4](https://github.com/lobehub/lobe-chat/compare/v1.0.3...v1.0.4)
6
+
7
+ <sup>Released on **2024-06-17**</sup>
8
+
9
+ #### ๐Ÿ’„ Styles
10
+
11
+ - **misc**: Add stepfun as a new provider.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Styles
19
+
20
+ - **misc**: Add stepfun as a new provider, closes [#2803](https://github.com/lobehub/lobe-chat/issues/2803) ([e1989a1](https://github.com/lobehub/lobe-chat/commit/e1989a1))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
30
+ ### [Version 1.0.3](https://github.com/lobehub/lobe-chat/compare/v1.0.2...v1.0.3)
31
+
32
+ <sup>Released on **2024-06-17**</sup>
33
+
34
+ #### ๐Ÿ› Bug Fixes
35
+
36
+ - **misc**: Fix clerk `UNAUTHORIZED` auth error.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### What's fixed
44
+
45
+ - **misc**: Fix clerk `UNAUTHORIZED` auth error, closes [#2907](https://github.com/lobehub/lobe-chat/issues/2907) ([bb33ba4](https://github.com/lobehub/lobe-chat/commit/bb33ba4))
46
+
47
+ </details>
48
+
49
+ <div align="right">
50
+
51
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
52
+
53
+ </div>
54
+
5
55
  ### [Version 1.0.2](https://github.com/lobehub/lobe-chat/compare/v1.0.1...v1.0.2)
6
56
 
7
57
  <sup>Released on **2024-06-17**</sup>
package/next.config.mjs CHANGED
@@ -65,6 +65,83 @@ const nextConfig = {
65
65
 
66
66
  return config;
67
67
  },
68
+
69
+ async headers() {
70
+ return [
71
+ {
72
+ source: '/icons/(.*).(png|jpe?g|gif|svg|ico|webp)',
73
+ headers: [
74
+ {
75
+ key: 'Cache-Control',
76
+ value: 'public, max-age=31536000, immutable',
77
+ },
78
+ ],
79
+ },
80
+ {
81
+ source: '/images/(.*).(png|jpe?g|gif|svg|ico|webp)',
82
+ headers: [
83
+ {
84
+ key: 'Cache-Control',
85
+ value: 'public, max-age=31536000, immutable',
86
+ },
87
+ ],
88
+ },
89
+ {
90
+ source: '/videos/(.*).(mp4|webm|ogg|avi|mov|wmv|flv|mkv)',
91
+ headers: [
92
+ {
93
+ key: 'Cache-Control',
94
+ value: 'public, max-age=31536000, immutable',
95
+ },
96
+ ],
97
+ },
98
+ {
99
+ source: '/screenshots/(.*).(png|jpe?g|gif|svg|ico|webp)',
100
+ headers: [
101
+ {
102
+ key: 'Cache-Control',
103
+ value: 'public, max-age=31536000, immutable',
104
+ },
105
+ ],
106
+ },
107
+ {
108
+ source: '/og/(.*).(png|jpe?g|gif|svg|ico|webp)',
109
+ headers: [
110
+ {
111
+ key: 'Cache-Control',
112
+ value: 'public, max-age=31536000, immutable',
113
+ },
114
+ ],
115
+ },
116
+ {
117
+ source: '/favicon.ico',
118
+ headers: [
119
+ {
120
+ key: 'Cache-Control',
121
+ value: 'public, max-age=31536000, immutable',
122
+ },
123
+ ],
124
+ },
125
+ {
126
+ source: '/favicon-32x32.ico',
127
+ headers: [
128
+ {
129
+ key: 'Cache-Control',
130
+ value: 'public, max-age=31536000, immutable',
131
+ },
132
+ ],
133
+ },
134
+ {
135
+ source: '/apple-touch-icon.png',
136
+ headers: [
137
+ {
138
+ key: 'Cache-Control',
139
+ value: 'public, max-age=31536000, immutable',
140
+ },
141
+ ],
142
+ },
143
+ ];
144
+ },
68
145
  };
69
146
 
70
147
  const noWrapper = (config) => config;
@@ -126,4 +203,4 @@ const withSentry =
126
203
  )
127
204
  : noWrapper;
128
205
 
129
- export default withBundleAnalyzer(withPWA(withSentry(nextConfig)));
206
+ export default withBundleAnalyzer(withPWA(withSentry(nextConfig)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -107,9 +107,9 @@
107
107
  "@khmyznikov/pwa-install": "^0.3.9",
108
108
  "@lobehub/chat-plugin-sdk": "^1.32.3",
109
109
  "@lobehub/chat-plugins-gateway": "^1.9.0",
110
- "@lobehub/icons": "^1.22.1",
110
+ "@lobehub/icons": "^1.23.0",
111
111
  "@lobehub/tts": "^1.24.1",
112
- "@lobehub/ui": "^1.141.2",
112
+ "@lobehub/ui": "^1.142.4",
113
113
  "@microsoft/fetch-event-source": "^2.0.1",
114
114
  "@neondatabase/serverless": "^0.9.3",
115
115
  "@next/third-parties": "^14.2.3",
@@ -127,7 +127,7 @@
127
127
  "brotli-wasm": "^3.0.0",
128
128
  "chroma-js": "^2.4.2",
129
129
  "dayjs": "^1.11.11",
130
- "debug": "^4.3.4",
130
+ "debug": "^4.3.5",
131
131
  "dexie": "^3.2.7",
132
132
  "diff": "^5.2.0",
133
133
  "drizzle-orm": "^0.30.10",
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "background_color": "#000000",
3
+ "cache_busting_mode": "all",
3
4
  "categories": ["productivity", "design", "development", "education"],
4
5
  "description": "Personal LLM productivity tool, brings you the best user experience of ChatGPT, OLLaMA, Gemini, Claude WebUI. Customize AI assistant features flexibly according to personalized needs to solve problems, enhance productivity, and explore future workflow in LobeChat.",
5
6
  "display": "standalone",
@@ -10,34 +11,48 @@
10
11
  "handle_links": "auto",
11
12
  "icons": [
12
13
  {
13
- "src": "/icons/icon-192x192.png",
14
+ "src": "/icons/icon-192x192.png?v=1",
14
15
  "sizes": "192x192",
15
16
  "type": "image/png",
16
- "purpose": "any"
17
+ "purpose": "any",
18
+ "cache_busting_mode": "query",
19
+ "max_age": 31536000,
20
+ "immutable": "true"
17
21
  },
18
22
  {
19
- "src": "/icons/icon-192x192.maskable.png",
23
+ "src": "/icons/icon-192x192.maskable.png?v=1",
20
24
  "sizes": "192x192",
21
25
  "type": "image/png",
22
- "purpose": "maskable"
26
+ "purpose": "maskable",
27
+ "cache_busting_mode": "query",
28
+ "max_age": 31536000,
29
+ "immutable": "true"
23
30
  },
24
31
  {
25
- "src": "/icons/icon-512x512.png",
32
+ "src": "/icons/icon-512x512.png?v=1",
26
33
  "sizes": "512x512",
27
34
  "type": "image/png",
28
- "purpose": "any"
35
+ "purpose": "any",
36
+ "cache_busting_mode": "query",
37
+ "max_age": 31536000,
38
+ "immutable": "true"
29
39
  },
30
40
  {
31
- "src": "/icons/icon-512x512.maskable.png",
41
+ "src": "/icons/icon-512x512.maskable.png?v=1",
32
42
  "sizes": "512x512",
33
43
  "type": "image/png",
34
- "purpose": "maskable"
44
+ "purpose": "maskable",
45
+ "cache_busting_mode": "query",
46
+ "max_age": 31536000,
47
+ "immutable": "true"
35
48
  }
36
49
  ],
37
50
  "id": "lobe-chat",
51
+ "immutable": "true",
38
52
  "launch_handler": {
39
53
  "client_mode": ["navigate-existing", "auto"]
40
54
  },
55
+ "max_age": 31536000,
41
56
  "name": "LobeChat",
42
57
  "orientation": "portrait",
43
58
  "related_applications": [
@@ -49,64 +64,94 @@
49
64
  "scope": "/",
50
65
  "screenshots": [
51
66
  {
52
- "src": "/screenshots/shot-1.mobile.png",
67
+ "src": "/screenshots/shot-1.mobile.png?v=1",
53
68
  "sizes": "640x1138",
54
69
  "type": "image/png",
55
- "form_factor": "narrow"
70
+ "form_factor": "narrow",
71
+ "cache_busting_mode": "query",
72
+ "max_age": 31536000,
73
+ "immutable": "true"
56
74
  },
57
75
  {
58
- "src": "/screenshots/shot-2.mobile.png",
76
+ "src": "/screenshots/shot-2.mobile.png?v=1",
59
77
  "sizes": "640x1138",
60
78
  "type": "image/png",
61
- "form_factor": "narrow"
79
+ "form_factor": "narrow",
80
+ "cache_busting_mode": "query",
81
+ "max_age": 31536000,
82
+ "immutable": "true"
62
83
  },
63
84
  {
64
- "src": "/screenshots/shot-3.mobile.png",
85
+ "src": "/screenshots/shot-3.mobile.png?v=1",
65
86
  "sizes": "640x1138",
66
87
  "type": "image/png",
67
- "form_factor": "narrow"
88
+ "form_factor": "narrow",
89
+ "cache_busting_mode": "query",
90
+ "max_age": 31536000,
91
+ "immutable": "true"
68
92
  },
69
93
  {
70
- "src": "/screenshots/shot-4.mobile.png",
94
+ "src": "/screenshots/shot-4.mobile.png?v=1",
71
95
  "sizes": "640x1138",
72
96
  "type": "image/png",
73
- "form_factor": "narrow"
97
+ "form_factor": "narrow",
98
+ "cache_busting_mode": "query",
99
+ "max_age": 31536000,
100
+ "immutable": "true"
74
101
  },
75
102
  {
76
- "src": "/screenshots/shot-5.mobile.png",
103
+ "src": "/screenshots/shot-5.mobile.png?v=1",
77
104
  "sizes": "640x1138",
78
105
  "type": "image/png",
79
- "form_factor": "narrow"
106
+ "form_factor": "narrow",
107
+ "cache_busting_mode": "query",
108
+ "max_age": 31536000,
109
+ "immutable": "true"
80
110
  },
81
111
  {
82
- "src": "/screenshots/shot-1.desktop.png",
112
+ "src": "/screenshots/shot-1.desktop.png?v=1",
83
113
  "sizes": "1280x676",
84
114
  "type": "image/png",
85
- "form_factor": "wide"
115
+ "form_factor": "wide",
116
+ "cache_busting_mode": "query",
117
+ "max_age": 31536000,
118
+ "immutable": "true"
86
119
  },
87
120
  {
88
- "src": "/screenshots/shot-2.desktop.png",
121
+ "src": "/screenshots/shot-2.desktop.png?v=1",
89
122
  "sizes": "1280x676",
90
123
  "type": "image/png",
91
- "form_factor": "wide"
124
+ "form_factor": "wide",
125
+ "cache_busting_mode": "query",
126
+ "max_age": 31536000,
127
+ "immutable": "true"
92
128
  },
93
129
  {
94
- "src": "/screenshots/shot-3.desktop.png",
130
+ "src": "/screenshots/shot-3.desktop.png?v=1",
95
131
  "sizes": "1280x676",
96
132
  "type": "image/png",
97
- "form_factor": "wide"
133
+ "form_factor": "wide",
134
+ "cache_busting_mode": "query",
135
+ "max_age": 31536000,
136
+ "immutable": "true"
98
137
  },
99
138
  {
100
- "src": "/screenshots/shot-4.desktop.png",
139
+ "src": "/screenshots/shot-4.desktop.png?v=1",
101
140
  "sizes": "1280x676",
102
141
  "type": "image/png",
103
- "form_factor": "wide"
142
+ "form_factor": "wide",
143
+ "cache_busting_mode": "query",
144
+ "max_age": 31536000,
145
+ "immutable": "true"
104
146
  },
105
147
  {
106
- "src": "/screenshots/shot-5.desktop.png",
148
+ "src": "/screenshots/shot-5.desktop.png?v=1",
107
149
  "sizes": "1280x676",
108
150
  "type": "image/png",
109
- "form_factor": "wide"
151
+ "form_factor": "wide",
152
+ "cache_busting_mode": "query",
153
+ "max_age": 31536000,
154
+ "immutable": "true"
110
155
  }
111
156
  ],
112
157
  "short_name": "LobeChat",
@@ -10,6 +10,7 @@ import {
10
10
  Moonshot,
11
11
  OpenRouter,
12
12
  Perplexity,
13
+ Stepfun,
13
14
  Together,
14
15
  Tongyi,
15
16
  ZeroOne,
@@ -31,6 +32,7 @@ import {
31
32
  OpenRouterProviderCard,
32
33
  PerplexityProviderCard,
33
34
  QwenProviderCard,
35
+ StepfunProviderCard,
34
36
  TogetherAIProviderCard,
35
37
  ZeroOneProviderCard,
36
38
  ZhiPuProviderCard,
@@ -135,6 +137,10 @@ export const useProviderList = (): ProviderItem[] => {
135
137
  ...ZeroOneProviderCard,
136
138
  title: <ZeroOne.Text size={20} />,
137
139
  },
140
+ {
141
+ ...StepfunProviderCard,
142
+ title: <Stepfun.Combine size={20} type={'color'} />,
143
+ },
138
144
  ],
139
145
  [azureProvider, ollamaProvider, ollamaProvider, bedrockProvider],
140
146
  );
@@ -74,7 +74,8 @@ const ProviderConfig = memo<ProviderConfigProps>(
74
74
  title,
75
75
  checkerItem,
76
76
  modelList,
77
- showBrowserRequest,
77
+ defaultShowBrowserRequest,
78
+ disableBrowserRequest,
78
79
  className,
79
80
  name,
80
81
  }) => {
@@ -126,25 +127,27 @@ const ProviderConfig = memo<ProviderConfigProps>(
126
127
  },
127
128
  /*
128
129
  * Conditions to show Client Fetch Switch
129
- * 1. Component props
130
- * 2. Provider allow to edit endpoint and the value of endpoint is not empty
131
- * 3. There is an apikey provided by user
130
+ * 1. provider is not disabled browser request
131
+ * 2. provider show browser request by default
132
+ * 3. Provider allow to edit endpoint and the value of endpoint is not empty
133
+ * 4. There is an apikey provided by user
132
134
  */
133
- (showBrowserRequest ||
134
- (showEndpoint && isProviderEndpointNotEmpty) ||
135
- (showApiKey && isProviderApiKeyNotEmpty)) && {
136
- children: (
137
- <Switch
138
- onChange={(enabled) => {
139
- setSettings({ [LLMProviderConfigKey]: { [id]: { fetchOnClient: enabled } } });
140
- }}
141
- value={isFetchOnClient}
142
- />
143
- ),
144
- desc: t('llm.fetchOnClient.desc'),
145
- label: t('llm.fetchOnClient.title'),
146
- minWidth: undefined,
147
- },
135
+ !disableBrowserRequest &&
136
+ (defaultShowBrowserRequest ||
137
+ (showEndpoint && isProviderEndpointNotEmpty) ||
138
+ (showApiKey && isProviderApiKeyNotEmpty)) && {
139
+ children: (
140
+ <Switch
141
+ onChange={(enabled) => {
142
+ setSettings({ [LLMProviderConfigKey]: { [id]: { fetchOnClient: enabled } } });
143
+ }}
144
+ value={isFetchOnClient}
145
+ />
146
+ ),
147
+ desc: t('llm.fetchOnClient.desc'),
148
+ label: t('llm.fetchOnClient.title'),
149
+ minWidth: undefined,
150
+ },
148
151
  {
149
152
  children: (
150
153
  <ProviderModelListSelect
@@ -24,6 +24,7 @@ import {
24
24
  ModelProvider,
25
25
  } from '@/libs/agent-runtime';
26
26
  import { AgentRuntime } from '@/libs/agent-runtime';
27
+ import { LobeStepfunAI } from '@/libs/agent-runtime/stepfun';
27
28
 
28
29
  import { initAgentRuntimeWithUserPayload } from './agentRuntime';
29
30
 
@@ -51,6 +52,7 @@ vi.mock('@/config/llm', () => ({
51
52
  OPENROUTER_API_KEY: 'test-openrouter-key',
52
53
  TOGETHERAI_API_KEY: 'test-togetherai-key',
53
54
  QWEN_API_KEY: 'test-qwen-key',
55
+ STEPFUN_API_KEY: 'test-stepfun-key',
54
56
  })),
55
57
  }));
56
58
 
@@ -192,6 +194,13 @@ describe('initAgentRuntimeWithUserPayload method', () => {
192
194
  expect(runtime['_runtime']).toBeInstanceOf(LobeGroq);
193
195
  });
194
196
 
197
+ it('Stepfun AI provider: with apikey', async () => {
198
+ const jwtPayload = { apiKey: 'user-stepfun-key' };
199
+ const runtime = await initAgentRuntimeWithUserPayload(ModelProvider.Stepfun, jwtPayload);
200
+ expect(runtime).toBeInstanceOf(AgentRuntime);
201
+ expect(runtime['_runtime']).toBeInstanceOf(LobeStepfunAI);
202
+ });
203
+
195
204
  it('Unknown Provider: with apikey and endpoint, should initialize to OpenAi', async () => {
196
205
  const jwtPayload: JWTPayload = {
197
206
  apiKey: 'user-unknown-key',
@@ -313,6 +322,14 @@ describe('initAgentRuntimeWithUserPayload method', () => {
313
322
  expect(runtime['_runtime']).toBeInstanceOf(LobeDeepSeekAI);
314
323
  });
315
324
 
325
+ it('Stepfun AI provider: without apikey', async () => {
326
+ const jwtPayload = {};
327
+ const runtime = await initAgentRuntimeWithUserPayload(ModelProvider.Stepfun, jwtPayload);
328
+
329
+ // ๅ‡่ฎพ LobeDeepSeekAI ๆ˜ฏ DeepSeek ๆไพ›่€…็š„ๅฎž็Žฐ็ฑป
330
+ expect(runtime['_runtime']).toBeInstanceOf(LobeStepfunAI);
331
+ });
332
+
316
333
  it('Together AI provider: without apikey', async () => {
317
334
  const jwtPayload = {};
318
335
  const runtime = await initAgentRuntimeWithUserPayload(ModelProvider.TogetherAI, jwtPayload);
@@ -163,6 +163,13 @@ const getLlmOptionsFromPayload = (provider: string, payload: JWTPayload) => {
163
163
 
164
164
  const apiKey = apiKeyManager.pick(payload?.apiKey || QWEN_API_KEY);
165
165
 
166
+ return { apiKey };
167
+ }
168
+ case ModelProvider.Stepfun: {
169
+ const { STEPFUN_API_KEY } = getLLMConfig();
170
+
171
+ const apiKey = apiKeyManager.pick(payload?.apiKey || STEPFUN_API_KEY);
172
+
166
173
  return { apiKey };
167
174
  }
168
175
  }
@@ -21,9 +21,9 @@ export const generateMetadata = async (): Promise<Metadata> => {
21
21
  },
22
22
  description: t('chat.description'),
23
23
  icons: {
24
- apple: '/apple-touch-icon.png',
25
- icon: '/favicon.ico',
26
- shortcut: '/favicon-32x32.ico',
24
+ apple: '/apple-touch-icon.png?v=1',
25
+ icon: '/favicon.ico?v=1',
26
+ shortcut: '/favicon-32x32.ico?v=1',
27
27
  },
28
28
  manifest: noManifest ? undefined : '/manifest.json',
29
29
  metadataBase: new URL(SITE_URL),
@@ -33,7 +33,7 @@ export const generateMetadata = async (): Promise<Metadata> => {
33
33
  {
34
34
  alt: t('chat.title'),
35
35
  height: 640,
36
- url: '/og/cover.png',
36
+ url: '/og/cover.png?v=1',
37
37
  width: 1200,
38
38
  },
39
39
  ],
@@ -50,7 +50,7 @@ export const generateMetadata = async (): Promise<Metadata> => {
50
50
  twitter: {
51
51
  card: 'summary_large_image',
52
52
  description: t('chat.description'),
53
- images: ['/og/cover.png'],
53
+ images: ['/og/cover.png?v=1'],
54
54
  site: '@lobehub',
55
55
  title: t('chat.title'),
56
56
  },
@@ -26,6 +26,7 @@ import {
26
26
  Rwkv,
27
27
  Spark,
28
28
  Stability,
29
+ Stepfun,
29
30
  Tongyi,
30
31
  Wenxin,
31
32
  Yi,
@@ -64,6 +65,7 @@ const ModelIcon = memo<ModelProviderIconProps>(({ model: originModel, size = 12
64
65
  if (model.startsWith('openchat')) return <OpenChat.Avatar size={size} />;
65
66
  if (model.includes('command')) return <Cohere.Avatar size={size} />;
66
67
  if (model.includes('dbrx')) return <Dbrx.Avatar size={size} />;
68
+ if (model.includes('step')) return <Stepfun.Avatar size={size} />;
67
69
 
68
70
  // below: To be supported in providers, move up if supported
69
71
  if (model.includes('baichuan'))
@@ -13,6 +13,7 @@ import {
13
13
  OpenAI,
14
14
  OpenRouter,
15
15
  Perplexity,
16
+ Stepfun,
16
17
  Together,
17
18
  Tongyi,
18
19
  ZeroOne,
@@ -30,7 +31,7 @@ interface ModelProviderIconProps {
30
31
  const ModelProviderIcon = memo<ModelProviderIconProps>(({ provider }) => {
31
32
  switch (provider) {
32
33
  case 'lobehub': {
33
- return <LobeHub size={20} />;
34
+ return <LobeHub.Color size={20} />;
34
35
  }
35
36
 
36
37
  case ModelProvider.ZhiPu: {
@@ -109,6 +110,10 @@ const ModelProviderIcon = memo<ModelProviderIconProps>(({ provider }) => {
109
110
  return <Tongyi size={20} />;
110
111
  }
111
112
 
113
+ case ModelProvider.Stepfun: {
114
+ return <Stepfun size={20} />;
115
+ }
116
+
112
117
  default: {
113
118
  return null;
114
119
  }
package/src/config/llm.ts CHANGED
@@ -117,6 +117,9 @@ export const getLLMConfig = () => {
117
117
 
118
118
  ENABLED_QWEN: z.boolean(),
119
119
  QWEN_API_KEY: z.string().optional(),
120
+
121
+ ENABLED_STEPFUN: z.boolean(),
122
+ STEPFUN_API_KEY: z.string().optional(),
120
123
  },
121
124
  runtimeEnv: {
122
125
  API_KEY_SELECT_MODE: process.env.API_KEY_SELECT_MODE,
@@ -188,6 +191,9 @@ export const getLLMConfig = () => {
188
191
 
189
192
  ENABLED_QWEN: !!process.env.QWEN_API_KEY,
190
193
  QWEN_API_KEY: process.env.QWEN_API_KEY,
194
+
195
+ ENABLED_STEPFUN: !!process.env.STEPFUN_API_KEY,
196
+ STEPFUN_API_KEY: process.env.STEPFUN_API_KEY,
191
197
  },
192
198
  });
193
199
  };
@@ -37,9 +37,9 @@ const Azure: ModelProviderCard = {
37
37
  vision: true,
38
38
  },
39
39
  ],
40
+ defaultShowBrowserRequest: true,
40
41
  id: 'azure',
41
42
  name: 'Azure',
42
- showBrowserRequest: true,
43
43
  };
44
44
 
45
45
  export default Azure;
@@ -14,6 +14,7 @@ import OpenAIProvider from './openai';
14
14
  import OpenRouterProvider from './openrouter';
15
15
  import PerplexityProvider from './perplexity';
16
16
  import QwenProvider from './qwen';
17
+ import StepfunProvider from './stepfun';
17
18
  import TogetherAIProvider from './togetherai';
18
19
  import ZeroOneProvider from './zeroone';
19
20
  import ZhiPuProvider from './zhipu';
@@ -35,6 +36,7 @@ export const LOBE_DEFAULT_MODEL_LIST: ChatModelCard[] = [
35
36
  PerplexityProvider.chatModels,
36
37
  AnthropicProvider.chatModels,
37
38
  ZeroOneProvider.chatModels,
39
+ StepfunProvider.chatModels,
38
40
  ].flat();
39
41
 
40
42
  export const DEFAULT_MODEL_PROVIDER_LIST = [
@@ -55,6 +57,7 @@ export const DEFAULT_MODEL_PROVIDER_LIST = [
55
57
  MoonshotProvider,
56
58
  ZeroOneProvider,
57
59
  ZhiPuProvider,
60
+ StepfunProvider,
58
61
  ];
59
62
 
60
63
  export const filterEnabledModels = (provider: ModelProviderCard) => {
@@ -75,6 +78,7 @@ export { default as OpenAIProviderCard } from './openai';
75
78
  export { default as OpenRouterProviderCard } from './openrouter';
76
79
  export { default as PerplexityProviderCard } from './perplexity';
77
80
  export { default as QwenProviderCard } from './qwen';
81
+ export { default as StepfunProviderCard } from './stepfun';
78
82
  export { default as TogetherAIProviderCard } from './togetherai';
79
83
  export { default as ZeroOneProviderCard } from './zeroone';
80
84
  export { default as ZhiPuProviderCard } from './zhipu';
@@ -180,11 +180,11 @@ const Ollama: ModelProviderCard = {
180
180
  vision: true,
181
181
  },
182
182
  ],
183
+ defaultShowBrowserRequest: true,
183
184
  id: 'ollama',
184
185
  modelList: { showModelFetcher: true },
185
186
  name: 'Ollama',
186
187
  showApiKey: false,
187
- showBrowserRequest: true,
188
188
  };
189
189
 
190
190
  export default Ollama;
@@ -0,0 +1,41 @@
1
+ import { ModelProviderCard } from '@/types/llm';
2
+
3
+ // ref https://platform.stepfun.com/docs/llm/text
4
+ const Stepfun: ModelProviderCard = {
5
+ chatModels: [
6
+ {
7
+ enabled: true,
8
+ id: 'step-1-256k',
9
+ tokens: 32_768,
10
+ },
11
+ {
12
+ enabled: true,
13
+ id: 'step-1-128k',
14
+ tokens: 32_768,
15
+ },
16
+ {
17
+ enabled: true,
18
+ id: 'step-1-32k',
19
+ tokens: 32_768,
20
+ },
21
+ {
22
+ enabled: true,
23
+ id: 'step-1v-32k',
24
+ tokens: 32_768,
25
+ vision: true,
26
+ },
27
+ {
28
+ id: 'step-1-8k',
29
+ tokens: 8192,
30
+ },
31
+ ],
32
+ checkModel: 'step-1-8k',
33
+ // after test, currently https://api.stepfun.com/v1/chat/completions has the CORS issue
34
+ // So we should close the browser request mode
35
+ disableBrowserRequest: true,
36
+ id: 'stepfun',
37
+ modelList: { showModelFetcher: true },
38
+ name: '้˜ถ่ทƒๆ˜Ÿ่พฐ',
39
+ };
40
+
41
+ export default Stepfun;
@@ -12,6 +12,7 @@ import {
12
12
  OpenRouterProviderCard,
13
13
  PerplexityProviderCard,
14
14
  QwenProviderCard,
15
+ StepfunProviderCard,
15
16
  TogetherAIProviderCard,
16
17
  ZeroOneProviderCard,
17
18
  ZhiPuProviderCard,
@@ -77,6 +78,10 @@ export const DEFAULT_LLM_CONFIG: UserModelProviderConfig = {
77
78
  enabled: false,
78
79
  enabledModels: filterEnabledModels(QwenProviderCard),
79
80
  },
81
+ stepfun: {
82
+ enabled: false,
83
+ enabledModels: filterEnabledModels(StepfunProviderCard),
84
+ },
80
85
  togetherai: {
81
86
  enabled: false,
82
87
  enabledModels: filterEnabledModels(TogetherAIProviderCard),
@@ -65,7 +65,7 @@ const Footer = memo<PropsWithChildren>(() => {
65
65
  </Flexbox>
66
66
  <GuideModal
67
67
  cancelText={t('footer.later')}
68
- cover={<GuideVideo height={269} src={'/videos/star.mp4'} width={358} />}
68
+ cover={<GuideVideo height={269} src={'/videos/star.mp4?v=1'} width={358} />}
69
69
  desc={t('footer.star.desc')}
70
70
  okText={t('footer.star.action')}
71
71
  onCancel={() => setOpenStar(false)}
@@ -78,7 +78,7 @@ const Footer = memo<PropsWithChildren>(() => {
78
78
  />
79
79
  <GuideModal
80
80
  cancelText={t('footer.later')}
81
- cover={<GuideVideo height={269} src={'/videos/feedback.mp4'} width={358} />}
81
+ cover={<GuideVideo height={269} src={'/videos/feedback.mp4?v=1'} width={358} />}
82
82
  desc={t('footer.feedback.desc')}
83
83
  okText={t('footer.feedback.action')}
84
84
  onCancel={() => setOpenFeedback(false)}
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useRouter, useSearchParams } from 'next/navigation';
4
4
  import { memo, useEffect } from 'react';
5
+ import { useTranslation } from 'react-i18next';
5
6
  import { createStoreUpdater } from 'zustand-utils';
6
7
 
7
8
  import { LOBE_URL_IMPORT_NAME } from '@/const/url';
@@ -14,8 +15,10 @@ import { useUserStore } from '@/store/user';
14
15
  import { authSelectors } from '@/store/user/selectors';
15
16
 
16
17
  const StoreInitialization = memo(() => {
17
- const router = useRouter();
18
+ // prefetch error ns to avoid don't show error content correctly
19
+ useTranslation('error');
18
20
 
21
+ const router = useRouter();
19
22
  const [isLogin, useInitUserState, importUrlShareSettings] = useUserStore((s) => [
20
23
  authSelectors.isLogin(s),
21
24
  s.useInitUserState,
@@ -57,21 +60,14 @@ const StoreInitialization = memo(() => {
57
60
  importUrlShareSettings(searchParam);
58
61
  }, [searchParam]);
59
62
 
60
- // useEffect(() => {
61
- // router.prefetch('/chat');
62
- // router.prefetch('/market');
63
- //
64
- // if (mobile) {
65
- // router.prefetch('/me');
66
- // router.prefetch('/chat/settings');
67
- // router.prefetch('/settings/common');
68
- // router.prefetch('/settings/agent');
69
- // router.prefetch('/settings/sync');
70
- // } else {
71
- // router.prefetch('/chat/settings/modal');
72
- // router.prefetch('/settings/modal');
73
- // }
74
- // }, [router, mobile]);
63
+ useEffect(() => {
64
+ if (mobile) {
65
+ router.prefetch('/me');
66
+ } else {
67
+ router.prefetch('/chat/settings/modal');
68
+ router.prefetch('/settings/modal');
69
+ }
70
+ }, [router, mobile]);
75
71
 
76
72
  return null;
77
73
  });
@@ -26,6 +26,7 @@ import {
26
26
  LobeZhipuAI,
27
27
  ModelProvider,
28
28
  } from '@/libs/agent-runtime';
29
+ import { LobeStepfunAI } from '@/libs/agent-runtime/stepfun';
29
30
 
30
31
  import { AgentChatOptions } from './AgentRuntime';
31
32
  import { LobeBedrockAIParams } from './bedrock';
@@ -227,6 +228,17 @@ describe('AgentRuntime', () => {
227
228
  });
228
229
  });
229
230
 
231
+ describe('Stepfun AI provider', () => {
232
+ it('should initialize correctly', async () => {
233
+ const jwtPayload: JWTPayload = { apiKey: 'user-stepfun-key' };
234
+ const runtime = await AgentRuntime.initializeWithProviderOptions(ModelProvider.Stepfun, {
235
+ stepfun: jwtPayload,
236
+ });
237
+
238
+ expect(runtime['_runtime']).toBeInstanceOf(LobeStepfunAI);
239
+ });
240
+ });
241
+
230
242
  describe('Together AI provider', () => {
231
243
  it('should initialize correctly', async () => {
232
244
  const jwtPayload: JWTPayload = { apiKey: 'user-togetherai-key' };
@@ -17,6 +17,7 @@ import { LobeOpenAI } from './openai';
17
17
  import { LobeOpenRouterAI } from './openrouter';
18
18
  import { LobePerplexityAI } from './perplexity';
19
19
  import { LobeQwenAI } from './qwen';
20
+ import { LobeStepfunAI } from './stepfun';
20
21
  import { LobeTogetherAI } from './togetherai';
21
22
  import {
22
23
  ChatCompetitionOptions,
@@ -114,6 +115,7 @@ class AgentRuntime {
114
115
  openrouter: Partial<ClientOptions>;
115
116
  perplexity: Partial<ClientOptions>;
116
117
  qwen: Partial<ClientOptions>;
118
+ stepfun: Partial<ClientOptions>;
117
119
  togetherai: Partial<ClientOptions>;
118
120
  zeroone: Partial<ClientOptions>;
119
121
  zhipu: Partial<ClientOptions>;
@@ -212,6 +214,11 @@ class AgentRuntime {
212
214
  runtimeModel = new LobeQwenAI(params.qwen ?? {});
213
215
  break;
214
216
  }
217
+
218
+ case ModelProvider.Stepfun: {
219
+ runtimeModel = new LobeStepfunAI(params.stepfun ?? {});
220
+ break;
221
+ }
215
222
  }
216
223
 
217
224
  return new AgentRuntime(runtimeModel);
@@ -0,0 +1,251 @@
1
+ // @vitest-environment node
2
+ import OpenAI from 'openai';
3
+ import { Mock, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4
+
5
+ import { LobeOpenAICompatibleRuntime } from '@/libs/agent-runtime';
6
+ import { ModelProvider } from '@/libs/agent-runtime';
7
+ import { AgentRuntimeErrorType } from '@/libs/agent-runtime';
8
+
9
+ import * as debugStreamModule from '../utils/debugStream';
10
+ import { LobeStepfunAI } from './index';
11
+
12
+ const provider = ModelProvider.Stepfun;
13
+ const defaultBaseURL = 'https://api.stepfun.com/v1';
14
+ const bizErrorType = AgentRuntimeErrorType.ProviderBizError;
15
+ const invalidErrorType = AgentRuntimeErrorType.InvalidProviderAPIKey;
16
+
17
+ // Mock the console.error to avoid polluting test output
18
+ vi.spyOn(console, 'error').mockImplementation(() => {});
19
+
20
+ let instance: LobeOpenAICompatibleRuntime;
21
+
22
+ beforeEach(() => {
23
+ instance = new LobeStepfunAI({ apiKey: 'test' });
24
+
25
+ // ไฝฟ็”จ vi.spyOn ๆฅๆจกๆ‹Ÿ chat.completions.create ๆ–นๆณ•
26
+ vi.spyOn(instance['client'].chat.completions, 'create').mockResolvedValue(
27
+ new ReadableStream() as any,
28
+ );
29
+ });
30
+
31
+ afterEach(() => {
32
+ vi.clearAllMocks();
33
+ });
34
+
35
+ describe('LobeStepfunAI', () => {
36
+ describe('init', () => {
37
+ it('should correctly initialize with an API key', async () => {
38
+ const instance = new LobeStepfunAI({ apiKey: 'test_api_key' });
39
+ expect(instance).toBeInstanceOf(LobeStepfunAI);
40
+ expect(instance.baseURL).toEqual(defaultBaseURL);
41
+ });
42
+ });
43
+
44
+ describe('chat', () => {
45
+ describe('Error', () => {
46
+ it('should return QwenBizError with an openai error response when OpenAI.APIError is thrown', async () => {
47
+ // Arrange
48
+ const apiError = new OpenAI.APIError(
49
+ 400,
50
+ {
51
+ status: 400,
52
+ error: {
53
+ message: 'Bad Request',
54
+ },
55
+ },
56
+ 'Error message',
57
+ {},
58
+ );
59
+
60
+ vi.spyOn(instance['client'].chat.completions, 'create').mockRejectedValue(apiError);
61
+
62
+ // Act
63
+ try {
64
+ await instance.chat({
65
+ messages: [{ content: 'Hello', role: 'user' }],
66
+ model: 'qwen-turbo',
67
+ temperature: 0.999,
68
+ });
69
+ } catch (e) {
70
+ expect(e).toEqual({
71
+ endpoint: defaultBaseURL,
72
+ error: {
73
+ error: { message: 'Bad Request' },
74
+ status: 400,
75
+ },
76
+ errorType: bizErrorType,
77
+ provider,
78
+ });
79
+ }
80
+ });
81
+
82
+ it('should throw AgentRuntimeError with InvalidQwenAPIKey if no apiKey is provided', async () => {
83
+ try {
84
+ new LobeStepfunAI({});
85
+ } catch (e) {
86
+ expect(e).toEqual({ errorType: invalidErrorType });
87
+ }
88
+ });
89
+
90
+ it('should return QwenBizError with the cause when OpenAI.APIError is thrown with cause', async () => {
91
+ // Arrange
92
+ const errorInfo = {
93
+ stack: 'abc',
94
+ cause: {
95
+ message: 'api is undefined',
96
+ },
97
+ };
98
+ const apiError = new OpenAI.APIError(400, errorInfo, 'module error', {});
99
+
100
+ vi.spyOn(instance['client'].chat.completions, 'create').mockRejectedValue(apiError);
101
+
102
+ // Act
103
+ try {
104
+ await instance.chat({
105
+ messages: [{ content: 'Hello', role: 'user' }],
106
+ model: 'qwen-turbo',
107
+ temperature: 0.999,
108
+ });
109
+ } catch (e) {
110
+ expect(e).toEqual({
111
+ endpoint: defaultBaseURL,
112
+ error: {
113
+ cause: { message: 'api is undefined' },
114
+ stack: 'abc',
115
+ },
116
+ errorType: bizErrorType,
117
+ provider,
118
+ });
119
+ }
120
+ });
121
+
122
+ it('should return QwenBizError with an cause response with desensitize Url', async () => {
123
+ // Arrange
124
+ const errorInfo = {
125
+ stack: 'abc',
126
+ cause: { message: 'api is undefined' },
127
+ };
128
+ const apiError = new OpenAI.APIError(400, errorInfo, 'module error', {});
129
+
130
+ instance = new LobeStepfunAI({
131
+ apiKey: 'test',
132
+
133
+ baseURL: 'https://api.abc.com/v1',
134
+ });
135
+
136
+ vi.spyOn(instance['client'].chat.completions, 'create').mockRejectedValue(apiError);
137
+
138
+ // Act
139
+ try {
140
+ await instance.chat({
141
+ messages: [{ content: 'Hello', role: 'user' }],
142
+ model: 'qwen-turbo',
143
+ temperature: 0.999,
144
+ });
145
+ } catch (e) {
146
+ expect(e).toEqual({
147
+ endpoint: 'https://api.***.com/v1',
148
+ error: {
149
+ cause: { message: 'api is undefined' },
150
+ stack: 'abc',
151
+ },
152
+ errorType: bizErrorType,
153
+ provider,
154
+ });
155
+ }
156
+ });
157
+
158
+ it('should throw an InvalidQwenAPIKey error type on 401 status code', async () => {
159
+ // Mock the API call to simulate a 401 error
160
+ const error = new Error('InvalidApiKey') as any;
161
+ error.status = 401;
162
+ vi.mocked(instance['client'].chat.completions.create).mockRejectedValue(error);
163
+
164
+ try {
165
+ await instance.chat({
166
+ messages: [{ content: 'Hello', role: 'user' }],
167
+ model: 'qwen-turbo',
168
+ temperature: 0.999,
169
+ });
170
+ } catch (e) {
171
+ expect(e).toEqual({
172
+ endpoint: defaultBaseURL,
173
+ error: new Error('InvalidApiKey'),
174
+ errorType: invalidErrorType,
175
+ provider,
176
+ });
177
+ }
178
+ });
179
+
180
+ it('should return AgentRuntimeError for non-OpenAI errors', async () => {
181
+ // Arrange
182
+ const genericError = new Error('Generic Error');
183
+
184
+ vi.spyOn(instance['client'].chat.completions, 'create').mockRejectedValue(genericError);
185
+
186
+ // Act
187
+ try {
188
+ await instance.chat({
189
+ messages: [{ content: 'Hello', role: 'user' }],
190
+ model: 'qwen-turbo',
191
+ temperature: 0.999,
192
+ });
193
+ } catch (e) {
194
+ expect(e).toEqual({
195
+ endpoint: defaultBaseURL,
196
+ errorType: 'AgentRuntimeError',
197
+ provider,
198
+ error: {
199
+ name: genericError.name,
200
+ cause: genericError.cause,
201
+ message: genericError.message,
202
+ stack: genericError.stack,
203
+ },
204
+ });
205
+ }
206
+ });
207
+ });
208
+
209
+ describe('DEBUG', () => {
210
+ it('should call debugStream and return StreamingTextResponse when DEBUG_STEPFUN_CHAT_COMPLETION is 1', async () => {
211
+ // Arrange
212
+ const mockProdStream = new ReadableStream() as any; // ๆจกๆ‹Ÿ็š„ prod ๆต
213
+ const mockDebugStream = new ReadableStream({
214
+ start(controller) {
215
+ controller.enqueue('Debug stream content');
216
+ controller.close();
217
+ },
218
+ }) as any;
219
+ mockDebugStream.toReadableStream = () => mockDebugStream; // ๆทปๅŠ  toReadableStream ๆ–นๆณ•
220
+
221
+ // ๆจกๆ‹Ÿ chat.completions.create ่ฟ”ๅ›žๅ€ผ๏ผŒๅŒ…ๆ‹ฌๆจกๆ‹Ÿ็š„ tee ๆ–นๆณ•
222
+ (instance['client'].chat.completions.create as Mock).mockResolvedValue({
223
+ tee: () => [mockProdStream, { toReadableStream: () => mockDebugStream }],
224
+ });
225
+
226
+ // ไฟๅญ˜ๅŽŸๅง‹็Žฏๅขƒๅ˜้‡ๅ€ผ
227
+ const originalDebugValue = process.env.DEBUG_STEPFUN_CHAT_COMPLETION;
228
+
229
+ // ๆจกๆ‹Ÿ็Žฏๅขƒๅ˜้‡
230
+ process.env.DEBUG_STEPFUN_CHAT_COMPLETION = '1';
231
+ vi.spyOn(debugStreamModule, 'debugStream').mockImplementation(() => Promise.resolve());
232
+
233
+ // ๆ‰ง่กŒๆต‹่ฏ•
234
+ // ่ฟ่กŒไฝ ็š„ๆต‹่ฏ•ๅ‡ฝๆ•ฐ๏ผŒ็กฎไฟๅฎƒไผšๅœจๆกไปถๆปก่ถณๆ—ถ่ฐƒ็”จ debugStream
235
+ // ๅ‡่ฎพ็š„ๆต‹่ฏ•ๅ‡ฝๆ•ฐ่ฐƒ็”จ๏ผŒไฝ ๅฏ่ƒฝ้œ€่ฆๆ นๆฎๅฎž้™…ๆƒ…ๅ†ต่ฐƒๆ•ด
236
+ await instance.chat({
237
+ messages: [{ content: 'Hello', role: 'user' }],
238
+ model: 'qwen-turbo',
239
+ stream: true,
240
+ temperature: 0.999,
241
+ });
242
+
243
+ // ้ชŒ่ฏ debugStream ่ขซ่ฐƒ็”จ
244
+ expect(debugStreamModule.debugStream).toHaveBeenCalled();
245
+
246
+ // ๆขๅคๅŽŸๅง‹็Žฏๅขƒๅ˜้‡ๅ€ผ
247
+ process.env.DEBUG_STEPFUN_CHAT_COMPLETION = originalDebugValue;
248
+ });
249
+ });
250
+ });
251
+ });
@@ -0,0 +1,10 @@
1
+ import { ModelProvider } from '../types';
2
+ import { LobeOpenAICompatibleFactory } from '../utils/openaiCompatibleFactory';
3
+
4
+ export const LobeStepfunAI = LobeOpenAICompatibleFactory({
5
+ baseURL: 'https://api.stepfun.com/v1',
6
+ debug: {
7
+ chatCompletion: () => process.env.DEBUG_STEPFUN_CHAT_COMPLETION === '1',
8
+ },
9
+ provider: ModelProvider.Stepfun,
10
+ });
@@ -36,6 +36,7 @@ export enum ModelProvider {
36
36
  OpenRouter = 'openrouter',
37
37
  Perplexity = 'perplexity',
38
38
  Qwen = 'qwen',
39
+ Stepfun = 'stepfun',
39
40
  TogetherAI = 'togetherai',
40
41
  ZeroOne = 'zeroone',
41
42
  ZhiPu = 'zhipu',
package/src/middleware.ts CHANGED
@@ -12,6 +12,8 @@ export const config = {
12
12
  '/(api|trpc)(.*)',
13
13
  // include the /
14
14
  '/',
15
+ '/chat(.*)',
16
+ // โ†“ cloud โ†“
15
17
  ],
16
18
  };
17
19
 
@@ -33,6 +33,7 @@ export const getServerGlobalConfig = () => {
33
33
  ENABLED_MINIMAX,
34
34
  ENABLED_MISTRAL,
35
35
  ENABLED_QWEN,
36
+ ENABLED_STEPFUN,
36
37
 
37
38
  ENABLED_AZURE_OPENAI,
38
39
  AZURE_MODEL_LIST,
@@ -104,6 +105,8 @@ export const getServerGlobalConfig = () => {
104
105
  perplexity: { enabled: ENABLED_PERPLEXITY },
105
106
  qwen: { enabled: ENABLED_QWEN },
106
107
 
108
+ stepfun: { enabled: ENABLED_STEPFUN },
109
+
107
110
  togetherai: {
108
111
  enabled: ENABLED_TOGETHERAI,
109
112
  enabledModels: extractEnabledModels(TOGETHERAI_MODEL_LIST),
@@ -112,7 +115,6 @@ export const getServerGlobalConfig = () => {
112
115
  modelString: TOGETHERAI_MODEL_LIST,
113
116
  }),
114
117
  },
115
-
116
118
  zeroone: { enabled: ENABLED_ZEROONE },
117
119
  zhipu: { enabled: ENABLED_ZHIPU },
118
120
  },
@@ -7,7 +7,7 @@ export class Meta {
7
7
  public generate({
8
8
  description = 'LobeChat offers you the best ChatGPT, OLLaMA, Gemini, Claude WebUI user experience',
9
9
  title,
10
- image = '/og/cover.png',
10
+ image = '/og/cover.png?v=1',
11
11
  url,
12
12
  type = 'website',
13
13
  tags,
@@ -168,6 +168,23 @@ describe('getProviderAuthPayload', () => {
168
168
  });
169
169
  });
170
170
 
171
+ it('should return correct payload for Stepfun provider', () => {
172
+ // ๅ‡่ฎพ็š„ OpenAI ้…็ฝฎ
173
+ const mockOpenAIConfig = {
174
+ apiKey: 'stepfun-api-key',
175
+ baseURL: 'stepfun-baseURL',
176
+ };
177
+ act(() => {
178
+ setModelProviderConfig('stepfun', mockOpenAIConfig);
179
+ });
180
+
181
+ const payload = getProviderAuthPayload(ModelProvider.Stepfun);
182
+ expect(payload).toEqual({
183
+ apiKey: mockOpenAIConfig.apiKey,
184
+ endpoint: mockOpenAIConfig.baseURL,
185
+ });
186
+ });
187
+
171
188
  it('should return an empty object or throw an error for an unknown provider', () => {
172
189
  const payload = getProviderAuthPayload('UnknownProvider');
173
190
  expect(payload).toEqual({});
package/src/types/llm.ts CHANGED
@@ -49,6 +49,22 @@ export interface ModelProviderCard {
49
49
  * the default model that used for connection check
50
50
  */
51
51
  checkModel?: string;
52
+ /**
53
+ * whether provider show browser request option by default
54
+ *
55
+ * @default false
56
+ */
57
+ defaultShowBrowserRequest?: boolean;
58
+ /**
59
+ * some provider server like stepfun and aliyun don't support browser request,
60
+ * So we should disable it
61
+ *
62
+ * @default false
63
+ */
64
+ disableBrowserRequest?: boolean;
65
+ /**
66
+ * whether provider is enabled by default
67
+ */
52
68
  enabled?: boolean;
53
69
  id: string;
54
70
  modelList?: {
@@ -57,6 +73,9 @@ export interface ModelProviderCard {
57
73
  placeholder?: string;
58
74
  showModelFetcher?: boolean;
59
75
  };
76
+ /**
77
+ * the name show for end user
78
+ */
60
79
  name: string;
61
80
  proxyUrl?:
62
81
  | {
@@ -65,8 +84,11 @@ export interface ModelProviderCard {
65
84
  title?: string;
66
85
  }
67
86
  | false;
87
+ /**
88
+ * whether show api key in the provider config
89
+ * so provider like ollama don't need api key field
90
+ */
68
91
  showApiKey?: boolean;
69
- showBrowserRequest?: boolean;
70
92
  }
71
93
 
72
94
  // ่ฏญ่จ€ๆจกๅž‹็š„่ฎพ็ฝฎๅ‚ๆ•ฐ
@@ -31,6 +31,7 @@ export interface UserKeyVaults {
31
31
  password?: string;
32
32
  perplexity?: OpenAICompatibleKeyVault;
33
33
  qwen?: OpenAICompatibleKeyVault;
34
+ stepfun?: OpenAICompatibleKeyVault;
34
35
  togetherai?: OpenAICompatibleKeyVault;
35
36
  zeroone?: OpenAICompatibleKeyVault;
36
37
  zhipu?: OpenAICompatibleKeyVault;