@lobehub/lobehub 2.1.2 → 2.1.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/.env.example +4 -6
- package/.env.example.development +0 -3
- package/.github/workflows/release-desktop-stable.yml +1 -1
- package/CHANGELOG.md +59 -0
- package/Dockerfile +6 -4
- package/README.md +2 -3
- package/README.zh-CN.md +2 -3
- package/changelog/v2.json +14 -0
- package/docker-compose/deploy/.env.example +3 -1
- package/docker-compose/deploy/.env.zh-CN.example +4 -1
- package/docker-compose/local/.env.example +0 -1
- package/docker-compose/local/.env.zh-CN.example +0 -1
- package/docker-compose/local/grafana/.env.example +0 -1
- package/docker-compose/local/grafana/.env.zh-CN.example +0 -1
- package/docker-compose/local/logto/docker-compose.yml +0 -1
- package/docker-compose/local/zitadel/.env.example +1 -2
- package/docker-compose/local/zitadel/.env.zh-CN.example +1 -2
- package/docker-compose/production/grafana/.env.example +0 -1
- package/docker-compose/production/grafana/.env.zh-CN.example +0 -1
- package/docker-compose/production/logto/.env.example +0 -2
- package/docker-compose/production/logto/.env.zh-CN.example +0 -2
- package/docker-compose/production/zitadel/.env.example +0 -2
- package/docker-compose/production/zitadel/.env.zh-CN.example +0 -2
- package/docker-compose/setup.sh +16 -2
- package/docs/development/basic/folder-structure.mdx +23 -14
- package/docs/development/basic/folder-structure.zh-CN.mdx +23 -14
- package/docs/development/basic/work-with-server-side-database.mdx +0 -1
- package/docs/development/basic/work-with-server-side-database.zh-CN.mdx +0 -1
- package/docs/development/start.mdx +19 -12
- package/docs/development/start.zh-CN.mdx +19 -12
- package/docs/self-hosting/advanced/s3/cloudflare-r2.mdx +0 -5
- package/docs/self-hosting/advanced/s3/cloudflare-r2.zh-CN.mdx +0 -5
- package/docs/self-hosting/advanced/s3/rustfs.mdx +0 -2
- package/docs/self-hosting/advanced/s3/rustfs.zh-CN.mdx +0 -2
- package/docs/self-hosting/advanced/s3/tencent-cloud.mdx +0 -1
- package/docs/self-hosting/advanced/s3/tencent-cloud.zh-CN.mdx +0 -2
- package/docs/self-hosting/advanced/s3.mdx +0 -9
- package/docs/self-hosting/advanced/s3.zh-CN.mdx +0 -8
- package/docs/self-hosting/auth/providers/password.mdx +112 -0
- package/docs/self-hosting/auth/providers/password.zh-CN.mdx +103 -0
- package/docs/self-hosting/auth.mdx +12 -0
- package/docs/self-hosting/auth.zh-CN.mdx +12 -0
- package/docs/self-hosting/environment-variables/auth.mdx +7 -0
- package/docs/self-hosting/environment-variables/auth.zh-CN.mdx +7 -0
- package/docs/self-hosting/environment-variables/basic.mdx +0 -7
- package/docs/self-hosting/environment-variables/basic.zh-CN.mdx +0 -7
- package/docs/self-hosting/environment-variables/s3.mdx +0 -7
- package/docs/self-hosting/environment-variables/s3.zh-CN.mdx +0 -7
- package/docs/self-hosting/examples/azure-openai.mdx +0 -1
- package/docs/self-hosting/examples/azure-openai.zh-CN.mdx +0 -1
- package/docs/self-hosting/platform/docker-compose.mdx +0 -1
- package/docs/self-hosting/platform/docker-compose.zh-CN.mdx +0 -1
- package/docs/self-hosting/platform/docker.mdx +5 -3
- package/docs/self-hosting/platform/docker.zh-CN.mdx +5 -4
- package/docs/self-hosting/platform/dokploy.mdx +0 -2
- package/docs/self-hosting/platform/dokploy.zh-CN.mdx +0 -2
- package/docs/self-hosting/platform/vercel.mdx +0 -7
- package/docs/self-hosting/platform/vercel.zh-CN.mdx +0 -7
- package/e2e/src/steps/home/sidebarAgent.steps.ts +56 -24
- package/locales/ar/authError.json +1 -0
- package/locales/ar/models.json +25 -22
- package/locales/ar/providers.json +0 -1
- package/locales/ar/setting.json +16 -0
- package/locales/bg-BG/authError.json +1 -0
- package/locales/bg-BG/models.json +18 -21
- package/locales/bg-BG/providers.json +0 -1
- package/locales/bg-BG/setting.json +16 -0
- package/locales/de-DE/authError.json +1 -0
- package/locales/de-DE/models.json +20 -20
- package/locales/de-DE/providers.json +0 -1
- package/locales/de-DE/setting.json +16 -0
- package/locales/en-US/auth.json +1 -0
- package/locales/en-US/models.json +22 -22
- package/locales/en-US/providers.json +0 -1
- package/locales/es-ES/authError.json +1 -0
- package/locales/es-ES/models.json +84 -20
- package/locales/es-ES/providers.json +0 -1
- package/locales/es-ES/setting.json +16 -0
- package/locales/fa-IR/authError.json +1 -0
- package/locales/fa-IR/models.json +43 -20
- package/locales/fa-IR/providers.json +0 -1
- package/locales/fa-IR/setting.json +16 -0
- package/locales/fr-FR/authError.json +1 -0
- package/locales/fr-FR/models.json +19 -21
- package/locales/fr-FR/providers.json +0 -1
- package/locales/fr-FR/setting.json +16 -0
- package/locales/it-IT/authError.json +1 -0
- package/locales/it-IT/models.json +17 -19
- package/locales/it-IT/providers.json +0 -1
- package/locales/it-IT/setting.json +16 -0
- package/locales/ja-JP/authError.json +1 -0
- package/locales/ja-JP/models.json +43 -22
- package/locales/ja-JP/providers.json +0 -1
- package/locales/ja-JP/setting.json +16 -0
- package/locales/ko-KR/authError.json +1 -0
- package/locales/ko-KR/models.json +41 -20
- package/locales/ko-KR/providers.json +0 -1
- package/locales/ko-KR/setting.json +16 -0
- package/locales/nl-NL/authError.json +1 -0
- package/locales/nl-NL/models.json +48 -20
- package/locales/nl-NL/providers.json +0 -1
- package/locales/nl-NL/setting.json +16 -0
- package/locales/pl-PL/authError.json +1 -0
- package/locales/pl-PL/models.json +19 -22
- package/locales/pl-PL/providers.json +0 -1
- package/locales/pl-PL/setting.json +16 -0
- package/locales/pt-BR/authError.json +1 -0
- package/locales/pt-BR/models.json +21 -21
- package/locales/pt-BR/providers.json +0 -1
- package/locales/pt-BR/setting.json +16 -0
- package/locales/ru-RU/authError.json +1 -0
- package/locales/ru-RU/models.json +23 -20
- package/locales/ru-RU/providers.json +0 -1
- package/locales/ru-RU/setting.json +16 -0
- package/locales/tr-TR/authError.json +1 -0
- package/locales/tr-TR/models.json +37 -20
- package/locales/tr-TR/providers.json +0 -1
- package/locales/tr-TR/setting.json +16 -0
- package/locales/vi-VN/authError.json +1 -0
- package/locales/vi-VN/models.json +15 -19
- package/locales/vi-VN/providers.json +0 -1
- package/locales/vi-VN/setting.json +16 -0
- package/locales/zh-CN/auth.json +1 -0
- package/locales/zh-CN/models.json +20 -20
- package/locales/zh-CN/providers.json +0 -1
- package/locales/zh-TW/authError.json +1 -0
- package/locales/zh-TW/models.json +20 -20
- package/locales/zh-TW/providers.json +0 -1
- package/locales/zh-TW/setting.json +16 -0
- package/netlify.toml +0 -1
- package/package.json +1 -1
- package/packages/model-bank/src/aiModels/google.ts +0 -19
- package/packages/model-bank/src/aiModels/moonshot.ts +56 -5
- package/packages/model-bank/src/aiModels/ollamacloud.ts +14 -0
- package/packages/model-bank/src/aiModels/openrouter.ts +0 -14
- package/packages/model-bank/src/aiModels/qwen.ts +105 -4
- package/packages/model-bank/src/aiModels/siliconcloud.ts +39 -0
- package/packages/model-bank/src/aiModels/wenxin.ts +0 -99
- package/packages/model-runtime/src/core/contextBuilders/openai.test.ts +24 -0
- package/packages/model-runtime/src/core/contextBuilders/openai.ts +22 -5
- package/packages/model-runtime/src/core/openaiCompatibleFactory/index.ts +10 -3
- package/packages/model-runtime/src/core/streams/google/google-ai.test.ts +54 -13
- package/packages/model-runtime/src/core/streams/google/index.ts +1 -4
- package/packages/model-runtime/src/providers/moonshot/index.ts +24 -2
- package/packages/model-runtime/src/providers/qwen/index.ts +16 -15
- package/packages/types/src/serverConfig.ts +1 -0
- package/src/app/[variants]/(auth)/signin/SignInEmailStep.tsx +56 -49
- package/src/app/[variants]/(auth)/signin/page.tsx +2 -0
- package/src/app/[variants]/(auth)/signin/useSignIn.ts +2 -0
- package/src/app/[variants]/(auth)/signup/[[...signup]]/page.tsx +7 -0
- package/src/envs/app.ts +0 -2
- package/src/envs/auth.ts +3 -0
- package/src/libs/better-auth/define-config.ts +1 -1
- package/src/libs/next/proxy/define-config.ts +0 -1
- package/src/locales/default/auth.ts +2 -0
- package/src/server/globalConfig/index.ts +1 -0
- package/src/server/routers/lambda/__tests__/integration/aiAgent/execAgent.integration.test.ts +3 -2
- package/src/store/chat/slices/topic/action.ts +1 -1
- package/src/store/electron/actions/settings.ts +7 -7
- package/src/store/electron/actions/sync.ts +11 -11
- package/src/store/global/actions/general.ts +12 -12
- package/src/store/global/initialState.ts +11 -11
- package/src/store/global/selectors/clientDB.ts +1 -1
- package/src/store/global/selectors/systemStatus.ts +1 -1
- package/src/store/image/slices/generationConfig/action.ts +12 -12
- package/src/store/image/utils/size.ts +11 -11
- package/src/store/library/slices/ragEval/actions/dataset.ts +1 -1
- package/src/store/serverConfig/selectors.ts +1 -0
- package/src/store/session/slices/session/initialState.ts +6 -6
- package/src/store/session/slices/session/reducers.ts +1 -1
- package/src/store/session/slices/sessionGroup/initialState.ts +2 -2
- package/src/store/tool/slices/customPlugin/action.ts +2 -2
- package/src/store/tool/slices/oldStore/action.ts +5 -5
- package/src/store/userMemory/slices/preference/action.ts +6 -6
|
@@ -191,37 +191,6 @@ const wenxinChatModels: AIChatModelCard[] = [
|
|
|
191
191
|
},
|
|
192
192
|
type: 'chat',
|
|
193
193
|
},
|
|
194
|
-
{
|
|
195
|
-
contextWindowTokens: 131_072,
|
|
196
|
-
description:
|
|
197
|
-
'ERNIE Speed 128K is a no-I/O-fee model for long-text understanding and large-scale trials.',
|
|
198
|
-
displayName: 'ERNIE Speed 128K',
|
|
199
|
-
id: 'ernie-speed-128k',
|
|
200
|
-
maxOutput: 4096,
|
|
201
|
-
pricing: {
|
|
202
|
-
currency: 'CNY',
|
|
203
|
-
units: [
|
|
204
|
-
{ name: 'textInput', rate: 0, strategy: 'fixed', unit: 'millionTokens' },
|
|
205
|
-
{ name: 'textOutput', rate: 0, strategy: 'fixed', unit: 'millionTokens' },
|
|
206
|
-
],
|
|
207
|
-
},
|
|
208
|
-
type: 'chat',
|
|
209
|
-
},
|
|
210
|
-
{
|
|
211
|
-
contextWindowTokens: 8192,
|
|
212
|
-
description: 'ERNIE Speed 8K is a free, fast model for daily chat and light text tasks.',
|
|
213
|
-
displayName: 'ERNIE Speed 8K',
|
|
214
|
-
id: 'ernie-speed-8k',
|
|
215
|
-
maxOutput: 2048,
|
|
216
|
-
pricing: {
|
|
217
|
-
currency: 'CNY',
|
|
218
|
-
units: [
|
|
219
|
-
{ name: 'textInput', rate: 0, strategy: 'fixed', unit: 'millionTokens' },
|
|
220
|
-
{ name: 'textOutput', rate: 0, strategy: 'fixed', unit: 'millionTokens' },
|
|
221
|
-
],
|
|
222
|
-
},
|
|
223
|
-
type: 'chat',
|
|
224
|
-
},
|
|
225
194
|
{
|
|
226
195
|
contextWindowTokens: 131_072,
|
|
227
196
|
description:
|
|
@@ -238,22 +207,6 @@ const wenxinChatModels: AIChatModelCard[] = [
|
|
|
238
207
|
},
|
|
239
208
|
type: 'chat',
|
|
240
209
|
},
|
|
241
|
-
{
|
|
242
|
-
contextWindowTokens: 8192,
|
|
243
|
-
description:
|
|
244
|
-
'ERNIE Lite 8K is a lightweight general model for cost-sensitive daily QA and content generation.',
|
|
245
|
-
displayName: 'ERNIE Lite 8K',
|
|
246
|
-
id: 'ernie-lite-8k',
|
|
247
|
-
maxOutput: 2048,
|
|
248
|
-
pricing: {
|
|
249
|
-
currency: 'CNY',
|
|
250
|
-
units: [
|
|
251
|
-
{ name: 'textInput', rate: 0, strategy: 'fixed', unit: 'millionTokens' },
|
|
252
|
-
{ name: 'textOutput', rate: 0, strategy: 'fixed', unit: 'millionTokens' },
|
|
253
|
-
],
|
|
254
|
-
},
|
|
255
|
-
type: 'chat',
|
|
256
|
-
},
|
|
257
210
|
{
|
|
258
211
|
abilities: {
|
|
259
212
|
functionCall: true,
|
|
@@ -273,22 +226,6 @@ const wenxinChatModels: AIChatModelCard[] = [
|
|
|
273
226
|
},
|
|
274
227
|
type: 'chat',
|
|
275
228
|
},
|
|
276
|
-
{
|
|
277
|
-
contextWindowTokens: 8192,
|
|
278
|
-
description:
|
|
279
|
-
'ERNIE Tiny 8K is ultra-lightweight for simple QA, classification, and low-cost inference.',
|
|
280
|
-
displayName: 'ERNIE Tiny 8K',
|
|
281
|
-
id: 'ernie-tiny-8k',
|
|
282
|
-
maxOutput: 2048,
|
|
283
|
-
pricing: {
|
|
284
|
-
currency: 'CNY',
|
|
285
|
-
units: [
|
|
286
|
-
{ name: 'textInput', rate: 0, strategy: 'fixed', unit: 'millionTokens' },
|
|
287
|
-
{ name: 'textOutput', rate: 0, strategy: 'fixed', unit: 'millionTokens' },
|
|
288
|
-
],
|
|
289
|
-
},
|
|
290
|
-
type: 'chat',
|
|
291
|
-
},
|
|
292
229
|
{
|
|
293
230
|
contextWindowTokens: 8192,
|
|
294
231
|
description:
|
|
@@ -486,24 +423,6 @@ const wenxinChatModels: AIChatModelCard[] = [
|
|
|
486
423
|
maxOutput: 2048,
|
|
487
424
|
type: 'chat',
|
|
488
425
|
},
|
|
489
|
-
{
|
|
490
|
-
contextWindowTokens: 32_768,
|
|
491
|
-
description:
|
|
492
|
-
'Qianfan Agent Speed 32K is a high-throughput agent model for large-scale, multi-task agent apps.',
|
|
493
|
-
displayName: 'Qianfan Agent Speed 32K',
|
|
494
|
-
id: 'qianfan-agent-speed-32k',
|
|
495
|
-
maxOutput: 4096,
|
|
496
|
-
type: 'chat',
|
|
497
|
-
},
|
|
498
|
-
{
|
|
499
|
-
contextWindowTokens: 8192,
|
|
500
|
-
description:
|
|
501
|
-
'Qianfan Agent Speed 8K is a high-concurrency agent model for short-to-mid conversations and fast response.',
|
|
502
|
-
displayName: 'Qianfan Agent Speed 8K',
|
|
503
|
-
id: 'qianfan-agent-speed-8k',
|
|
504
|
-
maxOutput: 2048,
|
|
505
|
-
type: 'chat',
|
|
506
|
-
},
|
|
507
426
|
{
|
|
508
427
|
abilities: {
|
|
509
428
|
vision: true,
|
|
@@ -1616,24 +1535,6 @@ const wenxinChatModels: AIChatModelCard[] = [
|
|
|
1616
1535
|
},
|
|
1617
1536
|
type: 'chat',
|
|
1618
1537
|
},
|
|
1619
|
-
{
|
|
1620
|
-
contextWindowTokens: 32_768,
|
|
1621
|
-
description: 'Qwen3 235B A22B is a general large model for complex tasks.',
|
|
1622
|
-
displayName: 'Qwen3 235B A22B',
|
|
1623
|
-
id: 'qwen3-235b-a22b',
|
|
1624
|
-
maxOutput: 8192,
|
|
1625
|
-
pricing: {
|
|
1626
|
-
currency: 'CNY',
|
|
1627
|
-
units: [
|
|
1628
|
-
{ name: 'textInput', rate: 2, strategy: 'fixed', unit: 'millionTokens' },
|
|
1629
|
-
{ name: 'textOutput', rate: 8, strategy: 'fixed', unit: 'millionTokens' },
|
|
1630
|
-
],
|
|
1631
|
-
},
|
|
1632
|
-
settings: {
|
|
1633
|
-
extendParams: ['enableReasoning', 'reasoningBudgetToken'],
|
|
1634
|
-
},
|
|
1635
|
-
type: 'chat',
|
|
1636
|
-
},
|
|
1637
1538
|
{
|
|
1638
1539
|
contextWindowTokens: 32_768,
|
|
1639
1540
|
description: 'Qwen3 30B A3B is a mid-large general model balancing cost and quality.',
|
|
@@ -73,6 +73,30 @@ describe('convertMessageContent', () => {
|
|
|
73
73
|
expect(result).toEqual(content);
|
|
74
74
|
expect(imageUrlToBase64).not.toHaveBeenCalled();
|
|
75
75
|
});
|
|
76
|
+
|
|
77
|
+
it('should convert image URL when forceImageBase64 is true', async () => {
|
|
78
|
+
process.env.LLM_VISION_IMAGE_USE_BASE64 = undefined;
|
|
79
|
+
|
|
80
|
+
const content = {
|
|
81
|
+
type: 'image_url',
|
|
82
|
+
image_url: { url: 'https://example.com/image.jpg' },
|
|
83
|
+
} as OpenAI.ChatCompletionContentPart;
|
|
84
|
+
|
|
85
|
+
vi.mocked(parseDataUri).mockReturnValue({ type: 'url', base64: null, mimeType: null });
|
|
86
|
+
vi.mocked(imageUrlToBase64).mockResolvedValue({
|
|
87
|
+
base64: 'forcedBase64',
|
|
88
|
+
mimeType: 'image/jpeg',
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const result = await convertMessageContent(content, { forceImageBase64: true });
|
|
92
|
+
|
|
93
|
+
expect(result).toEqual({
|
|
94
|
+
type: 'image_url',
|
|
95
|
+
image_url: { url: 'data:image/jpeg;base64,forcedBase64' },
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
expect(imageUrlToBase64).toHaveBeenCalledWith('https://example.com/image.jpg');
|
|
99
|
+
});
|
|
76
100
|
});
|
|
77
101
|
|
|
78
102
|
describe('convertOpenAIMessages', () => {
|
|
@@ -5,13 +5,21 @@ import { disableStreamModels, systemToUserModels } from '../../const/models';
|
|
|
5
5
|
import { ChatStreamPayload, OpenAIChatMessage } from '../../types';
|
|
6
6
|
import { parseDataUri } from '../../utils/uriParser';
|
|
7
7
|
|
|
8
|
+
type ConvertMessageContentOptions = {
|
|
9
|
+
forceImageBase64?: boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
8
12
|
export const convertMessageContent = async (
|
|
9
13
|
content: OpenAI.ChatCompletionContentPart,
|
|
14
|
+
options?: ConvertMessageContentOptions,
|
|
10
15
|
): Promise<OpenAI.ChatCompletionContentPart> => {
|
|
11
16
|
if (content.type === 'image_url') {
|
|
12
17
|
const { type } = parseDataUri(content.image_url.url);
|
|
13
18
|
|
|
14
|
-
|
|
19
|
+
const shouldUseBase64 =
|
|
20
|
+
options?.forceImageBase64 || process.env.LLM_VISION_IMAGE_USE_BASE64 === '1';
|
|
21
|
+
|
|
22
|
+
if (type === 'url' && shouldUseBase64) {
|
|
15
23
|
const { base64, mimeType } = await imageUrlToBase64(content.image_url.url);
|
|
16
24
|
|
|
17
25
|
return {
|
|
@@ -24,7 +32,10 @@ export const convertMessageContent = async (
|
|
|
24
32
|
return content;
|
|
25
33
|
};
|
|
26
34
|
|
|
27
|
-
export const convertOpenAIMessages = async (
|
|
35
|
+
export const convertOpenAIMessages = async (
|
|
36
|
+
messages: OpenAI.ChatCompletionMessageParam[],
|
|
37
|
+
options?: ConvertMessageContentOptions,
|
|
38
|
+
) => {
|
|
28
39
|
return (await Promise.all(
|
|
29
40
|
messages.map(async (message) => {
|
|
30
41
|
const msg = message as any;
|
|
@@ -37,7 +48,7 @@ export const convertOpenAIMessages = async (messages: OpenAI.ChatCompletionMessa
|
|
|
37
48
|
? message.content
|
|
38
49
|
: await Promise.all(
|
|
39
50
|
(message.content || []).map((c) =>
|
|
40
|
-
convertMessageContent(c as OpenAI.ChatCompletionContentPart),
|
|
51
|
+
convertMessageContent(c as OpenAI.ChatCompletionContentPart, options),
|
|
41
52
|
),
|
|
42
53
|
),
|
|
43
54
|
role: msg.role,
|
|
@@ -59,7 +70,10 @@ export const convertOpenAIMessages = async (messages: OpenAI.ChatCompletionMessa
|
|
|
59
70
|
)) as OpenAI.ChatCompletionMessageParam[];
|
|
60
71
|
};
|
|
61
72
|
|
|
62
|
-
export const convertOpenAIResponseInputs = async (
|
|
73
|
+
export const convertOpenAIResponseInputs = async (
|
|
74
|
+
messages: OpenAIChatMessage[],
|
|
75
|
+
options?: ConvertMessageContentOptions,
|
|
76
|
+
) => {
|
|
63
77
|
let input: OpenAI.Responses.ResponseInputItem[] = [];
|
|
64
78
|
await Promise.all(
|
|
65
79
|
messages.map(async (message) => {
|
|
@@ -113,7 +127,10 @@ export const convertOpenAIResponseInputs = async (messages: OpenAIChatMessage[])
|
|
|
113
127
|
return { ...c, type: 'input_text' };
|
|
114
128
|
}
|
|
115
129
|
|
|
116
|
-
const image = await convertMessageContent(
|
|
130
|
+
const image = await convertMessageContent(
|
|
131
|
+
c as OpenAI.ChatCompletionContentPart,
|
|
132
|
+
options,
|
|
133
|
+
);
|
|
117
134
|
return {
|
|
118
135
|
image_url: (image as OpenAI.ChatCompletionContentPartImage).image_url?.url,
|
|
119
136
|
type: 'input_image',
|
|
@@ -73,6 +73,7 @@ export interface OpenAICompatibleFactoryOptions<T extends Record<string, any> =
|
|
|
73
73
|
baseURL?: string;
|
|
74
74
|
chatCompletion?: {
|
|
75
75
|
excludeUsage?: boolean;
|
|
76
|
+
forceImageBase64?: boolean;
|
|
76
77
|
handleError?: (
|
|
77
78
|
error: any,
|
|
78
79
|
options: ConstructorOptions<T>,
|
|
@@ -387,7 +388,9 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
|
|
387
388
|
this.baseURL = targetBaseURL;
|
|
388
389
|
}
|
|
389
390
|
|
|
390
|
-
const messages = await convertOpenAIMessages(postPayload.messages
|
|
391
|
+
const messages = await convertOpenAIMessages(postPayload.messages, {
|
|
392
|
+
forceImageBase64: chatCompletion?.forceImageBase64,
|
|
393
|
+
});
|
|
391
394
|
|
|
392
395
|
let response: Stream<OpenAI.Chat.Completions.ChatCompletionChunk>;
|
|
393
396
|
|
|
@@ -877,7 +880,9 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
|
|
877
880
|
delete res.frequency_penalty;
|
|
878
881
|
delete res.presence_penalty;
|
|
879
882
|
|
|
880
|
-
const input = await convertOpenAIResponseInputs(messages as any
|
|
883
|
+
const input = await convertOpenAIResponseInputs(messages as any, {
|
|
884
|
+
forceImageBase64: chatCompletion?.forceImageBase64,
|
|
885
|
+
});
|
|
881
886
|
|
|
882
887
|
const isStreaming = payload.stream !== false;
|
|
883
888
|
log(
|
|
@@ -1006,7 +1011,9 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
|
|
1006
1011
|
|
|
1007
1012
|
if (shouldUseResponses) {
|
|
1008
1013
|
log('calling responses.create for tool calling');
|
|
1009
|
-
const input = await convertOpenAIResponseInputs(messages as any
|
|
1014
|
+
const input = await convertOpenAIResponseInputs(messages as any, {
|
|
1015
|
+
forceImageBase64: chatCompletion?.forceImageBase64,
|
|
1016
|
+
});
|
|
1010
1017
|
|
|
1011
1018
|
const res = await this.client.responses.create(
|
|
1012
1019
|
{
|
|
@@ -251,16 +251,16 @@ describe('GoogleGenerativeAIStream', () => {
|
|
|
251
251
|
expect(chunks).toEqual(
|
|
252
252
|
[
|
|
253
253
|
'id: chat_1',
|
|
254
|
-
'event:
|
|
255
|
-
'data:
|
|
254
|
+
'event: text',
|
|
255
|
+
'data: "234"\n',
|
|
256
256
|
|
|
257
257
|
'id: chat_1',
|
|
258
258
|
'event: text',
|
|
259
259
|
'data: ""\n',
|
|
260
260
|
|
|
261
261
|
'id: chat_1',
|
|
262
|
-
'event:
|
|
263
|
-
`data:
|
|
262
|
+
'event: text',
|
|
263
|
+
`data: "567890\\n"\n`,
|
|
264
264
|
// stop
|
|
265
265
|
'id: chat_1',
|
|
266
266
|
'event: stop',
|
|
@@ -384,12 +384,12 @@ describe('GoogleGenerativeAIStream', () => {
|
|
|
384
384
|
`data: {"content":"**Finalizing Interpretation**\\n\\n","inReasoning":true,"partType":"text"}\n`,
|
|
385
385
|
|
|
386
386
|
'id: chat_1',
|
|
387
|
-
'event:
|
|
388
|
-
|
|
387
|
+
'event: text',
|
|
388
|
+
'data: "简单来说,"\n',
|
|
389
389
|
|
|
390
390
|
'id: chat_1',
|
|
391
|
-
'event:
|
|
392
|
-
|
|
391
|
+
'event: text',
|
|
392
|
+
'data: "文本内容。"\n',
|
|
393
393
|
// stop
|
|
394
394
|
'id: chat_1',
|
|
395
395
|
'event: stop',
|
|
@@ -471,12 +471,12 @@ describe('GoogleGenerativeAIStream', () => {
|
|
|
471
471
|
expect(chunks).toEqual(
|
|
472
472
|
[
|
|
473
473
|
'id: chat_1',
|
|
474
|
-
'event:
|
|
475
|
-
'data:
|
|
474
|
+
'event: text',
|
|
475
|
+
'data: "234"\n',
|
|
476
476
|
|
|
477
477
|
'id: chat_1',
|
|
478
|
-
'event:
|
|
479
|
-
|
|
478
|
+
'event: text',
|
|
479
|
+
'data: "567890\\n"\n',
|
|
480
480
|
// stop
|
|
481
481
|
'id: chat_1',
|
|
482
482
|
'event: stop',
|
|
@@ -1103,7 +1103,7 @@ describe('GoogleGenerativeAIStream', () => {
|
|
|
1103
1103
|
content: {
|
|
1104
1104
|
parts: [
|
|
1105
1105
|
{
|
|
1106
|
-
text:
|
|
1106
|
+
text: "**Planning the Solution**\n\nI'm solidifying my plan...",
|
|
1107
1107
|
thought: true,
|
|
1108
1108
|
},
|
|
1109
1109
|
],
|
|
@@ -1901,5 +1901,46 @@ describe('GoogleGenerativeAIStream', () => {
|
|
|
1901
1901
|
].map((i) => i + '\n'),
|
|
1902
1902
|
);
|
|
1903
1903
|
});
|
|
1904
|
+
|
|
1905
|
+
it('should NOT use multimodal processing if only thoughtsTokenCount is present in metadata but no thought parts', async () => {
|
|
1906
|
+
vi.spyOn(uuidModule, 'nanoid').mockReturnValueOnce('1');
|
|
1907
|
+
|
|
1908
|
+
const data = [
|
|
1909
|
+
{
|
|
1910
|
+
candidates: [
|
|
1911
|
+
{
|
|
1912
|
+
content: {
|
|
1913
|
+
parts: [{ text: 'Hello world' }],
|
|
1914
|
+
role: 'model',
|
|
1915
|
+
},
|
|
1916
|
+
index: 0,
|
|
1917
|
+
},
|
|
1918
|
+
],
|
|
1919
|
+
usageMetadata: {
|
|
1920
|
+
promptTokenCount: 10,
|
|
1921
|
+
candidatesTokenCount: 2,
|
|
1922
|
+
totalTokenCount: 17,
|
|
1923
|
+
thoughtsTokenCount: 5,
|
|
1924
|
+
},
|
|
1925
|
+
modelVersion: 'gemini-2.5-flash',
|
|
1926
|
+
},
|
|
1927
|
+
];
|
|
1928
|
+
|
|
1929
|
+
const mockGoogleStream = new ReadableStream({
|
|
1930
|
+
start(controller) {
|
|
1931
|
+
data.forEach((item) => {
|
|
1932
|
+
controller.enqueue(item);
|
|
1933
|
+
});
|
|
1934
|
+
controller.close();
|
|
1935
|
+
},
|
|
1936
|
+
});
|
|
1937
|
+
|
|
1938
|
+
const protocolStream = GoogleGenerativeAIStream(mockGoogleStream);
|
|
1939
|
+
const chunks = await decodeStreamChunks(protocolStream);
|
|
1940
|
+
|
|
1941
|
+
// Should use 'text' event, not 'content_part'
|
|
1942
|
+
expect(chunks).toContain('event: text\n');
|
|
1943
|
+
expect(chunks).not.toContain('event: content_part\n');
|
|
1944
|
+
});
|
|
1904
1945
|
});
|
|
1905
1946
|
});
|
|
@@ -120,7 +120,6 @@ const transformGoogleGenerativeAIStream = (
|
|
|
120
120
|
const hasReasoningParts = parts.some((p: any) => p.thought === true);
|
|
121
121
|
const hasImageParts = parts.some((p: any) => p.inlineData);
|
|
122
122
|
const hasThoughtSignature = parts.some((p: any) => p.thoughtSignature);
|
|
123
|
-
const hasThoughtsInMetadata = (usageMetadata as any)?.thoughtsTokenCount > 0;
|
|
124
123
|
|
|
125
124
|
// Check model version to determine if new format should be used
|
|
126
125
|
const modelVersion = (chunk as any).modelVersion || '';
|
|
@@ -144,8 +143,7 @@ const transformGoogleGenerativeAIStream = (
|
|
|
144
143
|
// 1. There are reasoning parts in current chunk (thought: true)
|
|
145
144
|
// 2. There are multiple parts with images (multimodal content)
|
|
146
145
|
// 3. There are thoughtSignature in parts (reasoning metadata attached to content)
|
|
147
|
-
// 4.
|
|
148
|
-
// 5. This is Gemini 3 model with image generation (always use new format for consistency)
|
|
146
|
+
// 4. This is Gemini 3 model with image generation (always use new format for consistency)
|
|
149
147
|
// BUT NOT for:
|
|
150
148
|
// - The legacy single-image scenario
|
|
151
149
|
// - Grounding metadata scenario (uses legacy text + grounding events)
|
|
@@ -153,7 +151,6 @@ const transformGoogleGenerativeAIStream = (
|
|
|
153
151
|
(hasReasoningParts ||
|
|
154
152
|
(hasImageParts && parts.length > 1) ||
|
|
155
153
|
hasThoughtSignature ||
|
|
156
|
-
hasThoughtsInMetadata ||
|
|
157
154
|
isGemini3Model) &&
|
|
158
155
|
!isSingleImageWithFinish &&
|
|
159
156
|
!hasGroundingMetadata;
|
|
@@ -15,8 +15,9 @@ export interface MoonshotModelCard {
|
|
|
15
15
|
export const params = {
|
|
16
16
|
baseURL: 'https://api.moonshot.cn/v1',
|
|
17
17
|
chatCompletion: {
|
|
18
|
+
forceImageBase64: true,
|
|
18
19
|
handlePayload: (payload: ChatStreamPayload) => {
|
|
19
|
-
const { enabledSearch, messages, temperature, tools, ...rest } = payload;
|
|
20
|
+
const { enabledSearch, messages, model, temperature, thinking, tools, ...rest } = payload;
|
|
20
21
|
|
|
21
22
|
const filteredMessages = messages.map((message: any) => {
|
|
22
23
|
let normalizedMessage = message;
|
|
@@ -51,12 +52,33 @@ export const params = {
|
|
|
51
52
|
]
|
|
52
53
|
: tools;
|
|
53
54
|
|
|
54
|
-
|
|
55
|
+
const isK25Model = model === 'kimi-k2.5';
|
|
56
|
+
|
|
57
|
+
if (isK25Model) {
|
|
58
|
+
const thinkingParam =
|
|
59
|
+
thinking?.type === 'disabled' ? { type: 'disabled' } : { type: 'enabled' };
|
|
60
|
+
const isThinkingEnabled = thinkingParam.type === 'enabled';
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
...rest,
|
|
64
|
+
frequency_penalty: 0,
|
|
65
|
+
messages: filteredMessages,
|
|
66
|
+
model,
|
|
67
|
+
presence_penalty: 0,
|
|
68
|
+
temperature: isThinkingEnabled ? 1 : 0.6,
|
|
69
|
+
thinking: thinkingParam,
|
|
70
|
+
tools: moonshotTools,
|
|
71
|
+
top_p: 0.95,
|
|
72
|
+
} as any;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Resolve parameters with normalization for non-K2.5 models
|
|
55
76
|
const resolvedParams = resolveParameters({ temperature }, { normalizeTemperature: true });
|
|
56
77
|
|
|
57
78
|
return {
|
|
58
79
|
...rest,
|
|
59
80
|
messages: filteredMessages,
|
|
81
|
+
model,
|
|
60
82
|
temperature: resolvedParams.temperature,
|
|
61
83
|
tools: moonshotTools,
|
|
62
84
|
} as any;
|
|
@@ -47,24 +47,25 @@ export const LobeQwenAI = createOpenAICompatibleRuntime({
|
|
|
47
47
|
...rest,
|
|
48
48
|
...(model.includes('-thinking')
|
|
49
49
|
? {
|
|
50
|
-
|
|
50
|
+
enable_thinking: true,
|
|
51
|
+
thinking_budget:
|
|
52
|
+
thinking?.budget_tokens === 0 ? 0 : thinking?.budget_tokens || undefined,
|
|
53
|
+
}
|
|
54
|
+
: [
|
|
55
|
+
'qwen3',
|
|
56
|
+
'qwen-turbo',
|
|
57
|
+
'qwen-plus',
|
|
58
|
+
'qwen-flash',
|
|
59
|
+
'deepseek-v3.1',
|
|
60
|
+
'deepseek-v3.2',
|
|
61
|
+
'glm',
|
|
62
|
+
'kimi-k2.5',
|
|
63
|
+
].some((keyword) => model.toLowerCase().includes(keyword))
|
|
64
|
+
? {
|
|
65
|
+
enable_thinking: thinking !== undefined ? thinking.type === 'enabled' : false,
|
|
51
66
|
thinking_budget:
|
|
52
67
|
thinking?.budget_tokens === 0 ? 0 : thinking?.budget_tokens || undefined,
|
|
53
68
|
}
|
|
54
|
-
: [
|
|
55
|
-
'qwen3',
|
|
56
|
-
'qwen-turbo',
|
|
57
|
-
'qwen-plus',
|
|
58
|
-
'qwen-flash',
|
|
59
|
-
'deepseek-v3.1',
|
|
60
|
-
'deepseek-v3.2',
|
|
61
|
-
'glm',
|
|
62
|
-
].some((keyword) => model.toLowerCase().includes(keyword))
|
|
63
|
-
? {
|
|
64
|
-
enable_thinking: thinking !== undefined ? thinking.type === 'enabled' : false,
|
|
65
|
-
thinking_budget:
|
|
66
|
-
thinking?.budget_tokens === 0 ? 0 : thinking?.budget_tokens || undefined,
|
|
67
|
-
}
|
|
68
69
|
: {}),
|
|
69
70
|
frequency_penalty: undefined,
|
|
70
71
|
model,
|
|
@@ -49,6 +49,7 @@ export type ServerLanguageModel = Partial<Record<GlobalLLMProviderKey, ServerMod
|
|
|
49
49
|
export interface GlobalServerConfig {
|
|
50
50
|
aiProvider: ServerLanguageModel;
|
|
51
51
|
defaultAgent?: PartialDeep<UserDefaultAgent>;
|
|
52
|
+
disableEmailPassword?: boolean;
|
|
52
53
|
enableBusinessFeatures?: boolean;
|
|
53
54
|
enableEmailVerification?: boolean;
|
|
54
55
|
enableKlavis?: boolean;
|
|
@@ -24,6 +24,7 @@ export const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
|
24
24
|
export const USERNAME_REGEX = /^\w+$/;
|
|
25
25
|
|
|
26
26
|
export interface SignInEmailStepProps {
|
|
27
|
+
disableEmailPassword?: boolean;
|
|
27
28
|
form: FormInstance<{ email: string }>;
|
|
28
29
|
isSocialOnly: boolean;
|
|
29
30
|
loading: boolean;
|
|
@@ -36,6 +37,7 @@ export interface SignInEmailStepProps {
|
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
export const SignInEmailStep = ({
|
|
40
|
+
disableEmailPassword,
|
|
39
41
|
form,
|
|
40
42
|
isSocialOnly,
|
|
41
43
|
loading,
|
|
@@ -133,58 +135,63 @@ export const SignInEmailStep = ({
|
|
|
133
135
|
{getProviderLabel(provider)}
|
|
134
136
|
</Button>
|
|
135
137
|
))}
|
|
136
|
-
{divider}
|
|
138
|
+
{!disableEmailPassword && divider}
|
|
137
139
|
</Flexbox>
|
|
138
140
|
)}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
{ message: t('betterAuth.errors.emailRequired'), required: true },
|
|
148
|
-
{
|
|
149
|
-
validator: (_, value) => {
|
|
150
|
-
if (!value) return Promise.resolve();
|
|
151
|
-
const trimmedValue = (value as string).trim();
|
|
152
|
-
if (EMAIL_REGEX.test(trimmedValue) || USERNAME_REGEX.test(trimmedValue)) {
|
|
153
|
-
return Promise.resolve();
|
|
154
|
-
}
|
|
155
|
-
return Promise.reject(new Error(t('betterAuth.errors.emailInvalid')));
|
|
156
|
-
},
|
|
157
|
-
},
|
|
158
|
-
]}
|
|
159
|
-
style={{ marginBottom: 0 }}
|
|
141
|
+
{serverConfigInit && disableEmailPassword && oAuthSSOProviders.length === 0 && (
|
|
142
|
+
<Alert description={t('betterAuth.signin.ssoOnlyNoProviders')} showIcon type="warning" />
|
|
143
|
+
)}
|
|
144
|
+
{!disableEmailPassword && (
|
|
145
|
+
<Form
|
|
146
|
+
form={form}
|
|
147
|
+
layout="vertical"
|
|
148
|
+
onFinish={(values) => onCheckUser(values as { email: string })}
|
|
160
149
|
>
|
|
161
|
-
<
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
150
|
+
<Form.Item
|
|
151
|
+
name="email"
|
|
152
|
+
rules={[
|
|
153
|
+
{ message: t('betterAuth.errors.emailRequired'), required: true },
|
|
154
|
+
{
|
|
155
|
+
validator: (_, value) => {
|
|
156
|
+
if (!value) return Promise.resolve();
|
|
157
|
+
const trimmedValue = (value as string).trim();
|
|
158
|
+
if (EMAIL_REGEX.test(trimmedValue) || USERNAME_REGEX.test(trimmedValue)) {
|
|
159
|
+
return Promise.resolve();
|
|
160
|
+
}
|
|
161
|
+
return Promise.reject(new Error(t('betterAuth.errors.emailInvalid')));
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
]}
|
|
165
|
+
style={{ marginBottom: 0 }}
|
|
166
|
+
>
|
|
167
|
+
<Input
|
|
168
|
+
placeholder={t('betterAuth.signin.emailPlaceholder')}
|
|
169
|
+
prefix={
|
|
170
|
+
<Icon
|
|
171
|
+
icon={Mail}
|
|
172
|
+
style={{
|
|
173
|
+
marginInline: 6,
|
|
174
|
+
}}
|
|
175
|
+
/>
|
|
176
|
+
}
|
|
177
|
+
ref={emailInputRef}
|
|
178
|
+
size="large"
|
|
179
|
+
style={{
|
|
180
|
+
padding: 6,
|
|
181
|
+
}}
|
|
182
|
+
suffix={
|
|
183
|
+
<Button
|
|
184
|
+
icon={ChevronRight}
|
|
185
|
+
loading={loading}
|
|
186
|
+
onClick={() => form.submit()}
|
|
187
|
+
title={t('betterAuth.signin.nextStep')}
|
|
188
|
+
variant={'filled'}
|
|
189
|
+
/>
|
|
190
|
+
}
|
|
191
|
+
/>
|
|
192
|
+
</Form.Item>
|
|
193
|
+
</Form>
|
|
194
|
+
)}
|
|
188
195
|
{isSocialOnly && (
|
|
189
196
|
<Alert
|
|
190
197
|
description={
|
|
@@ -10,6 +10,7 @@ import { useSignIn } from './useSignIn';
|
|
|
10
10
|
|
|
11
11
|
const SignInPage = () => {
|
|
12
12
|
const {
|
|
13
|
+
disableEmailPassword,
|
|
13
14
|
email,
|
|
14
15
|
form,
|
|
15
16
|
handleBackToEmail,
|
|
@@ -29,6 +30,7 @@ const SignInPage = () => {
|
|
|
29
30
|
<Suspense fallback={<Loading debugId={'Signin'} />}>
|
|
30
31
|
{step === 'email' ? (
|
|
31
32
|
<SignInEmailStep
|
|
33
|
+
disableEmailPassword={disableEmailPassword}
|
|
32
34
|
form={form as any}
|
|
33
35
|
isSocialOnly={isSocialOnly}
|
|
34
36
|
loading={loading}
|
|
@@ -32,6 +32,7 @@ export const useSignIn = () => {
|
|
|
32
32
|
const router = useRouter();
|
|
33
33
|
const searchParams = useSearchParams();
|
|
34
34
|
const enableMagicLink = useServerConfigStore(serverConfigSelectors.enableMagicLink);
|
|
35
|
+
const disableEmailPassword = useServerConfigStore(serverConfigSelectors.disableEmailPassword);
|
|
35
36
|
const [form] = Form.useForm<SignInFormValues>();
|
|
36
37
|
const [loading, setLoading] = useState(false);
|
|
37
38
|
const [socialLoading, setSocialLoading] = useState<string | null>(null);
|
|
@@ -242,6 +243,7 @@ export const useSignIn = () => {
|
|
|
242
243
|
};
|
|
243
244
|
|
|
244
245
|
return {
|
|
246
|
+
disableEmailPassword,
|
|
245
247
|
email,
|
|
246
248
|
form,
|
|
247
249
|
handleBackToEmail,
|