@lobehub/chat 1.91.3 → 1.92.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 +69 -0
- package/changelog/v1.json +24 -0
- package/locales/ar/setting.json +1 -1
- package/locales/bg-BG/setting.json +1 -1
- package/locales/de-DE/setting.json +1 -1
- package/locales/en-US/setting.json +1 -1
- package/locales/es-ES/setting.json +1 -1
- package/locales/fa-IR/setting.json +1 -1
- package/locales/fr-FR/setting.json +1 -1
- package/locales/it-IT/setting.json +1 -1
- package/locales/ja-JP/setting.json +1 -1
- package/locales/ko-KR/setting.json +1 -1
- package/locales/nl-NL/setting.json +1 -1
- package/locales/pl-PL/setting.json +1 -1
- package/locales/pt-BR/setting.json +1 -1
- package/locales/ru-RU/setting.json +1 -1
- package/locales/tr-TR/setting.json +1 -1
- package/locales/vi-VN/setting.json +1 -1
- package/locales/zh-CN/setting.json +1 -1
- package/locales/zh-TW/setting.json +1 -1
- package/package.json +1 -1
- package/src/components/ModelSelect/index.tsx +6 -3
- package/src/config/aiModels/google.ts +25 -0
- package/src/config/aiModels/hunyuan.ts +44 -0
- package/src/config/aiModels/novita.ts +39 -3
- package/src/config/aiModels/openrouter.ts +0 -1
- package/src/config/aiModels/qwen.ts +48 -6
- package/src/config/aiModels/siliconcloud.ts +0 -106
- package/src/features/AgentSetting/AgentModal/index.tsx +3 -2
- package/src/features/ChatInput/ActionBar/Search/Controls.tsx +6 -2
- package/src/libs/model-runtime/utils/streams/vertex-ai.ts +12 -0
- package/src/locales/default/setting.ts +1 -1
- package/src/services/chat.ts +17 -9
- package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +23 -31
- package/src/store/user/slices/auth/selectors.test.ts +94 -0
- package/src/store/user/slices/auth/selectors.ts +3 -0
- package/src/utils/client/parserPlaceholder.test.ts +315 -0
- package/src/utils/client/parserPlaceholder.ts +192 -0
@@ -59,7 +59,7 @@ const qwenChatModels: AIChatModelCard[] = [
|
|
59
59
|
contextWindowTokens: 131_072,
|
60
60
|
description:
|
61
61
|
'Qwen3是一款能力大幅提升的新一代通义千问大模型,在推理、通用、Agent和多语言等多个核心能力上均达到业界领先水平,并支持思考模式切换。',
|
62
|
-
displayName: 'Qwen3 30B',
|
62
|
+
displayName: 'Qwen3 30B A3B',
|
63
63
|
enabled: true,
|
64
64
|
id: 'qwen3-30b-a3b',
|
65
65
|
maxOutput: 8192,
|
@@ -543,7 +543,7 @@ const qwenChatModels: AIChatModelCard[] = [
|
|
543
543
|
config: {
|
544
544
|
deploymentName: 'qvq-max-latest',
|
545
545
|
},
|
546
|
-
contextWindowTokens:
|
546
|
+
contextWindowTokens: 131_072,
|
547
547
|
description:
|
548
548
|
'通义千问QVQ视觉推理模型,支持视觉输入及思维链输出,在数学、编程、视觉分析、创作以及通用任务上都表现了更强的能力。',
|
549
549
|
displayName: 'QVQ Max',
|
@@ -555,7 +555,30 @@ const qwenChatModels: AIChatModelCard[] = [
|
|
555
555
|
input: 8,
|
556
556
|
output: 32,
|
557
557
|
},
|
558
|
-
releasedAt: '2025-
|
558
|
+
releasedAt: '2025-05-15',
|
559
|
+
type: 'chat',
|
560
|
+
},
|
561
|
+
{
|
562
|
+
abilities: {
|
563
|
+
reasoning: true,
|
564
|
+
vision: true,
|
565
|
+
},
|
566
|
+
config: {
|
567
|
+
deploymentName: 'qvq-plus-latest',
|
568
|
+
},
|
569
|
+
contextWindowTokens: 131_072,
|
570
|
+
description:
|
571
|
+
'视觉推理模型。支持视觉输入及思维链输出,继qvq-max模型后推出的plus版本,相较于qvq-max模型,qvq-plus系列模型推理速度更快,效果和成本更均衡。',
|
572
|
+
displayName: 'QVQ Plus',
|
573
|
+
id: 'qvq-plus',
|
574
|
+
maxOutput: 8192,
|
575
|
+
organization: 'Qwen',
|
576
|
+
pricing: {
|
577
|
+
currency: 'CNY',
|
578
|
+
input: 2,
|
579
|
+
output: 5,
|
580
|
+
},
|
581
|
+
releasedAt: '2025-05-15',
|
559
582
|
type: 'chat',
|
560
583
|
},
|
561
584
|
{
|
@@ -795,9 +818,28 @@ const qwenChatModels: AIChatModelCard[] = [
|
|
795
818
|
abilities: {
|
796
819
|
reasoning: true,
|
797
820
|
},
|
798
|
-
contextWindowTokens:
|
821
|
+
contextWindowTokens: 65_536,
|
822
|
+
description:
|
823
|
+
'685B 满血版模型,2025年5月28日发布。DeepSeek-R1 在后训练阶段大规模使用了强化学习技术,在仅有极少标注数据的情况下,极大提升了模型推理能力。在数学、代码、自然语言推理等任务上,性能较高,能力较强。',
|
824
|
+
displayName: 'DeepSeek R1 0528',
|
825
|
+
id: 'deepseek-r1-0528',
|
826
|
+
maxOutput: 8192,
|
827
|
+
organization: 'DeepSeek',
|
828
|
+
pricing: {
|
829
|
+
currency: 'CNY',
|
830
|
+
input: 4,
|
831
|
+
output: 16,
|
832
|
+
},
|
833
|
+
releasedAt: '2025-05-28',
|
834
|
+
type: 'chat',
|
835
|
+
},
|
836
|
+
{
|
837
|
+
abilities: {
|
838
|
+
reasoning: true,
|
839
|
+
},
|
840
|
+
contextWindowTokens: 65_536,
|
799
841
|
description:
|
800
|
-
'DeepSeek-R1 在后训练阶段大规模使用了强化学习技术,在仅有极少标注数据的情况下,极大提升了模型推理能力。在数学、代码、自然语言推理等任务上,性能较高,能力较强。',
|
842
|
+
'671B 满血版模型,2025年1月20日发布。DeepSeek-R1 在后训练阶段大规模使用了强化学习技术,在仅有极少标注数据的情况下,极大提升了模型推理能力。在数学、代码、自然语言推理等任务上,性能较高,能力较强。',
|
801
843
|
displayName: 'DeepSeek R1',
|
802
844
|
id: 'deepseek-r1',
|
803
845
|
maxOutput: 8192,
|
@@ -811,7 +853,7 @@ const qwenChatModels: AIChatModelCard[] = [
|
|
811
853
|
type: 'chat',
|
812
854
|
},
|
813
855
|
{
|
814
|
-
contextWindowTokens:
|
856
|
+
contextWindowTokens: 65_536,
|
815
857
|
description:
|
816
858
|
'DeepSeek-V3 为自研 MoE 模型,671B 参数,激活 37B,在 14.8T token 上进行了预训练,在长文本、代码、数学、百科、中文能力上表现优秀。',
|
817
859
|
displayName: 'DeepSeek V3',
|
@@ -215,7 +215,6 @@ const siliconcloudChatModels: AIChatModelCard[] = [
|
|
215
215
|
},
|
216
216
|
{
|
217
217
|
abilities: {
|
218
|
-
functionCall: true,
|
219
218
|
reasoning: true,
|
220
219
|
},
|
221
220
|
contextWindowTokens: 131_072,
|
@@ -381,23 +380,6 @@ const siliconcloudChatModels: AIChatModelCard[] = [
|
|
381
380
|
},
|
382
381
|
type: 'chat',
|
383
382
|
},
|
384
|
-
{
|
385
|
-
abilities: {
|
386
|
-
functionCall: true,
|
387
|
-
reasoning: true,
|
388
|
-
},
|
389
|
-
contextWindowTokens: 131_072,
|
390
|
-
description:
|
391
|
-
'DeepSeek-R1-Distill-Qwen-1.5B 是基于 Qwen2.5-Math-1.5B 通过知识蒸馏得到的模型。该模型使用 DeepSeek-R1 生成的 80 万个精选样本进行微调,在多个基准测试中展现出不错的性能。作为一个轻量级模型,在 MATH-500 上达到了 83.9% 的准确率,在 AIME 2024 上达到了 28.9% 的通过率,在 CodeForces 上获得了 954 的评分,显示出超出其参数规模的推理能力。',
|
392
|
-
displayName: 'DeepSeek-R1-Distill-Qwen-1.5B (Free)',
|
393
|
-
id: 'deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B', // 将于 2025-06-05 下线
|
394
|
-
pricing: {
|
395
|
-
currency: 'CNY',
|
396
|
-
input: 0,
|
397
|
-
output: 0,
|
398
|
-
},
|
399
|
-
type: 'chat',
|
400
|
-
},
|
401
383
|
{
|
402
384
|
abilities: {
|
403
385
|
functionCall: true,
|
@@ -415,23 +397,6 @@ const siliconcloudChatModels: AIChatModelCard[] = [
|
|
415
397
|
},
|
416
398
|
type: 'chat',
|
417
399
|
},
|
418
|
-
{
|
419
|
-
abilities: {
|
420
|
-
functionCall: true,
|
421
|
-
reasoning: true,
|
422
|
-
},
|
423
|
-
contextWindowTokens: 131_072,
|
424
|
-
description:
|
425
|
-
'DeepSeek-R1-Distill-Qwen-1.5B 是基于 Qwen2.5-Math-1.5B 通过知识蒸馏得到的模型。该模型使用 DeepSeek-R1 生成的 80 万个精选样本进行微调,在多个基准测试中展现出不错的性能。作为一个轻量级模型,在 MATH-500 上达到了 83.9% 的准确率,在 AIME 2024 上达到了 28.9% 的通过率,在 CodeForces 上获得了 954 的评分,显示出超出其参数规模的推理能力。',
|
426
|
-
displayName: 'DeepSeek-R1-Distill-Qwen-1.5B (Pro)',
|
427
|
-
id: 'Pro/deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B', // 将于 2025-06-05 下线
|
428
|
-
pricing: {
|
429
|
-
currency: 'CNY',
|
430
|
-
input: 0.14,
|
431
|
-
output: 0.14,
|
432
|
-
},
|
433
|
-
type: 'chat',
|
434
|
-
},
|
435
400
|
{
|
436
401
|
abilities: {
|
437
402
|
functionCall: true,
|
@@ -649,32 +614,6 @@ const siliconcloudChatModels: AIChatModelCard[] = [
|
|
649
614
|
},
|
650
615
|
type: 'chat',
|
651
616
|
},
|
652
|
-
{
|
653
|
-
contextWindowTokens: 32_768,
|
654
|
-
description:
|
655
|
-
'Qwen2-1.5B-Instruct 是 Qwen2 系列中的指令微调大语言模型,参数规模为 1.5B。该模型基于 Transformer 架构,采用了 SwiGLU 激活函数、注意力 QKV 偏置和组查询注意力等技术。它在语言理解、生成、多语言能力、编码、数学和推理等多个基准测试中表现出色,超越了大多数开源模型。与 Qwen1.5-1.8B-Chat 相比,Qwen2-1.5B-Instruct 在 MMLU、HumanEval、GSM8K、C-Eval 和 IFEval 等测试中均显示出显著的性能提升,尽管参数量略少',
|
656
|
-
displayName: 'Qwen2 1.5B Instruct (Free)',
|
657
|
-
id: 'Qwen/Qwen2-1.5B-Instruct', // 将于 2025-06-05 下线
|
658
|
-
pricing: {
|
659
|
-
currency: 'CNY',
|
660
|
-
input: 0,
|
661
|
-
output: 0,
|
662
|
-
},
|
663
|
-
type: 'chat',
|
664
|
-
},
|
665
|
-
{
|
666
|
-
contextWindowTokens: 32_768,
|
667
|
-
description:
|
668
|
-
'Qwen2-1.5B-Instruct 是 Qwen2 系列中的指令微调大语言模型,参数规模为 1.5B。该模型基于 Transformer 架构,采用了 SwiGLU 激活函数、注意力 QKV 偏置和组查询注意力等技术。它在语言理解、生成、多语言能力、编码、数学和推理等多个基准测试中表现出色,超越了大多数开源模型。与 Qwen1.5-1.8B-Chat 相比,Qwen2-1.5B-Instruct 在 MMLU、HumanEval、GSM8K、C-Eval 和 IFEval 等测试中均显示出显著的性能提升,尽管参数量略少',
|
669
|
-
displayName: 'Qwen2 1.5B Instruct (Pro)',
|
670
|
-
id: 'Pro/Qwen/Qwen2-1.5B-Instruct', // 将于 2025-06-05 下线
|
671
|
-
pricing: {
|
672
|
-
currency: 'CNY',
|
673
|
-
input: 0.14,
|
674
|
-
output: 0.14,
|
675
|
-
},
|
676
|
-
type: 'chat',
|
677
|
-
},
|
678
617
|
{
|
679
618
|
contextWindowTokens: 32_768,
|
680
619
|
description:
|
@@ -701,22 +640,6 @@ const siliconcloudChatModels: AIChatModelCard[] = [
|
|
701
640
|
},
|
702
641
|
type: 'chat',
|
703
642
|
},
|
704
|
-
{
|
705
|
-
abilities: {
|
706
|
-
vision: true,
|
707
|
-
},
|
708
|
-
contextWindowTokens: 32_768,
|
709
|
-
description:
|
710
|
-
'Qwen2-VL-7B-Instruct 是 Qwen-VL 模型的最新迭代版本,在视觉理解基准测试中达到了最先进的性能,包括 MathVista、DocVQA、RealWorldQA 和 MTVQA 等。Qwen2-VL 能够用于高质量的基于视频的问答、对话和内容创作,还具备复杂推理和决策能力,可以与移动设备、机器人等集成,基于视觉环境和文本指令进行自动操作。除了英语和中文,Qwen2-VL 现在还支持理解图像中不同语言的文本,包括大多数欧洲语言、日语、韩语、阿拉伯语和越南语等',
|
711
|
-
displayName: 'Qwen2 VL 7B Instruct (Pro)',
|
712
|
-
id: 'Pro/Qwen/Qwen2-VL-7B-Instruct', // 将于 2025-06-05 下线
|
713
|
-
pricing: {
|
714
|
-
currency: 'CNY',
|
715
|
-
input: 0.35,
|
716
|
-
output: 0.35,
|
717
|
-
},
|
718
|
-
type: 'chat',
|
719
|
-
},
|
720
643
|
{
|
721
644
|
abilities: {
|
722
645
|
vision: true,
|
@@ -797,22 +720,6 @@ const siliconcloudChatModels: AIChatModelCard[] = [
|
|
797
720
|
},
|
798
721
|
type: 'chat',
|
799
722
|
},
|
800
|
-
{
|
801
|
-
abilities: {
|
802
|
-
functionCall: true,
|
803
|
-
},
|
804
|
-
contextWindowTokens: 32_768,
|
805
|
-
description:
|
806
|
-
'InternLM2.5-20B-Chat 是一个开源的大规模对话模型,基于 InternLM2 架构开发。该模型拥有 200 亿参数,在数学推理方面表现出色,超越了同量级的 Llama3 和 Gemma2-27B 模型。InternLM2.5-20B-Chat 在工具调用能力方面有显著提升,支持从上百个网页收集信息进行分析推理,并具备更强的指令理解、工具选择和结果反思能力。它适用于构建复杂智能体,可进行多轮工具调用以完成复杂任务',
|
807
|
-
displayName: 'InternLM2.5 20B Chat',
|
808
|
-
id: 'internlm/internlm2_5-20b-chat', // 将于 2025-06-05 下线
|
809
|
-
pricing: {
|
810
|
-
currency: 'CNY',
|
811
|
-
input: 1,
|
812
|
-
output: 1,
|
813
|
-
},
|
814
|
-
type: 'chat',
|
815
|
-
},
|
816
723
|
{
|
817
724
|
abilities: {
|
818
725
|
functionCall: true,
|
@@ -845,19 +752,6 @@ const siliconcloudChatModels: AIChatModelCard[] = [
|
|
845
752
|
},
|
846
753
|
type: 'chat',
|
847
754
|
},
|
848
|
-
{
|
849
|
-
contextWindowTokens: 32_768,
|
850
|
-
description:
|
851
|
-
'ChatGLM3-6B 是 ChatGLM 系列的开源模型,由智谱 AI 开发。该模型保留了前代模型的优秀特性,如对话流畅和部署门槛低,同时引入了新的特性。它采用了更多样的训练数据、更充分的训练步数和更合理的训练策略,在 10B 以下的预训练模型中表现出色。ChatGLM3-6B 支持多轮对话、工具调用、代码执行和 Agent 任务等复杂场景。除对话模型外,还开源了基础模型 ChatGLM-6B-Base 和长文本对话模型 ChatGLM3-6B-32K。该模型对学术研究完全开放,在登记后也允许免费商业使用',
|
852
|
-
displayName: 'ChatGLM3 6B (Free)',
|
853
|
-
id: 'THUDM/chatglm3-6b', // 将于 2025-06-05 下线
|
854
|
-
pricing: {
|
855
|
-
currency: 'CNY',
|
856
|
-
input: 0,
|
857
|
-
output: 0,
|
858
|
-
},
|
859
|
-
type: 'chat',
|
860
|
-
},
|
861
755
|
];
|
862
756
|
|
863
757
|
export const allModels = [...siliconcloudChatModels];
|
@@ -1,7 +1,7 @@
|
|
1
1
|
'use client';
|
2
2
|
|
3
3
|
import { Form, type FormGroupItemType, SliderWithInput } from '@lobehub/ui';
|
4
|
-
import { Switch } from 'antd';
|
4
|
+
import { Form as AntdForm, Switch } from 'antd';
|
5
5
|
import isEqual from 'fast-deep-equal';
|
6
6
|
import { memo } from 'react';
|
7
7
|
import { useTranslation } from 'react-i18next';
|
@@ -15,6 +15,7 @@ import { selectors, useStore } from '../store';
|
|
15
15
|
const AgentModal = memo(() => {
|
16
16
|
const { t } = useTranslation('setting');
|
17
17
|
const [form] = Form.useForm();
|
18
|
+
const enableMaxTokens = AntdForm.useWatch(['chatConfig', 'enableMaxTokens'], form);
|
18
19
|
const config = useStore(selectors.currentAgentConfig, isEqual);
|
19
20
|
|
20
21
|
const updateConfig = useStore((s) => s.setAgentConfig);
|
@@ -69,7 +70,7 @@ const AgentModal = memo(() => {
|
|
69
70
|
children: <SliderWithInput max={32_000} min={0} step={100} unlimitedInput={true} />,
|
70
71
|
desc: t('settingModel.maxTokens.desc'),
|
71
72
|
divider: false,
|
72
|
-
hidden: !
|
73
|
+
hidden: !enableMaxTokens,
|
73
74
|
label: t('settingModel.maxTokens.title'),
|
74
75
|
name: ['params', 'max_tokens'],
|
75
76
|
tag: 'max_tokens',
|
@@ -9,7 +9,7 @@ import { Center, Flexbox } from 'react-layout-kit';
|
|
9
9
|
|
10
10
|
import { useAgentStore } from '@/store/agent';
|
11
11
|
import { agentChatConfigSelectors, agentSelectors } from '@/store/agent/slices/chat';
|
12
|
-
import { aiModelSelectors, useAiInfraStore } from '@/store/aiInfra';
|
12
|
+
import { aiModelSelectors, aiProviderSelectors, useAiInfraStore } from '@/store/aiInfra';
|
13
13
|
import { SearchMode } from '@/types/search';
|
14
14
|
|
15
15
|
import FCSearchModel from './FCSearchModel';
|
@@ -99,6 +99,9 @@ const Controls = memo(() => {
|
|
99
99
|
]);
|
100
100
|
|
101
101
|
const supportFC = useAiInfraStore(aiModelSelectors.isModelSupportToolUse(model, provider));
|
102
|
+
const isProviderHasBuiltinSearchConfig = useAiInfraStore(
|
103
|
+
aiProviderSelectors.isProviderHasBuiltinSearchConfig(provider),
|
104
|
+
);
|
102
105
|
const isModelHasBuiltinSearchConfig = useAiInfraStore(
|
103
106
|
aiModelSelectors.isModelHasBuiltinSearchConfig(model, provider),
|
104
107
|
);
|
@@ -119,6 +122,7 @@ const Controls = memo(() => {
|
|
119
122
|
];
|
120
123
|
|
121
124
|
const showDivider = isModelHasBuiltinSearchConfig || !supportFC;
|
125
|
+
const showModelBuiltinSearch = isModelHasBuiltinSearchConfig || isProviderHasBuiltinSearchConfig;
|
122
126
|
|
123
127
|
return (
|
124
128
|
<Flexbox gap={4}>
|
@@ -126,7 +130,7 @@ const Controls = memo(() => {
|
|
126
130
|
<Item {...option} key={option.value} />
|
127
131
|
))}
|
128
132
|
{showDivider && <Divider style={{ margin: 0 }} />}
|
129
|
-
{
|
133
|
+
{showModelBuiltinSearch && <ModelBuiltinSearch />}
|
130
134
|
{!supportFC && <FCSearchModel />}
|
131
135
|
</Flexbox>
|
132
136
|
);
|
@@ -48,6 +48,18 @@ const transformVertexAIStream = (
|
|
48
48
|
);
|
49
49
|
}
|
50
50
|
|
51
|
+
if (
|
52
|
+
candidate && // 首先检查是否为 reasoning 内容 (thought: true)
|
53
|
+
Array.isArray(candidate.content.parts) &&
|
54
|
+
candidate.content.parts.length > 0
|
55
|
+
) {
|
56
|
+
for (const part of candidate.content.parts) {
|
57
|
+
if (part && part.text && (part as any).thought === true) {
|
58
|
+
return { data: part.text, id: context.id, type: 'reasoning' };
|
59
|
+
}
|
60
|
+
}
|
61
|
+
}
|
62
|
+
|
51
63
|
const candidates = chunk.candidates;
|
52
64
|
if (!candidates)
|
53
65
|
return {
|
package/src/services/chat.ts
CHANGED
@@ -41,6 +41,7 @@ import { createErrorResponse } from '@/utils/errorResponse';
|
|
41
41
|
import { FetchSSEOptions, fetchSSE, getMessageError } from '@/utils/fetch';
|
42
42
|
import { genToolCallingName } from '@/utils/toolCall';
|
43
43
|
import { createTraceHeader, getTraceId } from '@/utils/trace';
|
44
|
+
import { parsePlaceholderVariablesMessages } from '@/utils/client/parserPlaceholder';
|
44
45
|
|
45
46
|
import { createHeaderWithAuth, createPayloadWithKeyVaults } from './_auth';
|
46
47
|
import { API_ENDPOINTS } from './_url';
|
@@ -172,14 +173,18 @@ class ChatService {
|
|
172
173
|
|
173
174
|
// =================== 0. process search =================== //
|
174
175
|
const chatConfig = agentChatConfigSelectors.currentChatConfig(getAgentStoreState());
|
175
|
-
|
176
|
+
const aiInfraStoreState = getAiInfraStoreState();
|
176
177
|
const enabledSearch = chatConfig.searchMode !== 'off';
|
178
|
+
const isProviderHasBuiltinSearch = aiProviderSelectors.isProviderHasBuiltinSearch(
|
179
|
+
payload.provider!,
|
180
|
+
)(aiInfraStoreState);
|
177
181
|
const isModelHasBuiltinSearch = aiModelSelectors.isModelHasBuiltinSearch(
|
178
182
|
payload.model,
|
179
183
|
payload.provider!,
|
180
|
-
)(
|
184
|
+
)(aiInfraStoreState);
|
181
185
|
|
182
|
-
const useModelSearch =
|
186
|
+
const useModelSearch =
|
187
|
+
(isProviderHasBuiltinSearch || isModelHasBuiltinSearch) && chatConfig.useModelBuiltinSearch;
|
183
188
|
|
184
189
|
const useApplicationBuiltinSearchTool = enabledSearch && !useModelSearch;
|
185
190
|
|
@@ -189,11 +194,14 @@ class ChatService {
|
|
189
194
|
pluginIds.push(WebBrowsingManifest.identifier);
|
190
195
|
}
|
191
196
|
|
192
|
-
// ============ 1. preprocess
|
197
|
+
// ============ 1. preprocess placeholder variables ============ //
|
198
|
+
const parsedMessages = parsePlaceholderVariablesMessages(messages);
|
199
|
+
|
200
|
+
// ============ 2. preprocess messages ============ //
|
193
201
|
|
194
202
|
const oaiMessages = this.processMessages(
|
195
203
|
{
|
196
|
-
messages,
|
204
|
+
messages: parsedMessages,
|
197
205
|
model: payload.model,
|
198
206
|
provider: payload.provider!,
|
199
207
|
tools: pluginIds,
|
@@ -201,28 +209,28 @@ class ChatService {
|
|
201
209
|
options,
|
202
210
|
);
|
203
211
|
|
204
|
-
// ============
|
212
|
+
// ============ 3. preprocess tools ============ //
|
205
213
|
|
206
214
|
const tools = this.prepareTools(pluginIds, {
|
207
215
|
model: payload.model,
|
208
216
|
provider: payload.provider!,
|
209
217
|
});
|
210
218
|
|
211
|
-
// ============
|
219
|
+
// ============ 4. process extend params ============ //
|
212
220
|
|
213
221
|
let extendParams: Record<string, any> = {};
|
214
222
|
|
215
223
|
const isModelHasExtendParams = aiModelSelectors.isModelHasExtendParams(
|
216
224
|
payload.model,
|
217
225
|
payload.provider!,
|
218
|
-
)(
|
226
|
+
)(aiInfraStoreState);
|
219
227
|
|
220
228
|
// model
|
221
229
|
if (isModelHasExtendParams) {
|
222
230
|
const modelExtendParams = aiModelSelectors.modelExtendParams(
|
223
231
|
payload.model,
|
224
232
|
payload.provider!,
|
225
|
-
)(
|
233
|
+
)(aiInfraStoreState);
|
226
234
|
// if model has extended params, then we need to check if the model can use reasoning
|
227
235
|
|
228
236
|
if (modelExtendParams!.includes('enableReasoning')) {
|
@@ -1,7 +1,6 @@
|
|
1
1
|
/* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
|
2
2
|
// Disable the auto sort key eslint rule to make the code more logic and readable
|
3
3
|
import { produce } from 'immer';
|
4
|
-
import { template } from 'lodash-es';
|
5
4
|
import { StateCreator } from 'zustand/vanilla';
|
6
5
|
|
7
6
|
import { LOADING_FLAT, MESSAGE_CANCEL_FLAT } from '@/const/message';
|
@@ -13,7 +12,7 @@ import { messageService } from '@/services/message';
|
|
13
12
|
import { useAgentStore } from '@/store/agent';
|
14
13
|
import { agentChatConfigSelectors, agentSelectors } from '@/store/agent/selectors';
|
15
14
|
import { getAgentStoreState } from '@/store/agent/store';
|
16
|
-
import { aiModelSelectors } from '@/store/aiInfra';
|
15
|
+
import { aiModelSelectors, aiProviderSelectors } from '@/store/aiInfra';
|
17
16
|
import { getAiInfraStoreState } from '@/store/aiInfra/store';
|
18
17
|
import { chatHelpers } from '@/store/chat/helpers';
|
19
18
|
import { ChatStore } from '@/store/chat/store';
|
@@ -299,7 +298,8 @@ export const generateAIChat: StateCreator<
|
|
299
298
|
// create a new array to avoid the original messages array change
|
300
299
|
const messages = [...originalMessages];
|
301
300
|
|
302
|
-
const
|
301
|
+
const agentStoreState = getAgentStoreState();
|
302
|
+
const { model, provider, chatConfig } = agentSelectors.currentAgentConfig(agentStoreState);
|
303
303
|
|
304
304
|
let fileChunks: MessageSemanticSearchChunk[] | undefined;
|
305
305
|
let ragQueryId;
|
@@ -323,7 +323,7 @@ export const generateAIChat: StateCreator<
|
|
323
323
|
chunks,
|
324
324
|
userQuery: lastMsg.content,
|
325
325
|
rewriteQuery,
|
326
|
-
knowledge: agentSelectors.currentEnabledKnowledge(
|
326
|
+
knowledge: agentSelectors.currentEnabledKnowledge(agentStoreState),
|
327
327
|
});
|
328
328
|
|
329
329
|
// 3. add the retrieve context messages to the messages history
|
@@ -355,14 +355,25 @@ export const generateAIChat: StateCreator<
|
|
355
355
|
if (!assistantId) return;
|
356
356
|
|
357
357
|
// 3. place a search with the search working model if this model is not support tool use
|
358
|
+
const aiInfraStoreState = getAiInfraStoreState();
|
358
359
|
const isModelSupportToolUse = aiModelSelectors.isModelSupportToolUse(
|
359
360
|
model,
|
360
361
|
provider!,
|
361
|
-
)(
|
362
|
-
const
|
362
|
+
)(aiInfraStoreState);
|
363
|
+
const isProviderHasBuiltinSearch = aiProviderSelectors.isProviderHasBuiltinSearch(provider!)(
|
364
|
+
aiInfraStoreState,
|
365
|
+
);
|
366
|
+
const isModelHasBuiltinSearch = aiModelSelectors.isModelHasBuiltinSearch(
|
367
|
+
model,
|
368
|
+
provider!,
|
369
|
+
)(aiInfraStoreState);
|
370
|
+
const useModelBuiltinSearch = agentChatConfigSelectors.useModelBuiltinSearch(agentStoreState);
|
371
|
+
const useModelSearch =
|
372
|
+
(isProviderHasBuiltinSearch || isModelHasBuiltinSearch) && useModelBuiltinSearch;
|
373
|
+
const isAgentEnableSearch = agentChatConfigSelectors.isAgentEnableSearch(agentStoreState);
|
363
374
|
|
364
|
-
if (isAgentEnableSearch && !isModelSupportToolUse) {
|
365
|
-
const { model, provider } = agentChatConfigSelectors.searchFCModel(
|
375
|
+
if (isAgentEnableSearch && !useModelSearch && !isModelSupportToolUse) {
|
376
|
+
const { model, provider } = agentChatConfigSelectors.searchFCModel(agentStoreState);
|
366
377
|
|
367
378
|
let isToolsCalling = false;
|
368
379
|
let isError = false;
|
@@ -460,10 +471,10 @@ export const generateAIChat: StateCreator<
|
|
460
471
|
}
|
461
472
|
|
462
473
|
// 6. summary history if context messages is larger than historyCount
|
463
|
-
const historyCount = agentChatConfigSelectors.historyCount(
|
474
|
+
const historyCount = agentChatConfigSelectors.historyCount(agentStoreState);
|
464
475
|
|
465
476
|
if (
|
466
|
-
agentChatConfigSelectors.enableHistoryCount(
|
477
|
+
agentChatConfigSelectors.enableHistoryCount(agentStoreState) &&
|
467
478
|
chatConfig.enableCompressHistory &&
|
468
479
|
originalMessages.length > historyCount
|
469
480
|
) {
|
@@ -495,8 +506,6 @@ export const generateAIChat: StateCreator<
|
|
495
506
|
const agentConfig = agentSelectors.currentAgentConfig(getAgentStoreState());
|
496
507
|
const chatConfig = agentChatConfigSelectors.currentChatConfig(getAgentStoreState());
|
497
508
|
|
498
|
-
const compiler = template(chatConfig.inputTemplate, { interpolate: /{{([\S\s]+?)}}/g });
|
499
|
-
|
500
509
|
// ================================== //
|
501
510
|
// messages uniformly preprocess //
|
502
511
|
// ================================== //
|
@@ -511,29 +520,12 @@ export const generateAIChat: StateCreator<
|
|
511
520
|
historyCount,
|
512
521
|
});
|
513
522
|
|
514
|
-
// 2.
|
515
|
-
preprocessMsgs = !chatConfig.inputTemplate
|
516
|
-
? preprocessMsgs
|
517
|
-
: preprocessMsgs.map((m) => {
|
518
|
-
if (m.role === 'user') {
|
519
|
-
try {
|
520
|
-
return { ...m, content: compiler({ text: m.content }) };
|
521
|
-
} catch (error) {
|
522
|
-
console.error(error);
|
523
|
-
|
524
|
-
return m;
|
525
|
-
}
|
526
|
-
}
|
527
|
-
|
528
|
-
return m;
|
529
|
-
});
|
530
|
-
|
531
|
-
// 3. add systemRole
|
523
|
+
// 2. add systemRole
|
532
524
|
if (agentConfig.systemRole) {
|
533
525
|
preprocessMsgs.unshift({ content: agentConfig.systemRole, role: 'system' } as ChatMessage);
|
534
526
|
}
|
535
527
|
|
536
|
-
//
|
528
|
+
// 3. handle max_tokens
|
537
529
|
agentConfig.params.max_tokens = chatConfig.enableMaxTokens
|
538
530
|
? agentConfig.params.max_tokens
|
539
531
|
: undefined;
|
@@ -33,6 +33,100 @@ afterEach(() => {
|
|
33
33
|
});
|
34
34
|
|
35
35
|
describe('userProfileSelectors', () => {
|
36
|
+
describe('displayUserName', () => {
|
37
|
+
it('should return default username when auth is disabled and not desktop', () => {
|
38
|
+
enableAuth = false;
|
39
|
+
isDesktop = false;
|
40
|
+
|
41
|
+
const store: UserStore = {
|
42
|
+
isSignedIn: false,
|
43
|
+
user: null,
|
44
|
+
enableAuth: () => false,
|
45
|
+
} as unknown as UserStore;
|
46
|
+
|
47
|
+
expect(userProfileSelectors.displayUserName(store)).toBe('LobeChat');
|
48
|
+
});
|
49
|
+
|
50
|
+
it('should return user username when auth is disabled and is desktop', () => {
|
51
|
+
enableAuth = false;
|
52
|
+
isDesktop = true;
|
53
|
+
|
54
|
+
const store: UserStore = {
|
55
|
+
isSignedIn: false,
|
56
|
+
user: { username: 'johndoe' },
|
57
|
+
enableAuth: () => false,
|
58
|
+
} as unknown as UserStore;
|
59
|
+
|
60
|
+
expect(userProfileSelectors.displayUserName(store)).toBe('johndoe');
|
61
|
+
});
|
62
|
+
|
63
|
+
it('should return user username when signed in', () => {
|
64
|
+
const store: UserStore = {
|
65
|
+
isSignedIn: true,
|
66
|
+
user: { username: 'johndoe' },
|
67
|
+
enableAuth: () => true,
|
68
|
+
} as UserStore;
|
69
|
+
|
70
|
+
expect(userProfileSelectors.displayUserName(store)).toBe('johndoe');
|
71
|
+
});
|
72
|
+
|
73
|
+
it('should return email when signed in but username is not existed in UserStore', () => {
|
74
|
+
const store: UserStore = {
|
75
|
+
isSignedIn: true,
|
76
|
+
user: { email: 'demo@lobehub.com' },
|
77
|
+
enableAuth: () => true,
|
78
|
+
} as UserStore;
|
79
|
+
|
80
|
+
expect(userProfileSelectors.displayUserName(store)).toBe('demo@lobehub.com');
|
81
|
+
});
|
82
|
+
|
83
|
+
it('should return "anonymous" when not signed in', () => {
|
84
|
+
const store: UserStore = {
|
85
|
+
enableAuth: () => true,
|
86
|
+
isSignedIn: false,
|
87
|
+
user: null,
|
88
|
+
} as unknown as UserStore;
|
89
|
+
|
90
|
+
expect(userProfileSelectors.displayUserName(store)).toBe('anonymous');
|
91
|
+
});
|
92
|
+
});
|
93
|
+
|
94
|
+
describe('email', () => {
|
95
|
+
it('should return user email if exist', () => {
|
96
|
+
const store: UserStore = {
|
97
|
+
user: { email: 'demo@lobehub.com' },
|
98
|
+
} as UserStore;
|
99
|
+
|
100
|
+
expect(userProfileSelectors.email(store)).toBe('demo@lobehub.com');
|
101
|
+
});
|
102
|
+
|
103
|
+
it('should return empty string if not exist', () => {
|
104
|
+
const store: UserStore = {
|
105
|
+
user: { email: undefined },
|
106
|
+
} as UserStore;
|
107
|
+
|
108
|
+
expect(userProfileSelectors.email(store)).toBe('');
|
109
|
+
});
|
110
|
+
});
|
111
|
+
|
112
|
+
describe('fullName', () => {
|
113
|
+
it('should return user fullName if exist', () => {
|
114
|
+
const store: UserStore = {
|
115
|
+
user: { fullName: 'John Doe' },
|
116
|
+
} as UserStore;
|
117
|
+
|
118
|
+
expect(userProfileSelectors.fullName(store)).toBe('John Doe');
|
119
|
+
});
|
120
|
+
|
121
|
+
it('should return empty string if not exist', () => {
|
122
|
+
const store: UserStore = {
|
123
|
+
user: { fullName: undefined },
|
124
|
+
} as UserStore;
|
125
|
+
|
126
|
+
expect(userProfileSelectors.fullName(store)).toBe('');
|
127
|
+
});
|
128
|
+
});
|
129
|
+
|
36
130
|
describe('nickName', () => {
|
37
131
|
it('should return default nickname when auth is disabled and not desktop', () => {
|
38
132
|
enableAuth = false;
|
@@ -36,6 +36,9 @@ const username = (s: UserStore) => {
|
|
36
36
|
};
|
37
37
|
|
38
38
|
export const userProfileSelectors = {
|
39
|
+
displayUserName: (s: UserStore): string => username(s) || s.user?.email || '',
|
40
|
+
email: (s: UserStore): string => s.user?.email || '',
|
41
|
+
fullName: (s: UserStore): string => s.user?.fullName || '',
|
39
42
|
nickName,
|
40
43
|
userAvatar: (s: UserStore): string => s.user?.avatar || '',
|
41
44
|
userId: (s: UserStore) => s.user?.id,
|