@lobehub/chat 1.86.1 → 1.87.1
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 +59 -0
- package/Dockerfile +1 -1
- package/Dockerfile.database +1 -1
- package/Dockerfile.pglite +1 -1
- package/changelog/v1.json +21 -0
- package/locales/ar/setting.json +43 -31
- package/locales/bg-BG/setting.json +43 -31
- package/locales/de-DE/setting.json +43 -31
- package/locales/en-US/setting.json +43 -31
- package/locales/es-ES/setting.json +43 -31
- package/locales/fa-IR/setting.json +43 -31
- package/locales/fr-FR/setting.json +43 -31
- package/locales/it-IT/setting.json +43 -31
- package/locales/ja-JP/setting.json +43 -31
- package/locales/ko-KR/setting.json +43 -31
- package/locales/nl-NL/setting.json +43 -31
- package/locales/pl-PL/setting.json +43 -31
- package/locales/pt-BR/setting.json +43 -31
- package/locales/ru-RU/setting.json +43 -31
- package/locales/tr-TR/setting.json +43 -31
- package/locales/vi-VN/setting.json +43 -31
- package/locales/zh-CN/setting.json +43 -31
- package/locales/zh-TW/setting.json +43 -31
- package/package.json +3 -3
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem/WelcomeMessage.tsx +1 -1
- package/src/app/[variants]/(main)/settings/agent/index.tsx +8 -2
- package/src/app/[variants]/(main)/settings/common/features/Appearance/Preview.tsx +298 -0
- package/src/app/[variants]/(main)/settings/common/features/{Theme → Appearance}/ThemeSwatches/ThemeSwatchesNeutral.tsx +6 -11
- package/src/app/[variants]/(main)/settings/common/features/{Theme → Appearance}/ThemeSwatches/ThemeSwatchesPrimary.tsx +6 -10
- package/src/app/[variants]/(main)/settings/common/features/Appearance/index.tsx +67 -0
- package/src/app/[variants]/(main)/settings/common/features/ChatAppearance/ChatPreview.tsx +35 -0
- package/src/app/[variants]/(main)/settings/common/features/ChatAppearance/HighlighterPreview.tsx +55 -0
- package/src/app/[variants]/(main)/settings/common/features/ChatAppearance/MermaidPreview.tsx +51 -0
- package/src/app/[variants]/(main)/settings/common/features/ChatAppearance/index.tsx +128 -0
- package/src/app/[variants]/(main)/settings/common/features/Common.tsx +74 -42
- package/src/app/[variants]/(main)/settings/common/index.tsx +4 -2
- package/src/app/[variants]/(main)/settings/hotkey/features/{HotkeySetting.tsx → Conversation.tsx} +19 -18
- package/src/app/[variants]/(main)/settings/hotkey/features/Essential.tsx +88 -0
- package/src/app/[variants]/(main)/settings/hotkey/page.tsx +8 -2
- package/src/app/[variants]/(main)/settings/llm/components/ProviderModelList/ModelConfigModal/index.tsx +1 -1
- package/src/app/[variants]/(main)/settings/storage/Advanced.tsx +26 -0
- package/src/app/[variants]/(main)/settings/system-agent/features/createForm.tsx +37 -22
- package/src/app/[variants]/(main)/settings/tts/features/OpenAI.tsx +20 -10
- package/src/app/[variants]/(main)/settings/tts/features/STT.tsx +20 -11
- package/src/config/aiModels/internlm.ts +21 -5
- package/src/config/aiModels/spark.ts +16 -14
- package/src/config/modelProviders/spark.ts +4 -0
- package/src/const/settings/common.ts +2 -0
- package/src/features/AgentSetting/AgentTTS/index.tsx +1 -1
- package/src/features/ChatItem/index.tsx +35 -0
- package/src/features/Conversation/components/ChatItem/index.tsx +2 -6
- package/src/features/User/UserPanel/LangButton.tsx +1 -1
- package/src/features/User/UserPanel/ThemeButton.tsx +3 -3
- package/src/libs/model-runtime/{AgentRuntime.test.ts → ModelRuntime.test.ts} +1 -1
- package/src/libs/model-runtime/{AgentRuntime.ts → ModelRuntime.ts} +3 -3
- package/src/libs/model-runtime/index.ts +1 -1
- package/src/libs/model-runtime/internlm/index.ts +15 -3
- package/src/libs/model-runtime/spark/index.test.ts +3 -0
- package/src/libs/model-runtime/spark/index.ts +23 -1
- package/src/libs/model-runtime/utils/streams/spark.test.ts +66 -0
- package/src/libs/model-runtime/utils/streams/spark.ts +31 -2
- package/src/libs/oidc-provider/config.ts +1 -1
- package/src/locales/default/setting.ts +45 -31
- package/src/store/electron/initialState.ts +1 -1
- package/src/store/electron/selectors/__tests__/desktopState.test.ts +55 -0
- package/src/store/global/selectors/systemStatus.ts +2 -0
- package/src/store/user/slices/settings/selectors/general.test.ts +29 -1
- package/src/store/user/slices/settings/selectors/general.ts +4 -0
- package/src/types/user/settings/general.ts +3 -1
- package/src/app/[variants]/(main)/settings/common/features/Theme/index.tsx +0 -146
- /package/src/app/[variants]/(main)/settings/common/features/{Theme → Appearance}/ThemeSwatches/index.ts +0 -0
@@ -27,7 +27,6 @@ const internlmChatModels: AIChatModelCard[] = [
|
|
27
27
|
description:
|
28
28
|
'我们仍在维护的老版本模型,经过多轮迭代有着极其优异且稳定的性能,包含 7B、20B 多种模型参数量可选,支持 1M 的上下文长度以及更强的指令跟随和工具调用能力。默认指向我们最新发布的 InternLM2.5 系列模型,当前指向 internlm2.5-20b-chat。',
|
29
29
|
displayName: 'InternLM2.5',
|
30
|
-
enabled: true,
|
31
30
|
id: 'internlm2.5-latest',
|
32
31
|
pricing: {
|
33
32
|
input: 0,
|
@@ -37,12 +36,29 @@ const internlmChatModels: AIChatModelCard[] = [
|
|
37
36
|
},
|
38
37
|
{
|
39
38
|
abilities: {
|
40
|
-
|
39
|
+
vision: true,
|
41
40
|
},
|
42
41
|
contextWindowTokens: 32_768,
|
43
|
-
description:
|
44
|
-
|
45
|
-
|
42
|
+
description:
|
43
|
+
'我们最新发布多模态大模型,具备更强的图文理解能力、长时序图片理解能力,性能比肩顶尖闭源模型。默认指向我们最新发布的 InternVL 系列模型,当前指向 internvl3-78b。',
|
44
|
+
displayName: 'InternVL3',
|
45
|
+
enabled: true,
|
46
|
+
id: 'internvl3-latest',
|
47
|
+
pricing: {
|
48
|
+
input: 0,
|
49
|
+
output: 0,
|
50
|
+
},
|
51
|
+
type: 'chat',
|
52
|
+
},
|
53
|
+
{
|
54
|
+
abilities: {
|
55
|
+
vision: true,
|
56
|
+
},
|
57
|
+
contextWindowTokens: 32_768,
|
58
|
+
description:
|
59
|
+
'我们仍在维护的 InternVL2.5 版本,具备优异且稳定的性能。默认指向我们最新发布的 InternVL2.5 系列模型,当前指向 internvl2.5-78b。',
|
60
|
+
displayName: 'InternVL2.5',
|
61
|
+
id: 'internvl2.5-latest',
|
46
62
|
pricing: {
|
47
63
|
input: 0,
|
48
64
|
output: 0,
|
@@ -3,8 +3,21 @@ import { AIChatModelCard } from '@/types/aiModel';
|
|
3
3
|
const sparkChatModels: AIChatModelCard[] = [
|
4
4
|
{
|
5
5
|
abilities: {
|
6
|
+
reasoning: true,
|
6
7
|
search: true,
|
7
8
|
},
|
9
|
+
contextWindowTokens: 32_768,
|
10
|
+
description:
|
11
|
+
'Spark X1 模型将进一步升级,在原来数学任务国内领先基础上,推理、文本生成、语言理解等通用任务实现效果对标 OpenAI o1 和 DeepSeek R1。',
|
12
|
+
displayName: 'Spark X1',
|
13
|
+
id: 'x1',
|
14
|
+
maxOutput: 32_768,
|
15
|
+
settings: {
|
16
|
+
searchImpl: 'params',
|
17
|
+
},
|
18
|
+
type: 'chat',
|
19
|
+
},
|
20
|
+
{
|
8
21
|
contextWindowTokens: 8192,
|
9
22
|
description:
|
10
23
|
'Spark Lite 是一款轻量级大语言模型,具备极低的延迟与高效的处理能力,完全免费开放,支持实时在线搜索功能。其快速响应的特性使其在低算力设备上的推理应用和模型微调中表现出色,为用户带来出色的成本效益和智能体验,尤其在知识问答、内容生成及搜索场景下表现不俗。',
|
@@ -12,9 +25,6 @@ const sparkChatModels: AIChatModelCard[] = [
|
|
12
25
|
enabled: true,
|
13
26
|
id: 'lite',
|
14
27
|
maxOutput: 4096,
|
15
|
-
settings: {
|
16
|
-
searchImpl: 'internal',
|
17
|
-
},
|
18
28
|
type: 'chat',
|
19
29
|
},
|
20
30
|
{
|
@@ -29,24 +39,17 @@ const sparkChatModels: AIChatModelCard[] = [
|
|
29
39
|
id: 'generalv3',
|
30
40
|
maxOutput: 8192,
|
31
41
|
settings: {
|
32
|
-
searchImpl: '
|
42
|
+
searchImpl: 'params',
|
33
43
|
},
|
34
44
|
type: 'chat',
|
35
45
|
},
|
36
46
|
{
|
37
|
-
abilities: {
|
38
|
-
search: true,
|
39
|
-
},
|
40
47
|
contextWindowTokens: 131_072,
|
41
48
|
description:
|
42
49
|
'Spark Pro 128K 配置了特大上下文处理能力,能够处理多达128K的上下文信息,特别适合需通篇分析和长期逻辑关联处理的长文内容,可在复杂文本沟通中提供流畅一致的逻辑与多样的引用支持。',
|
43
50
|
displayName: 'Spark Pro 128K',
|
44
|
-
enabled: true,
|
45
51
|
id: 'pro-128k',
|
46
52
|
maxOutput: 4096,
|
47
|
-
settings: {
|
48
|
-
searchImpl: 'internal',
|
49
|
-
},
|
50
53
|
type: 'chat',
|
51
54
|
},
|
52
55
|
{
|
@@ -62,7 +65,7 @@ const sparkChatModels: AIChatModelCard[] = [
|
|
62
65
|
id: 'generalv3.5',
|
63
66
|
maxOutput: 8192,
|
64
67
|
settings: {
|
65
|
-
searchImpl: '
|
68
|
+
searchImpl: 'params',
|
66
69
|
},
|
67
70
|
type: 'chat',
|
68
71
|
},
|
@@ -75,7 +78,6 @@ const sparkChatModels: AIChatModelCard[] = [
|
|
75
78
|
description:
|
76
79
|
'Spark Max 32K 配置了大上下文处理能力,更强的上下文理解和逻辑推理能力,支持32K tokens的文本输入,适用于长文档阅读、私有知识问答等场景',
|
77
80
|
displayName: 'Spark Max 32K',
|
78
|
-
enabled: true,
|
79
81
|
id: 'max-32k',
|
80
82
|
maxOutput: 8192,
|
81
83
|
settings: {
|
@@ -96,7 +98,7 @@ const sparkChatModels: AIChatModelCard[] = [
|
|
96
98
|
id: '4.0Ultra',
|
97
99
|
maxOutput: 8192,
|
98
100
|
settings: {
|
99
|
-
searchImpl: '
|
101
|
+
searchImpl: 'params',
|
100
102
|
},
|
101
103
|
type: 'chat',
|
102
104
|
},
|
@@ -69,7 +69,11 @@ const Spark: ModelProviderCard = {
|
|
69
69
|
modelsUrl: 'https://xinghuo.xfyun.cn/spark',
|
70
70
|
name: 'Spark',
|
71
71
|
settings: {
|
72
|
+
disableBrowserRequest: true,
|
72
73
|
modelEditable: false,
|
74
|
+
proxyUrl: {
|
75
|
+
placeholder: 'https://spark-api-open.xf-yun.com/v1',
|
76
|
+
},
|
73
77
|
sdkType: 'openai',
|
74
78
|
showModelFetcher: false,
|
75
79
|
smoothing: {
|
@@ -0,0 +1,35 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { ChatItemProps, ChatItem as ChatItemRaw } from '@lobehub/ui/chat';
|
4
|
+
import isEqual from 'fast-deep-equal';
|
5
|
+
import { memo } from 'react';
|
6
|
+
|
7
|
+
import { useUserStore } from '@/store/user';
|
8
|
+
import { settingsSelectors } from '@/store/user/selectors';
|
9
|
+
|
10
|
+
const ChatItem = memo<ChatItemProps>(({ markdownProps = {}, ...rest }) => {
|
11
|
+
const { componentProps, ...restMarkdown } = markdownProps;
|
12
|
+
const { general } = useUserStore(settingsSelectors.currentSettings, isEqual);
|
13
|
+
return (
|
14
|
+
<ChatItemRaw
|
15
|
+
fontSize={general.fontSize}
|
16
|
+
markdownProps={{
|
17
|
+
...restMarkdown,
|
18
|
+
componentProps: {
|
19
|
+
...componentProps,
|
20
|
+
highlight: {
|
21
|
+
theme: general.highlighterTheme,
|
22
|
+
...componentProps?.highlight,
|
23
|
+
},
|
24
|
+
mermaid: {
|
25
|
+
theme: general.mermaidTheme,
|
26
|
+
...componentProps?.mermaid,
|
27
|
+
},
|
28
|
+
},
|
29
|
+
}}
|
30
|
+
{...rest}
|
31
|
+
/>
|
32
|
+
);
|
33
|
+
});
|
34
|
+
|
35
|
+
export default ChatItem;
|
@@ -1,19 +1,17 @@
|
|
1
1
|
'use client';
|
2
2
|
|
3
|
-
import { ChatItem } from '@lobehub/ui/chat';
|
4
3
|
import { createStyles } from 'antd-style';
|
5
4
|
import isEqual from 'fast-deep-equal';
|
6
5
|
import { MouseEventHandler, ReactNode, memo, use, useCallback, useMemo } from 'react';
|
7
6
|
import { useTranslation } from 'react-i18next';
|
8
7
|
import { Flexbox } from 'react-layout-kit';
|
9
8
|
|
9
|
+
import ChatItem from '@/features/ChatItem';
|
10
10
|
import { VirtuosoContext } from '@/features/Conversation/components/VirtualizedList/VirtuosoContext';
|
11
11
|
import { useAgentStore } from '@/store/agent';
|
12
12
|
import { agentChatConfigSelectors } from '@/store/agent/selectors';
|
13
13
|
import { useChatStore } from '@/store/chat';
|
14
14
|
import { chatSelectors } from '@/store/chat/selectors';
|
15
|
-
import { useUserStore } from '@/store/user';
|
16
|
-
import { userGeneralSettingsSelectors } from '@/store/user/selectors';
|
17
15
|
import { ChatMessage } from '@/types/message';
|
18
16
|
|
19
17
|
import ErrorMessageExtra, { useErrorContent } from '../../Error';
|
@@ -72,7 +70,6 @@ const Item = memo<ChatListItemProps>(
|
|
72
70
|
|
73
71
|
const type = useAgentStore(agentChatConfigSelectors.displayMode);
|
74
72
|
const item = useChatStore(chatSelectors.getMessageById(id), isEqual);
|
75
|
-
const fontSize = useUserStore(userGeneralSettingsSelectors.fontSize);
|
76
73
|
|
77
74
|
const [
|
78
75
|
isMessageLoading,
|
@@ -178,7 +175,7 @@ const Item = memo<ChatListItemProps>(
|
|
178
175
|
enableCustomFootnotes: item?.role === 'assistant',
|
179
176
|
rehypePlugins: item?.role === 'user' ? undefined : rehypePlugins,
|
180
177
|
remarkPlugins: item?.role === 'user' ? undefined : remarkPlugins,
|
181
|
-
|
178
|
+
showFootnotes:
|
182
179
|
item?.role === 'user'
|
183
180
|
? undefined
|
184
181
|
: item?.search?.citations &&
|
@@ -235,7 +232,6 @@ const Item = memo<ChatListItemProps>(
|
|
235
232
|
editing={editing}
|
236
233
|
error={error}
|
237
234
|
errorMessage={errorMessage}
|
238
|
-
fontSize={fontSize}
|
239
235
|
loading={isProcessing}
|
240
236
|
markdownProps={markdownProps}
|
241
237
|
message={message}
|
@@ -26,7 +26,7 @@ const LangButton = memo<{ placement?: PopoverProps['placement'] }>(({ placement
|
|
26
26
|
() => [
|
27
27
|
{
|
28
28
|
key: 'auto',
|
29
|
-
label: t('
|
29
|
+
label: t('settingCommon.lang.autoMode'),
|
30
30
|
onClick: () => handleLangChange('auto'),
|
31
31
|
},
|
32
32
|
...localeOptions.map((item) => ({
|
@@ -27,19 +27,19 @@ const ThemeButton = memo<{ placement?: PopoverProps['placement'] }>(({ placement
|
|
27
27
|
{
|
28
28
|
icon: <Icon icon={themeIcons.auto} />,
|
29
29
|
key: 'auto',
|
30
|
-
label: t('
|
30
|
+
label: t('settingCommon.themeMode.auto'),
|
31
31
|
onClick: () => switchThemeMode('auto'),
|
32
32
|
},
|
33
33
|
{
|
34
34
|
icon: <Icon icon={themeIcons.light} />,
|
35
35
|
key: 'light',
|
36
|
-
label: t('
|
36
|
+
label: t('settingCommon.themeMode.light'),
|
37
37
|
onClick: () => switchThemeMode('light'),
|
38
38
|
},
|
39
39
|
{
|
40
40
|
icon: <Icon icon={themeIcons.dark} />,
|
41
41
|
key: 'dark',
|
42
|
-
label: t('
|
42
|
+
label: t('settingCommon.themeMode.dark'),
|
43
43
|
onClick: () => switchThemeMode('dark'),
|
44
44
|
},
|
45
45
|
],
|
@@ -10,7 +10,7 @@ import { AgentRuntime, ChatStreamPayload, LobeOpenAI, ModelProvider } from '@/li
|
|
10
10
|
import { providerRuntimeMap } from '@/libs/model-runtime/runtimeMap';
|
11
11
|
import { createTraceOptions } from '@/server/modules/AgentRuntime';
|
12
12
|
|
13
|
-
import { AgentChatOptions } from './
|
13
|
+
import { AgentChatOptions } from './ModelRuntime';
|
14
14
|
|
15
15
|
const specialProviders = [
|
16
16
|
{ id: 'openai', payload: { apiKey: 'user-openai-key', baseURL: 'user-endpoint' } },
|
@@ -24,7 +24,7 @@ export interface AgentChatOptions {
|
|
24
24
|
trace?: TracePayload;
|
25
25
|
}
|
26
26
|
|
27
|
-
class
|
27
|
+
class ModelRuntime {
|
28
28
|
private _runtime: LobeRuntimeAI;
|
29
29
|
|
30
30
|
constructor(runtime: LobeRuntimeAI) {
|
@@ -110,8 +110,8 @@ class AgentRuntime {
|
|
110
110
|
const providerAI = providerRuntimeMap[provider] ?? LobeOpenAI;
|
111
111
|
const runtimeModel: LobeRuntimeAI = new providerAI(params);
|
112
112
|
|
113
|
-
return new
|
113
|
+
return new ModelRuntime(runtimeModel);
|
114
114
|
}
|
115
115
|
}
|
116
116
|
|
117
|
-
export default
|
117
|
+
export default ModelRuntime;
|
@@ -1,4 +1,3 @@
|
|
1
|
-
export { default as AgentRuntime } from './AgentRuntime';
|
2
1
|
export { LobeAnthropicAI } from './anthropic';
|
3
2
|
export { LobeAzureAI } from './azureai';
|
4
3
|
export { LobeAzureOpenAI } from './azureOpenai';
|
@@ -11,6 +10,7 @@ export { LobeGroq } from './groq';
|
|
11
10
|
export * from './helpers';
|
12
11
|
export { LobeMinimaxAI } from './minimax';
|
13
12
|
export { LobeMistralAI } from './mistral';
|
13
|
+
export { default as AgentRuntime } from './ModelRuntime';
|
14
14
|
export { LobeMoonshotAI } from './moonshot';
|
15
15
|
export { LobeOllamaAI } from './ollama';
|
16
16
|
export { LobeOpenAI } from './openai';
|
@@ -23,6 +23,10 @@ export const LobeInternLMAI = LobeOpenAICompatibleFactory({
|
|
23
23
|
models: async ({ client }) => {
|
24
24
|
const { LOBE_DEFAULT_MODEL_LIST } = await import('@/config/aiModels');
|
25
25
|
|
26
|
+
const functionCallKeywords = ['internlm']
|
27
|
+
|
28
|
+
const visionKeywords = ['internvl']
|
29
|
+
|
26
30
|
const modelsPage = (await client.models.list()) as any;
|
27
31
|
const modelList: InternLMModelCard[] = modelsPage.data;
|
28
32
|
|
@@ -36,10 +40,18 @@ export const LobeInternLMAI = LobeOpenAICompatibleFactory({
|
|
36
40
|
contextWindowTokens: knownModel?.contextWindowTokens ?? undefined,
|
37
41
|
displayName: knownModel?.displayName ?? undefined,
|
38
42
|
enabled: knownModel?.enabled || false,
|
39
|
-
functionCall:
|
43
|
+
functionCall:
|
44
|
+
functionCallKeywords.some(keyword => model.id.toLowerCase().includes(keyword)) ||
|
45
|
+
knownModel?.abilities?.functionCall ||
|
46
|
+
false,
|
40
47
|
id: model.id,
|
41
|
-
reasoning:
|
42
|
-
|
48
|
+
reasoning:
|
49
|
+
knownModel?.abilities?.reasoning ||
|
50
|
+
false,
|
51
|
+
vision:
|
52
|
+
visionKeywords.some(keyword => model.id.toLowerCase().includes(keyword)) ||
|
53
|
+
knownModel?.abilities?.vision ||
|
54
|
+
false,
|
43
55
|
};
|
44
56
|
})
|
45
57
|
.filter(Boolean) as ChatModelCard[];
|
@@ -1,10 +1,32 @@
|
|
1
|
-
import { ModelProvider } from '../types';
|
1
|
+
import { ChatStreamPayload, ModelProvider } from '../types';
|
2
2
|
import { LobeOpenAICompatibleFactory } from '../utils/openaiCompatibleFactory';
|
3
3
|
import { SparkAIStream, transformSparkResponseToStream } from '../utils/streams';
|
4
4
|
|
5
5
|
export const LobeSparkAI = LobeOpenAICompatibleFactory({
|
6
6
|
baseURL: 'https://spark-api-open.xf-yun.com/v1',
|
7
7
|
chatCompletion: {
|
8
|
+
handlePayload: (payload: ChatStreamPayload) => {
|
9
|
+
const { enabledSearch, tools, ...rest } = payload;
|
10
|
+
|
11
|
+
const sparkTools = enabledSearch ? [
|
12
|
+
...(tools || []),
|
13
|
+
{
|
14
|
+
type: "web_search",
|
15
|
+
web_search: {
|
16
|
+
enable: true,
|
17
|
+
search_mode: process.env.SPARK_SEARCH_MODE || "normal", // normal or deep
|
18
|
+
/*
|
19
|
+
show_ref_label: true,
|
20
|
+
*/
|
21
|
+
},
|
22
|
+
}
|
23
|
+
] : tools;
|
24
|
+
|
25
|
+
return {
|
26
|
+
...rest,
|
27
|
+
tools: sparkTools,
|
28
|
+
} as any;
|
29
|
+
},
|
8
30
|
handleStream: SparkAIStream,
|
9
31
|
handleTransformResponseToStream: transformSparkResponseToStream,
|
10
32
|
noUserId: true,
|
@@ -6,6 +6,72 @@ import { SparkAIStream, transformSparkResponseToStream } from './spark';
|
|
6
6
|
describe('SparkAIStream', () => {
|
7
7
|
beforeAll(() => {});
|
8
8
|
|
9
|
+
it('should handle reasoning content in stream', async () => {
|
10
|
+
const data = [
|
11
|
+
{
|
12
|
+
id: 'test-id',
|
13
|
+
object: 'chat.completion.chunk',
|
14
|
+
created: 1734395014,
|
15
|
+
model: 'x1',
|
16
|
+
choices: [
|
17
|
+
{
|
18
|
+
delta: {
|
19
|
+
reasoning_content: 'Hello',
|
20
|
+
role: 'assistant',
|
21
|
+
},
|
22
|
+
index: 0,
|
23
|
+
finish_reason: null,
|
24
|
+
},
|
25
|
+
],
|
26
|
+
},
|
27
|
+
{
|
28
|
+
id: 'test-id',
|
29
|
+
object: 'chat.completion.chunk',
|
30
|
+
created: 1734395014,
|
31
|
+
model: 'x1',
|
32
|
+
choices: [
|
33
|
+
{
|
34
|
+
delta: {
|
35
|
+
reasoning_content: ' World',
|
36
|
+
role: 'assistant',
|
37
|
+
},
|
38
|
+
index: 0,
|
39
|
+
finish_reason: null,
|
40
|
+
},
|
41
|
+
],
|
42
|
+
},
|
43
|
+
]
|
44
|
+
|
45
|
+
const mockSparkStream = new ReadableStream({
|
46
|
+
start(controller) {
|
47
|
+
data.forEach((chunk) => {
|
48
|
+
controller.enqueue(chunk);
|
49
|
+
});
|
50
|
+
|
51
|
+
controller.close();
|
52
|
+
},
|
53
|
+
});
|
54
|
+
|
55
|
+
const protocolStream = SparkAIStream(mockSparkStream);
|
56
|
+
|
57
|
+
const decoder = new TextDecoder();
|
58
|
+
const chunks = [];
|
59
|
+
|
60
|
+
// @ts-ignore
|
61
|
+
for await (const chunk of protocolStream) {
|
62
|
+
chunks.push(decoder.decode(chunk, { stream: true }));
|
63
|
+
}
|
64
|
+
|
65
|
+
expect(chunks).toEqual([
|
66
|
+
'id: test-id\n',
|
67
|
+
'event: reasoning\n',
|
68
|
+
'data: "Hello"\n\n',
|
69
|
+
'id: test-id\n',
|
70
|
+
'event: reasoning\n',
|
71
|
+
'data: " World"\n\n',
|
72
|
+
]);
|
73
|
+
});
|
74
|
+
|
9
75
|
it('should transform non-streaming response to stream', async () => {
|
10
76
|
const mockResponse = {
|
11
77
|
id: 'cha000ceba6@dx193d200b580b8f3532',
|
@@ -11,11 +11,15 @@ import {
|
|
11
11
|
generateToolCallId,
|
12
12
|
} from './protocol';
|
13
13
|
|
14
|
+
import { convertUsage } from '../usageConverter';
|
15
|
+
|
14
16
|
export function transformSparkResponseToStream(data: OpenAI.ChatCompletion) {
|
15
17
|
return new ReadableStream({
|
16
18
|
start(controller) {
|
19
|
+
const choices = data?.choices || [];
|
20
|
+
|
17
21
|
const chunk: OpenAI.ChatCompletionChunk = {
|
18
|
-
choices:
|
22
|
+
choices: choices.map((choice: OpenAI.ChatCompletion.Choice) => {
|
19
23
|
const toolCallsArray = choice.message.tool_calls
|
20
24
|
? Array.isArray(choice.message.tool_calls)
|
21
25
|
? choice.message.tool_calls
|
@@ -49,7 +53,7 @@ export function transformSparkResponseToStream(data: OpenAI.ChatCompletion) {
|
|
49
53
|
controller.enqueue(chunk);
|
50
54
|
|
51
55
|
controller.enqueue({
|
52
|
-
choices:
|
56
|
+
choices: choices.map((choice: OpenAI.ChatCompletion.Choice) => ({
|
53
57
|
delta: {
|
54
58
|
content: null,
|
55
59
|
role: choice.message.role,
|
@@ -106,7 +110,27 @@ export const transformSparkStream = (chunk: OpenAI.ChatCompletionChunk): StreamP
|
|
106
110
|
return { data: item.finish_reason, id: chunk.id, type: 'stop' };
|
107
111
|
}
|
108
112
|
|
113
|
+
if (
|
114
|
+
item.delta &&
|
115
|
+
'reasoning_content' in item.delta &&
|
116
|
+
typeof item.delta.reasoning_content === 'string' &&
|
117
|
+
item.delta.reasoning_content !== ''
|
118
|
+
) {
|
119
|
+
return { data: item.delta.reasoning_content, id: chunk.id, type: 'reasoning' };
|
120
|
+
}
|
121
|
+
|
109
122
|
if (typeof item.delta?.content === 'string') {
|
123
|
+
/*
|
124
|
+
处理 v1 endpoint usage,混合在最后一个 content 内容中
|
125
|
+
{"code":0,"message":"Success","sid":"cha000d05ef@dx196553ae415b80a432","id":"cha000d05ef@dx196553ae415b80a432","created":1745186655,"choices":[{"delta":{"role":"assistant","content":"😊"},"index":0}],"usage":{"prompt_tokens":1,"completion_tokens":418,"total_tokens":419}}
|
126
|
+
*/
|
127
|
+
if (chunk.usage) {
|
128
|
+
return [
|
129
|
+
{ data: item.delta.content, id: chunk.id, type: 'text' },
|
130
|
+
{ data: convertUsage(chunk.usage), id: chunk.id, type: 'usage' },
|
131
|
+
] as any;
|
132
|
+
}
|
133
|
+
|
110
134
|
return { data: item.delta.content, id: chunk.id, type: 'text' };
|
111
135
|
}
|
112
136
|
|
@@ -114,6 +138,11 @@ export const transformSparkStream = (chunk: OpenAI.ChatCompletionChunk): StreamP
|
|
114
138
|
return { data: item.delta, id: chunk.id, type: 'data' };
|
115
139
|
}
|
116
140
|
|
141
|
+
// 处理 v2 endpoint usage
|
142
|
+
if (chunk.usage) {
|
143
|
+
return { data: convertUsage(chunk.usage), id: chunk.id, type: 'usage' };
|
144
|
+
}
|
145
|
+
|
117
146
|
return {
|
118
147
|
data: { delta: item.delta, id: chunk.id, index: item.index },
|
119
148
|
id: chunk.id,
|
@@ -25,7 +25,7 @@ export const defaultClients: ClientMetadata[] = [
|
|
25
25
|
'com.lobehub.lobehub-desktop-dev://auth/callback',
|
26
26
|
'com.lobehub.lobehub-desktop-nightly://auth/callback',
|
27
27
|
'com.lobehub.lobehub-desktop-beta://auth/callback',
|
28
|
-
'com.lobehub.lobehub-desktop://auth/
|
28
|
+
'com.lobehub.lobehub-desktop://auth/callback',
|
29
29
|
],
|
30
30
|
|
31
31
|
// 支持授权码获取令牌和刷新令牌
|
@@ -172,6 +172,22 @@ export default {
|
|
172
172
|
},
|
173
173
|
title: '助手信息',
|
174
174
|
},
|
175
|
+
|
176
|
+
settingAppearance: {
|
177
|
+
neutralColor: {
|
178
|
+
desc: '不同色彩倾向的灰阶自定义',
|
179
|
+
title: '中性色',
|
180
|
+
},
|
181
|
+
preview: {
|
182
|
+
title: '调色盘',
|
183
|
+
},
|
184
|
+
primaryColor: {
|
185
|
+
desc: '自定义主题色',
|
186
|
+
title: '主题色',
|
187
|
+
},
|
188
|
+
title: '应用外观',
|
189
|
+
},
|
190
|
+
|
175
191
|
settingChat: {
|
176
192
|
autoCreateTopicThreshold: {
|
177
193
|
desc: '当前消息数超过设定该值后,将自动创建话题',
|
@@ -214,6 +230,35 @@ export default {
|
|
214
230
|
submit: '更新聊天偏好',
|
215
231
|
title: '聊天设置',
|
216
232
|
},
|
233
|
+
settingChatAppearance: {
|
234
|
+
fontSize: {
|
235
|
+
desc: '聊天内容的字体大小',
|
236
|
+
marks: {
|
237
|
+
normal: '标准',
|
238
|
+
},
|
239
|
+
title: '字体大小',
|
240
|
+
},
|
241
|
+
highlighterTheme: {
|
242
|
+
title: '代码高亮主题',
|
243
|
+
},
|
244
|
+
mermaidTheme: {
|
245
|
+
title: 'Mermaid 主题',
|
246
|
+
},
|
247
|
+
title: '聊天外观',
|
248
|
+
},
|
249
|
+
settingCommon: {
|
250
|
+
lang: {
|
251
|
+
autoMode: '跟随系统',
|
252
|
+
title: '语言',
|
253
|
+
},
|
254
|
+
themeMode: {
|
255
|
+
auto: '自动',
|
256
|
+
dark: '深色',
|
257
|
+
light: '浅色',
|
258
|
+
title: '主题',
|
259
|
+
},
|
260
|
+
title: '通用设置',
|
261
|
+
},
|
217
262
|
settingModel: {
|
218
263
|
enableMaxTokens: {
|
219
264
|
title: '开启单次回复限制',
|
@@ -339,37 +384,6 @@ export default {
|
|
339
384
|
title: '语音合成声源',
|
340
385
|
},
|
341
386
|
},
|
342
|
-
settingTheme: {
|
343
|
-
avatar: {
|
344
|
-
title: '头像',
|
345
|
-
},
|
346
|
-
fontSize: {
|
347
|
-
desc: '聊天内容的字体大小',
|
348
|
-
marks: {
|
349
|
-
normal: '标准',
|
350
|
-
},
|
351
|
-
title: '字体大小',
|
352
|
-
},
|
353
|
-
lang: {
|
354
|
-
autoMode: '跟随系统',
|
355
|
-
title: '语言',
|
356
|
-
},
|
357
|
-
neutralColor: {
|
358
|
-
desc: '不同色彩倾向的灰阶自定义',
|
359
|
-
title: '中性色',
|
360
|
-
},
|
361
|
-
primaryColor: {
|
362
|
-
desc: '自定义主题色',
|
363
|
-
title: '主题色',
|
364
|
-
},
|
365
|
-
themeMode: {
|
366
|
-
auto: '自动',
|
367
|
-
dark: '深色',
|
368
|
-
light: '浅色',
|
369
|
-
title: '主题',
|
370
|
-
},
|
371
|
-
title: '主题设置',
|
372
|
-
},
|
373
387
|
storage: {
|
374
388
|
actions: {
|
375
389
|
export: {
|
@@ -5,7 +5,7 @@ export type RemoteServerError = 'CONFIG_ERROR' | 'AUTH_ERROR' | 'DISCONNECT_ERRO
|
|
5
5
|
export interface ElectronState {
|
6
6
|
appState: ElectronAppState;
|
7
7
|
dataSyncConfig: DataSyncConfig;
|
8
|
-
isAppStateInit
|
8
|
+
isAppStateInit?: boolean;
|
9
9
|
isConnectingServer?: boolean;
|
10
10
|
isInitRemoteServerConfig: boolean;
|
11
11
|
isSyncActive?: boolean;
|