@lobehub/chat 0.147.21 → 0.148.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 (52) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/locales/ar/setting.json +4 -0
  3. package/locales/bg-BG/setting.json +4 -0
  4. package/locales/de-DE/setting.json +4 -0
  5. package/locales/en-US/setting.json +4 -0
  6. package/locales/es-ES/setting.json +4 -0
  7. package/locales/fr-FR/setting.json +4 -0
  8. package/locales/it-IT/setting.json +4 -0
  9. package/locales/ja-JP/setting.json +4 -0
  10. package/locales/ko-KR/setting.json +4 -0
  11. package/locales/nl-NL/setting.json +4 -0
  12. package/locales/pl-PL/setting.json +4 -0
  13. package/locales/pt-BR/setting.json +4 -0
  14. package/locales/ru-RU/setting.json +4 -0
  15. package/locales/tr-TR/setting.json +4 -0
  16. package/locales/vi-VN/setting.json +4 -0
  17. package/locales/zh-CN/setting.json +4 -0
  18. package/locales/zh-TW/setting.json +4 -0
  19. package/package.json +3 -2
  20. package/public/favicon-32x32.ico +0 -0
  21. package/public/favicon.ico +0 -0
  22. package/public/icons/apple-touch-icon.png +0 -0
  23. package/src/app/api/chat/[provider]/route.test.ts +5 -7
  24. package/src/app/api/chat/[provider]/route.ts +13 -7
  25. package/src/app/api/chat/agentRuntime.test.ts +195 -451
  26. package/src/app/api/chat/agentRuntime.ts +197 -280
  27. package/src/app/api/chat/models/[provider]/route.ts +2 -2
  28. package/src/app/chat/features/TopicListContent/Topic/TopicContent.tsx +2 -2
  29. package/src/app/metadata.ts +3 -5
  30. package/src/app/settings/llm/components/ProviderConfig/index.tsx +23 -1
  31. package/src/app/settings/llm/index.tsx +2 -2
  32. package/src/app/settings/llm/page.tsx +1 -5
  33. package/src/features/ChatInput/Topic/index.tsx +6 -2
  34. package/src/features/Conversation/components/ChatItem/index.tsx +8 -3
  35. package/src/libs/agent-runtime/AgentRuntime.test.ts +400 -0
  36. package/src/libs/agent-runtime/AgentRuntime.ts +192 -0
  37. package/src/libs/agent-runtime/index.ts +1 -0
  38. package/src/libs/swr/index.ts +9 -0
  39. package/src/locales/default/setting.ts +4 -0
  40. package/src/services/__tests__/chat.test.ts +287 -1
  41. package/src/services/chat.ts +148 -2
  42. package/src/store/chat/slices/message/action.ts +80 -42
  43. package/src/store/chat/slices/message/initialState.ts +1 -1
  44. package/src/store/chat/slices/message/reducer.ts +32 -1
  45. package/src/store/chat/slices/topic/action.test.ts +25 -2
  46. package/src/store/chat/slices/topic/action.ts +24 -7
  47. package/src/store/chat/slices/topic/reducer.test.ts +141 -0
  48. package/src/store/chat/slices/topic/reducer.ts +67 -0
  49. package/src/store/global/slices/settings/selectors/modelConfig.ts +13 -0
  50. package/src/store/session/slices/session/action.ts +4 -5
  51. package/src/types/settings/modelProvider.ts +4 -0
  52. package/vercel.json +1 -1
@@ -7,25 +7,7 @@ import {
7
7
  TracePayload,
8
8
  TraceTagMap,
9
9
  } from '@/const/trace';
10
- import {
11
- ChatStreamPayload,
12
- LobeAnthropicAI,
13
- LobeAzureOpenAI,
14
- LobeBedrockAI,
15
- LobeGoogleAI,
16
- LobeGroq,
17
- LobeMistralAI,
18
- LobeMoonshotAI,
19
- LobeOllamaAI,
20
- LobeOpenAI,
21
- LobeOpenRouterAI,
22
- LobePerplexityAI,
23
- LobeRuntimeAI,
24
- LobeTogetherAI,
25
- LobeZeroOneAI,
26
- LobeZhipuAI,
27
- ModelProvider,
28
- } from '@/libs/agent-runtime';
10
+ import { AgentRuntime, ChatStreamPayload, ModelProvider } from '@/libs/agent-runtime';
29
11
  import { TraceClient } from '@/libs/traces';
30
12
 
31
13
  import apiKeyManager from './apiKeyManager';
@@ -36,272 +18,207 @@ export interface AgentChatOptions {
36
18
  trace?: TracePayload;
37
19
  }
38
20
 
39
- class AgentRuntime {
40
- private _runtime: LobeRuntimeAI;
41
-
42
- constructor(runtime: LobeRuntimeAI) {
43
- this._runtime = runtime;
44
- }
45
-
46
- async chat(
47
- payload: ChatStreamPayload,
48
- { trace: tracePayload, provider, enableTrace }: AgentChatOptions,
49
- ) {
50
- const { messages, model, tools, ...parameters } = payload;
51
-
52
- // if not enabled trace then just call the runtime
53
- if (!enableTrace) return this._runtime.chat(payload);
54
-
55
- // create a trace to monitor the completion
56
- const traceClient = new TraceClient();
57
- const trace = traceClient.createTrace({
58
- id: tracePayload?.traceId,
59
- input: messages,
60
- metadata: { provider },
61
- name: tracePayload?.traceName,
62
- sessionId: `${tracePayload?.sessionId || INBOX_SESSION_ID}@${tracePayload?.topicId || 'start'}`,
63
- tags: tracePayload?.tags,
64
- userId: tracePayload?.userId,
65
- });
66
-
67
- const generation = trace?.generation({
68
- input: messages,
69
- metadata: { provider },
70
- model,
71
- modelParameters: parameters as any,
72
- name: `Chat Completion (${provider})`,
73
- startTime: new Date(),
74
- });
75
-
76
- return this._runtime.chat(payload, {
77
- callback: {
78
- experimental_onToolCall: async () => {
79
- trace?.update({
80
- tags: [...(tracePayload?.tags || []), TraceTagMap.ToolsCall],
81
- });
82
- },
83
-
84
- onCompletion: async (completion) => {
85
- generation?.update({
86
- endTime: new Date(),
87
- metadata: { provider, tools },
88
- output: completion,
89
- });
90
-
91
- trace?.update({ output: completion });
92
- },
93
-
94
- onFinal: async () => {
95
- await traceClient.shutdownAsync();
96
- },
97
-
98
- onStart: () => {
99
- generation?.update({ completionStartTime: new Date() });
100
- },
101
- },
102
- headers: {
103
- [LOBE_CHAT_OBSERVATION_ID]: generation?.id,
104
- [LOBE_CHAT_TRACE_ID]: trace?.id,
105
- },
106
- });
107
- }
108
-
109
- async models() {
110
- return this._runtime.models?.();
111
- }
112
-
113
- static async initializeWithUserPayload(provider: string, payload: JWTPayload) {
114
- let runtimeModel: LobeRuntimeAI;
115
-
116
- switch (provider) {
117
- default:
118
- case 'oneapi':
119
- case ModelProvider.OpenAI: {
120
- runtimeModel = this.initOpenAI(payload);
121
- break;
122
- }
123
-
124
- case ModelProvider.Azure: {
125
- runtimeModel = this.initAzureOpenAI(payload);
126
- break;
127
- }
128
-
129
- case ModelProvider.ZhiPu: {
130
- runtimeModel = await this.initZhipu(payload);
131
- break;
132
- }
133
-
134
- case ModelProvider.Google: {
135
- runtimeModel = this.initGoogle(payload);
136
- break;
137
- }
138
-
139
- case ModelProvider.Moonshot: {
140
- runtimeModel = this.initMoonshot(payload);
141
- break;
142
- }
143
-
144
- case ModelProvider.Bedrock: {
145
- runtimeModel = this.initBedrock(payload);
146
- break;
147
- }
148
-
149
- case ModelProvider.Ollama: {
150
- runtimeModel = this.initOllama(payload);
151
- break;
152
- }
153
-
154
- case ModelProvider.Perplexity: {
155
- runtimeModel = this.initPerplexity(payload);
156
- break;
157
- }
158
-
159
- case ModelProvider.Anthropic: {
160
- runtimeModel = this.initAnthropic(payload);
161
- break;
162
- }
163
-
164
- case ModelProvider.Mistral: {
165
- runtimeModel = this.initMistral(payload);
166
- break;
167
- }
168
-
169
- case ModelProvider.Groq: {
170
- runtimeModel = this.initGroq(payload);
171
- break;
172
- }
173
-
174
- case ModelProvider.OpenRouter: {
175
- runtimeModel = this.initOpenRouter(payload);
176
- break;
177
- }
178
-
179
- case ModelProvider.TogetherAI: {
180
- runtimeModel = this.initTogetherAI(payload);
181
- break;
182
- }
183
-
184
- case ModelProvider.ZeroOne: {
185
- runtimeModel = this.initZeroOne(payload);
186
- break;
21
+ /**
22
+ * Retrieves the options object from environment and apikeymanager
23
+ * based on the provider and payload.
24
+ *
25
+ * @param provider - The model provider.
26
+ * @param payload - The JWT payload.
27
+ * @returns The options object.
28
+ */
29
+ const getLlmOptionsFromPayload = (provider: string, payload: JWTPayload) => {
30
+ switch (provider) {
31
+ default: // Use Openai options as default
32
+ case ModelProvider.OpenAI: {
33
+ const { OPENAI_API_KEY, OPENAI_PROXY_URL } = getServerConfig();
34
+ const openaiApiKey = payload?.apiKey || OPENAI_API_KEY;
35
+ const baseURL = payload?.endpoint || OPENAI_PROXY_URL;
36
+ const apiKey = apiKeyManager.pick(openaiApiKey);
37
+ return {
38
+ apiKey,
39
+ baseURL,
40
+ };
41
+ }
42
+ case ModelProvider.Azure: {
43
+ const { AZURE_API_KEY, AZURE_API_VERSION, AZURE_ENDPOINT } = getServerConfig();
44
+ const apiKey = apiKeyManager.pick(payload?.apiKey || AZURE_API_KEY);
45
+ const endpoint = payload?.endpoint || AZURE_ENDPOINT;
46
+ const apiVersion = payload?.azureApiVersion || AZURE_API_VERSION;
47
+ return {
48
+ apiVersion,
49
+ apikey: apiKey,
50
+ endpoint,
51
+ };
52
+ }
53
+ case ModelProvider.ZhiPu: {
54
+ const { ZHIPU_API_KEY } = getServerConfig();
55
+ const apiKey = apiKeyManager.pick(payload?.apiKey || ZHIPU_API_KEY);
56
+ return {
57
+ apiKey,
58
+ };
59
+ }
60
+ case ModelProvider.Google: {
61
+ const { GOOGLE_API_KEY, GOOGLE_PROXY_URL } = getServerConfig();
62
+ const apiKey = apiKeyManager.pick(payload?.apiKey || GOOGLE_API_KEY);
63
+ const baseURL = payload?.endpoint || GOOGLE_PROXY_URL;
64
+ return {
65
+ apiKey,
66
+ baseURL,
67
+ };
68
+ }
69
+ case ModelProvider.Moonshot: {
70
+ const { MOONSHOT_API_KEY, MOONSHOT_PROXY_URL } = getServerConfig();
71
+ const apiKey = apiKeyManager.pick(payload?.apiKey || MOONSHOT_API_KEY);
72
+ return {
73
+ apiKey,
74
+ baseURL: MOONSHOT_PROXY_URL,
75
+ };
76
+ }
77
+ case ModelProvider.Bedrock: {
78
+ const { AWS_SECRET_ACCESS_KEY, AWS_ACCESS_KEY_ID, AWS_REGION } = getServerConfig();
79
+ let accessKeyId: string | undefined = AWS_ACCESS_KEY_ID;
80
+ let accessKeySecret: string | undefined = AWS_SECRET_ACCESS_KEY;
81
+ let region = AWS_REGION;
82
+ // if the payload has the api key, use user
83
+ if (payload.apiKey) {
84
+ accessKeyId = payload?.awsAccessKeyId;
85
+ accessKeySecret = payload?.awsSecretAccessKey;
86
+ region = payload?.awsRegion;
187
87
  }
88
+ return {
89
+ accessKeyId,
90
+ accessKeySecret,
91
+ region,
92
+ };
188
93
  }
189
-
190
- return new AgentRuntime(runtimeModel);
191
- }
192
-
193
- private static initOpenAI(payload: JWTPayload) {
194
- const { OPENAI_API_KEY, OPENAI_PROXY_URL } = getServerConfig();
195
- const openaiApiKey = payload?.apiKey || OPENAI_API_KEY;
196
- const baseURL = payload?.endpoint || OPENAI_PROXY_URL;
197
-
198
- const apiKey = apiKeyManager.pick(openaiApiKey);
199
-
200
- return new LobeOpenAI({ apiKey, baseURL });
201
- }
202
-
203
- private static initAzureOpenAI(payload: JWTPayload) {
204
- const { AZURE_API_KEY, AZURE_API_VERSION, AZURE_ENDPOINT } = getServerConfig();
205
- const apiKey = apiKeyManager.pick(payload?.apiKey || AZURE_API_KEY);
206
- const endpoint = payload?.endpoint || AZURE_ENDPOINT;
207
- const apiVersion = payload?.azureApiVersion || AZURE_API_VERSION;
208
-
209
- return new LobeAzureOpenAI(endpoint, apiKey, apiVersion);
210
- }
211
-
212
- private static async initZhipu(payload: JWTPayload) {
213
- const { ZHIPU_API_KEY } = getServerConfig();
214
- const apiKey = apiKeyManager.pick(payload?.apiKey || ZHIPU_API_KEY);
215
-
216
- return LobeZhipuAI.fromAPIKey({ apiKey });
217
- }
218
-
219
- private static initMoonshot(payload: JWTPayload) {
220
- const { MOONSHOT_API_KEY, MOONSHOT_PROXY_URL } = getServerConfig();
221
- const apiKey = apiKeyManager.pick(payload?.apiKey || MOONSHOT_API_KEY);
222
-
223
- return new LobeMoonshotAI({ apiKey, baseURL: MOONSHOT_PROXY_URL });
224
- }
225
-
226
- private static initGoogle(payload: JWTPayload) {
227
- const { GOOGLE_API_KEY, GOOGLE_PROXY_URL } = getServerConfig();
228
- const apiKey = apiKeyManager.pick(payload?.apiKey || GOOGLE_API_KEY);
229
- const baseURL = payload?.endpoint || GOOGLE_PROXY_URL;
230
-
231
- return new LobeGoogleAI({ apiKey, baseURL });
232
- }
233
-
234
- private static initBedrock(payload: JWTPayload) {
235
- const { AWS_SECRET_ACCESS_KEY, AWS_ACCESS_KEY_ID, AWS_REGION } = getServerConfig();
236
-
237
- let accessKeyId: string | undefined = AWS_ACCESS_KEY_ID;
238
- let accessKeySecret: string | undefined = AWS_SECRET_ACCESS_KEY;
239
- let region = AWS_REGION;
240
- // if the payload has the api key, use user
241
- if (payload.apiKey) {
242
- accessKeyId = payload?.awsAccessKeyId;
243
- accessKeySecret = payload?.awsSecretAccessKey;
244
- region = payload?.awsRegion;
94
+ case ModelProvider.Ollama: {
95
+ const { OLLAMA_PROXY_URL } = getServerConfig();
96
+ const baseURL = payload?.endpoint || OLLAMA_PROXY_URL;
97
+ return {
98
+ baseURL,
99
+ };
100
+ }
101
+ case ModelProvider.Perplexity: {
102
+ const { PERPLEXITY_API_KEY } = getServerConfig();
103
+ const apiKey = apiKeyManager.pick(payload?.apiKey || PERPLEXITY_API_KEY);
104
+ return {
105
+ apiKey,
106
+ };
107
+ }
108
+ case ModelProvider.Anthropic: {
109
+ const { ANTHROPIC_API_KEY, ANTHROPIC_PROXY_URL } = getServerConfig();
110
+ const apiKey = apiKeyManager.pick(payload?.apiKey || ANTHROPIC_API_KEY);
111
+ const baseURL = payload?.endpoint || ANTHROPIC_PROXY_URL;
112
+ return {
113
+ apiKey,
114
+ baseURL,
115
+ };
116
+ }
117
+ case ModelProvider.Mistral: {
118
+ const { MISTRAL_API_KEY } = getServerConfig();
119
+ const apiKey = apiKeyManager.pick(payload?.apiKey || MISTRAL_API_KEY);
120
+ return {
121
+ apiKey,
122
+ };
123
+ }
124
+ case ModelProvider.Groq: {
125
+ const { GROQ_API_KEY } = getServerConfig();
126
+ const apiKey = apiKeyManager.pick(payload?.apiKey || GROQ_API_KEY);
127
+ return {
128
+ apiKey,
129
+ };
130
+ }
131
+ case ModelProvider.OpenRouter: {
132
+ const { OPENROUTER_API_KEY } = getServerConfig();
133
+ const apiKey = apiKeyManager.pick(payload?.apiKey || OPENROUTER_API_KEY);
134
+ return {
135
+ apiKey,
136
+ };
137
+ }
138
+ case ModelProvider.TogetherAI: {
139
+ const { TOGETHERAI_API_KEY } = getServerConfig();
140
+ const apiKey = apiKeyManager.pick(payload?.apiKey || TOGETHERAI_API_KEY);
141
+ return {
142
+ apiKey,
143
+ };
144
+ }
145
+ case ModelProvider.ZeroOne: {
146
+ const { ZEROONE_API_KEY } = getServerConfig();
147
+ const apiKey = apiKeyManager.pick(payload?.apiKey || ZEROONE_API_KEY);
148
+ return {
149
+ apiKey,
150
+ };
245
151
  }
246
-
247
- return new LobeBedrockAI({ accessKeyId, accessKeySecret, region });
248
- }
249
-
250
- private static initOllama(payload: JWTPayload) {
251
- const { OLLAMA_PROXY_URL } = getServerConfig();
252
- const baseURL = payload?.endpoint || OLLAMA_PROXY_URL;
253
-
254
- return new LobeOllamaAI({ baseURL });
255
- }
256
-
257
- private static initPerplexity(payload: JWTPayload) {
258
- const { PERPLEXITY_API_KEY } = getServerConfig();
259
- const apiKey = apiKeyManager.pick(payload?.apiKey || PERPLEXITY_API_KEY);
260
-
261
- return new LobePerplexityAI({ apiKey });
262
- }
263
-
264
- private static initAnthropic(payload: JWTPayload) {
265
- const { ANTHROPIC_API_KEY, ANTHROPIC_PROXY_URL } = getServerConfig();
266
- const apiKey = apiKeyManager.pick(payload?.apiKey || ANTHROPIC_API_KEY);
267
- const baseURL = payload?.endpoint || ANTHROPIC_PROXY_URL;
268
- return new LobeAnthropicAI({ apiKey, baseURL });
269
- }
270
-
271
- private static initMistral(payload: JWTPayload) {
272
- const { MISTRAL_API_KEY } = getServerConfig();
273
- const apiKey = apiKeyManager.pick(payload?.apiKey || MISTRAL_API_KEY);
274
-
275
- return new LobeMistralAI({ apiKey });
276
- }
277
-
278
- private static initGroq(payload: JWTPayload) {
279
- const { GROQ_API_KEY } = getServerConfig();
280
- const apiKey = apiKeyManager.pick(payload?.apiKey || GROQ_API_KEY);
281
-
282
- return new LobeGroq({ apiKey });
283
- }
284
-
285
- private static initOpenRouter(payload: JWTPayload) {
286
- const { OPENROUTER_API_KEY } = getServerConfig();
287
- const apiKey = apiKeyManager.pick(payload?.apiKey || OPENROUTER_API_KEY);
288
-
289
- return new LobeOpenRouterAI({ apiKey });
290
152
  }
153
+ };
154
+
155
+ /**
156
+ * Initializes the agent runtime with the user payload in backend
157
+ * @param provider - The provider name.
158
+ * @param payload - The JWT payload.
159
+ * @returns A promise that resolves when the agent runtime is initialized.
160
+ */
161
+ export const initAgentRuntimeWithUserPayload = (provider: string, payload: JWTPayload) => {
162
+ return AgentRuntime.initializeWithProviderOptions(provider, {
163
+ [provider]: getLlmOptionsFromPayload(provider, payload),
164
+ });
165
+ };
166
+
167
+ export const createTraceOptions = (
168
+ payload: ChatStreamPayload,
169
+ { trace: tracePayload, provider }: AgentChatOptions,
170
+ ) => {
171
+ const { messages, model, tools, ...parameters } = payload;
172
+ // create a trace to monitor the completion
173
+ const traceClient = new TraceClient();
174
+ const trace = traceClient.createTrace({
175
+ id: tracePayload?.traceId,
176
+ input: messages,
177
+ metadata: { provider },
178
+ name: tracePayload?.traceName,
179
+ sessionId: `${tracePayload?.sessionId || INBOX_SESSION_ID}@${tracePayload?.topicId || 'start'}`,
180
+ tags: tracePayload?.tags,
181
+ userId: tracePayload?.userId,
182
+ });
183
+
184
+ const generation = trace?.generation({
185
+ input: messages,
186
+ metadata: { provider },
187
+ model,
188
+ modelParameters: parameters as any,
189
+ name: `Chat Completion (${provider})`,
190
+ startTime: new Date(),
191
+ });
192
+
193
+ return {
194
+ callback: {
195
+ experimental_onToolCall: async () => {
196
+ trace?.update({
197
+ tags: [...(tracePayload?.tags || []), TraceTagMap.ToolsCall],
198
+ });
199
+ },
291
200
 
292
- private static initTogetherAI(payload: JWTPayload) {
293
- const { TOGETHERAI_API_KEY } = getServerConfig();
294
- const apiKey = apiKeyManager.pick(payload?.apiKey || TOGETHERAI_API_KEY);
295
-
296
- return new LobeTogetherAI({ apiKey });
297
- }
201
+ onCompletion: async (completion: string) => {
202
+ generation?.update({
203
+ endTime: new Date(),
204
+ metadata: { provider, tools },
205
+ output: completion,
206
+ });
298
207
 
299
- private static initZeroOne(payload: JWTPayload) {
300
- const { ZEROONE_API_KEY } = getServerConfig();
301
- const apiKey = apiKeyManager.pick(payload?.apiKey || ZEROONE_API_KEY);
208
+ trace?.update({ output: completion });
209
+ },
302
210
 
303
- return new LobeZeroOneAI({ apiKey });
304
- }
305
- }
211
+ onFinal: async () => {
212
+ await traceClient.shutdownAsync();
213
+ },
306
214
 
307
- export default AgentRuntime;
215
+ onStart: () => {
216
+ generation?.update({ completionStartTime: new Date() });
217
+ },
218
+ },
219
+ headers: {
220
+ [LOBE_CHAT_OBSERVATION_ID]: generation?.id,
221
+ [LOBE_CHAT_TRACE_ID]: trace?.id,
222
+ },
223
+ };
224
+ };
@@ -5,7 +5,7 @@ import { createErrorResponse } from '@/app/api/errorResponse';
5
5
  import { ChatCompletionErrorPayload, ModelProvider } from '@/libs/agent-runtime';
6
6
  import { ChatErrorType } from '@/types/fetch';
7
7
 
8
- import AgentRuntime from '../../agentRuntime';
8
+ import { initAgentRuntimeWithUserPayload } from '../../agentRuntime';
9
9
  import { checkAuth } from '../../auth';
10
10
 
11
11
  export const runtime = 'edge';
@@ -21,7 +21,7 @@ export const GET = checkAuth(async (req, { params, jwtPayload }) => {
21
21
  try {
22
22
  const hasDefaultApiKey = jwtPayload.apiKey || 'dont-need-api-key-for-model-list';
23
23
 
24
- const agentRuntime = await AgentRuntime.initializeWithUserPayload(provider, {
24
+ const agentRuntime = await initAgentRuntimeWithUserPayload(provider, {
25
25
  ...jwtPayload,
26
26
  apiKey: noNeedAPIKey(provider) ? hasDefaultApiKey : jwtPayload.apiKey,
27
27
  });
@@ -114,8 +114,8 @@ const TopicContent = memo<TopicContentProps>(({ id, title, fav, showMore }) => {
114
114
  modal.confirm({
115
115
  centered: true,
116
116
  okButtonProps: { danger: true },
117
- onOk: () => {
118
- removeTopic(id);
117
+ onOk: async () => {
118
+ await removeTopic(id);
119
119
  },
120
120
  title: t('topic.confirmRemoveTopic', { ns: 'chat' }),
121
121
  });
@@ -22,11 +22,9 @@ const metadata: Metadata = {
22
22
  },
23
23
  description,
24
24
  icons: {
25
- apple:
26
- 'https://registry.npmmirror.com/@lobehub/assets-favicons/latest/files/assets/apple-touch-icon.png',
27
- icon: 'https://registry.npmmirror.com/@lobehub/assets-favicons/latest/files/assets/favicon-32x32.png',
28
- shortcut:
29
- 'https://registry.npmmirror.com/@lobehub/assets-favicons/latest/files/assets/favicon.ico',
25
+ apple:'icons/apple-touch-icon.png',
26
+ icon:'favicon.ico',
27
+ shortcut:'favicon-32x32.ico',
30
28
  },
31
29
  manifest: noManifest ? undefined : '/manifest.json',
32
30
  metadataBase: new URL(SITE_URL),
@@ -51,10 +51,18 @@ const ProviderConfig = memo<ProviderConfigProps>(
51
51
  const { t } = useTranslation('setting');
52
52
  const { t: modelT } = useTranslation('modelProvider');
53
53
  const [form] = AntForm.useForm();
54
- const [toggleProviderEnabled, setSettings, enabled] = useGlobalStore((s) => [
54
+ const [
55
+ toggleProviderEnabled,
56
+ setSettings,
57
+ enabled,
58
+ isFetchOnClient,
59
+ isProviderEndpointNotEmpty,
60
+ ] = useGlobalStore((s) => [
55
61
  s.toggleProviderEnabled,
56
62
  s.setSettings,
57
63
  modelConfigSelectors.isProviderEnabled(provider)(s),
64
+ modelConfigSelectors.isProviderFetchOnClient(provider)(s),
65
+ modelConfigSelectors.isProviderEndpointNotEmpty(provider)(s),
58
66
  ]);
59
67
 
60
68
  useSyncSettings(form);
@@ -99,6 +107,20 @@ const ProviderConfig = memo<ProviderConfigProps>(
99
107
  label: t('llm.modelList.title'),
100
108
  name: [LLMProviderConfigKey, provider, LLMProviderModelListKey],
101
109
  },
110
+ showEndpoint &&
111
+ isProviderEndpointNotEmpty && {
112
+ children: (
113
+ <Switch
114
+ onChange={(enabled) => {
115
+ setSettings({ [LLMProviderConfigKey]: { [provider]: { fetchOnClient: enabled } } });
116
+ }}
117
+ value={isFetchOnClient}
118
+ />
119
+ ),
120
+ desc: t('llm.fetchOnClient.desc'),
121
+ label: t('llm.fetchOnClient.title'),
122
+ minWidth: undefined,
123
+ },
102
124
  checkerItem ?? {
103
125
  children: <Checker model={checkModel!} provider={provider} />,
104
126
  desc: t('llm.checker.desc'),
@@ -23,7 +23,7 @@ import TogetherAI from './TogetherAI';
23
23
  import ZeroOne from './ZeroOne';
24
24
  import Zhipu from './Zhipu';
25
25
 
26
- export default memo<{ showOllama: boolean }>(({ showOllama }) => {
26
+ export default memo(() => {
27
27
  const { t } = useTranslation('setting');
28
28
 
29
29
  return (
@@ -31,7 +31,7 @@ export default memo<{ showOllama: boolean }>(({ showOllama }) => {
31
31
  <PageTitle title={t('tab.llm')} />
32
32
  <OpenAI />
33
33
  <Azure />
34
- {showOllama && <Ollama />}
34
+ <Ollama />
35
35
  <Google />
36
36
  <Anthropic />
37
37
  <Bedrock />
@@ -1,9 +1,5 @@
1
- import { getServerConfig } from '@/config/server';
2
-
3
1
  import Page from './index';
4
2
 
5
3
  export default () => {
6
- const { ENABLE_OLLAMA } = getServerConfig();
7
-
8
- return <Page showOllama={ENABLE_OLLAMA} />;
4
+ return <Page />;
9
5
  };
@@ -7,6 +7,7 @@ import { useTranslation } from 'react-i18next';
7
7
 
8
8
  import HotKeys from '@/components/HotKeys';
9
9
  import { PREFIX_KEY, SAVE_TOPIC_KEY } from '@/const/hotkeys';
10
+ import { useActionSWR } from '@/libs/swr';
10
11
  import { useChatStore } from '@/store/chat';
11
12
 
12
13
  const SaveTopic = memo<{ mobile?: boolean }>(({ mobile }) => {
@@ -16,20 +17,23 @@ const SaveTopic = memo<{ mobile?: boolean }>(({ mobile }) => {
16
17
  s.openNewTopicOrSaveTopic,
17
18
  ]);
18
19
 
20
+ const { mutate, isValidating } = useActionSWR('openNewTopicOrSaveTopic', openNewTopicOrSaveTopic);
21
+
19
22
  const icon = hasTopic ? LucideMessageSquarePlus : LucideGalleryVerticalEnd;
20
23
  const Render = mobile ? ActionIcon : Button;
21
24
  const iconRender: any = mobile ? icon : <Icon icon={icon} />;
22
25
  const desc = t(hasTopic ? 'topic.openNewTopic' : 'topic.saveCurrentMessages');
23
26
 
24
27
  const hotkeys = [PREFIX_KEY, SAVE_TOPIC_KEY].join('+');
25
- useHotkeys(hotkeys, openNewTopicOrSaveTopic, {
28
+
29
+ useHotkeys(hotkeys, () => mutate(), {
26
30
  enableOnFormTags: true,
27
31
  preventDefault: true,
28
32
  });
29
33
 
30
34
  return (
31
35
  <Tooltip title={<HotKeys desc={desc} keys={hotkeys} />}>
32
- <Render aria-label={desc} icon={iconRender} onClick={openNewTopicOrSaveTopic} />
36
+ <Render aria-label={desc} icon={iconRender} loading={isValidating} onClick={() => mutate()} />
33
37
  </Tooltip>
34
38
  );
35
39
  });