@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 +50 -0
- package/next.config.mjs +78 -1
- package/package.json +4 -4
- package/public/manifest.json +73 -28
- package/src/app/(main)/settings/llm/ProviderList/providers.tsx +6 -0
- package/src/app/(main)/settings/llm/components/ProviderConfig/index.tsx +22 -19
- package/src/app/api/chat/agentRuntime.test.ts +17 -0
- package/src/app/api/chat/agentRuntime.ts +7 -0
- package/src/app/metadata.ts +5 -5
- package/src/components/ModelIcon/index.tsx +2 -0
- package/src/components/ModelProviderIcon/index.tsx +6 -1
- package/src/config/llm.ts +6 -0
- package/src/config/modelProviders/azure.ts +1 -1
- package/src/config/modelProviders/index.ts +4 -0
- package/src/config/modelProviders/ollama.ts +1 -1
- package/src/config/modelProviders/stepfun.ts +41 -0
- package/src/const/settings/llm.ts +5 -0
- package/src/features/Setting/Footer.tsx +2 -2
- package/src/layout/GlobalProvider/StoreInitialization.tsx +12 -16
- package/src/libs/agent-runtime/AgentRuntime.test.ts +12 -0
- package/src/libs/agent-runtime/AgentRuntime.ts +7 -0
- package/src/libs/agent-runtime/stepfun/index.test.ts +251 -0
- package/src/libs/agent-runtime/stepfun/index.ts +10 -0
- package/src/libs/agent-runtime/types/type.ts +1 -0
- package/src/middleware.ts +2 -0
- package/src/server/globalConfig/index.ts +3 -1
- package/src/server/metadata.ts +1 -1
- package/src/services/_auth.test.ts +17 -0
- package/src/types/llm.ts +23 -1
- package/src/types/user/settings/keyVaults.ts +1 -0
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
|
+
[](#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
|
+
[](#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.
|
|
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.
|
|
110
|
+
"@lobehub/icons": "^1.23.0",
|
|
111
111
|
"@lobehub/tts": "^1.24.1",
|
|
112
|
-
"@lobehub/ui": "^1.
|
|
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.
|
|
130
|
+
"debug": "^4.3.5",
|
|
131
131
|
"dexie": "^3.2.7",
|
|
132
132
|
"diff": "^5.2.0",
|
|
133
133
|
"drizzle-orm": "^0.30.10",
|
package/public/manifest.json
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
130
|
-
* 2.
|
|
131
|
-
* 3.
|
|
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
|
-
|
|
134
|
-
(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
}
|
package/src/app/metadata.ts
CHANGED
|
@@ -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
|
};
|
|
@@ -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
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
+
});
|
package/src/middleware.ts
CHANGED
|
@@ -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
|
},
|
package/src/server/metadata.ts
CHANGED
|
@@ -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;
|