@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.
Files changed (38) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/changelog/v1.json +24 -0
  3. package/locales/ar/setting.json +1 -1
  4. package/locales/bg-BG/setting.json +1 -1
  5. package/locales/de-DE/setting.json +1 -1
  6. package/locales/en-US/setting.json +1 -1
  7. package/locales/es-ES/setting.json +1 -1
  8. package/locales/fa-IR/setting.json +1 -1
  9. package/locales/fr-FR/setting.json +1 -1
  10. package/locales/it-IT/setting.json +1 -1
  11. package/locales/ja-JP/setting.json +1 -1
  12. package/locales/ko-KR/setting.json +1 -1
  13. package/locales/nl-NL/setting.json +1 -1
  14. package/locales/pl-PL/setting.json +1 -1
  15. package/locales/pt-BR/setting.json +1 -1
  16. package/locales/ru-RU/setting.json +1 -1
  17. package/locales/tr-TR/setting.json +1 -1
  18. package/locales/vi-VN/setting.json +1 -1
  19. package/locales/zh-CN/setting.json +1 -1
  20. package/locales/zh-TW/setting.json +1 -1
  21. package/package.json +1 -1
  22. package/src/components/ModelSelect/index.tsx +6 -3
  23. package/src/config/aiModels/google.ts +25 -0
  24. package/src/config/aiModels/hunyuan.ts +44 -0
  25. package/src/config/aiModels/novita.ts +39 -3
  26. package/src/config/aiModels/openrouter.ts +0 -1
  27. package/src/config/aiModels/qwen.ts +48 -6
  28. package/src/config/aiModels/siliconcloud.ts +0 -106
  29. package/src/features/AgentSetting/AgentModal/index.tsx +3 -2
  30. package/src/features/ChatInput/ActionBar/Search/Controls.tsx +6 -2
  31. package/src/libs/model-runtime/utils/streams/vertex-ai.ts +12 -0
  32. package/src/locales/default/setting.ts +1 -1
  33. package/src/services/chat.ts +17 -9
  34. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +23 -31
  35. package/src/store/user/slices/auth/selectors.test.ts +94 -0
  36. package/src/store/user/slices/auth/selectors.ts +3 -0
  37. package/src/utils/client/parserPlaceholder.test.ts +315 -0
  38. 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: 122_880,
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-03-25',
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: 65_792,
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: 65_792,
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: !config.chatConfig.enableMaxTokens,
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
- {isModelHasBuiltinSearchConfig && <ModelBuiltinSearch />}
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 {
@@ -224,7 +224,7 @@ export default {
224
224
  },
225
225
  inputTemplate: {
226
226
  desc: '用户最新的一条消息会填充到此模板',
227
- placeholder: '预处理模版 {{text}} 将替换为实时输入信息',
227
+ placeholder: '预处理模版 {{input_template}} 将替换为实时输入信息',
228
228
  title: '用户输入预处理',
229
229
  },
230
230
  submit: '更新聊天偏好',
@@ -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
- )(getAiInfraStoreState());
184
+ )(aiInfraStoreState);
181
185
 
182
- const useModelSearch = isModelHasBuiltinSearch && chatConfig.useModelBuiltinSearch;
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 messages ============ //
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
- // ============ 2. preprocess tools ============ //
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
- // ============ 3. process extend params ============ //
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
- )(getAiInfraStoreState());
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
- )(getAiInfraStoreState());
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 { model, provider, chatConfig } = agentSelectors.currentAgentConfig(getAgentStoreState());
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(getAgentStoreState()),
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
- )(getAiInfraStoreState());
362
- const isAgentEnableSearch = agentChatConfigSelectors.isAgentEnableSearch(getAgentStoreState());
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(getAgentStoreState());
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(getAgentStoreState());
474
+ const historyCount = agentChatConfigSelectors.historyCount(agentStoreState);
464
475
 
465
476
  if (
466
- agentChatConfigSelectors.enableHistoryCount(getAgentStoreState()) &&
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. replace inputMessage template
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
- // 4. handle max_tokens
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,