@lobehub/chat 1.52.12 → 1.52.14

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 (22) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/changelog/v1.json +18 -0
  3. package/docs/self-hosting/platform/btpanel.mdx +4 -0
  4. package/docs/self-hosting/platform/btpanel.zh-CN.mdx +4 -0
  5. package/docs/self-hosting/server-database/docker-compose.mdx +4 -0
  6. package/docs/self-hosting/server-database/docker-compose.zh-CN.mdx +3 -0
  7. package/package.json +1 -1
  8. package/src/app/[variants]/{@modal/chat/(.)settings/modal/features/CategoryContent.tsx → (main)/chat/(workspace)/features/AgentSettings/CategoryContent/index.tsx} +7 -6
  9. package/src/app/[variants]/(main)/chat/(workspace)/features/AgentSettings/index.tsx +114 -0
  10. package/src/app/[variants]/(main)/chat/(workspace)/features/SettingButton.tsx +14 -6
  11. package/src/hooks/useInterceptingRoutes.test.ts +10 -5
  12. package/src/hooks/useInterceptingRoutes.ts +10 -8
  13. package/src/libs/agent-runtime/utils/streams/openai.test.ts +219 -0
  14. package/src/libs/agent-runtime/utils/streams/openai.ts +24 -13
  15. package/src/libs/agent-runtime/utils/streams/qwen.ts +10 -0
  16. package/src/store/agent/slices/chat/initialState.ts +2 -0
  17. package/src/app/[variants]/(main)/chat/settings/modal/page.tsx +0 -23
  18. package/src/app/[variants]/@modal/chat/(.)settings/modal/layout.tsx +0 -61
  19. package/src/app/[variants]/@modal/chat/(.)settings/modal/loading.tsx +0 -5
  20. package/src/app/[variants]/@modal/chat/(.)settings/modal/page.tsx +0 -56
  21. package/src/hooks/useChatSettingsTab.ts +0 -12
  22. /package/src/app/[variants]/{@modal/chat/(.)settings/modal/features → (main)/chat/(workspace)/features/AgentSettings/CategoryContent}/useCategory.tsx +0 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,57 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 1.52.14](https://github.com/lobehub/lobe-chat/compare/v1.52.13...v1.52.14)
6
+
7
+ <sup>Released on **2025-02-10**</sup>
8
+
9
+ #### 💄 Styles
10
+
11
+ - **misc**: Refactor agent settings modal.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Styles
19
+
20
+ - **misc**: Refactor agent settings modal, closes [#5987](https://github.com/lobehub/lobe-chat/issues/5987) ([6482f8a](https://github.com/lobehub/lobe-chat/commit/6482f8a))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
30
+ ### [Version 1.52.13](https://github.com/lobehub/lobe-chat/compare/v1.52.12...v1.52.13)
31
+
32
+ <sup>Released on **2025-02-10**</sup>
33
+
34
+ #### 🐛 Bug Fixes
35
+
36
+ - **misc**: Fix Aliyun deepseek-r1 reasoning parsing with oneapi, Support Aliyun deepseek-r1 reasoning.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### What's fixed
44
+
45
+ - **misc**: Fix Aliyun deepseek-r1 reasoning parsing with oneapi, closes [#5964](https://github.com/lobehub/lobe-chat/issues/5964) ([0d7e665](https://github.com/lobehub/lobe-chat/commit/0d7e665))
46
+ - **misc**: Support Aliyun deepseek-r1 reasoning, closes [#5954](https://github.com/lobehub/lobe-chat/issues/5954) ([cf7a2d6](https://github.com/lobehub/lobe-chat/commit/cf7a2d6))
47
+
48
+ </details>
49
+
50
+ <div align="right">
51
+
52
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
53
+
54
+ </div>
55
+
5
56
  ### [Version 1.52.12](https://github.com/lobehub/lobe-chat/compare/v1.52.11...v1.52.12)
6
57
 
7
58
  <sup>Released on **2025-02-10**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,22 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "improvements": [
5
+ "Refactor agent settings modal."
6
+ ]
7
+ },
8
+ "date": "2025-02-10",
9
+ "version": "1.52.14"
10
+ },
11
+ {
12
+ "children": {
13
+ "fixes": [
14
+ "Fix Aliyun deepseek-r1 reasoning parsing with oneapi, Support Aliyun deepseek-r1 reasoning."
15
+ ]
16
+ },
17
+ "date": "2025-02-10",
18
+ "version": "1.52.13"
19
+ },
2
20
  {
3
21
  "children": {
4
22
  "fixes": [
@@ -36,6 +36,10 @@ To install aaPanel, go to the [aaPanel](https://www.aapanel.com/new/download.htm
36
36
 
37
37
  6. After submission, the panel will automatically initialize the application, which will take about `1-3` minutes. It can be accessed after the initialization is completed.
38
38
 
39
+ <Callout type="warning">
40
+ ⚠️ Do not enable any form of cache in the reverse proxy settings of the panel to avoid affecting the normal operation of the service. Read more at https://github.com/lobehub/lobe-chat/discussions/5986
41
+ </Callout>
42
+
39
43
  ## Visit LobeChat
40
44
 
41
45
  - If you have set a domain name, please directly enter the domain name in the browser address bar, such as `http://demo.lobechat`, to access the `LobeChat` console.
@@ -40,6 +40,10 @@ tags:
40
40
 
41
41
  5. 提交后面板会自动进行应用初始化,大概需要`1-3`分钟,初始化完成后即可访问。
42
42
 
43
+ <Callout type="warning">
44
+ ⚠️ 请不要在面板的反向代理设置中开启任何形式的缓存,以免影响服务的正常运行。详情请见 https://github.com/lobehub/lobe-chat/discussions/5986
45
+ </Callout>
46
+
43
47
  ## 访问 LobeChat
44
48
 
45
49
  - 如果您填写域名,请在浏览器输入您的域名访问,如`http://demo.lobechat`,即可访问 `LobeChat` 页面。
@@ -226,6 +226,10 @@ The script supports the following deployment modes; please choose the appropriat
226
226
  proxy_set_header X-Forwarded-Proto $scheme; # Keep the request protocol
227
227
  }
228
228
  ```
229
+
230
+ ⚠️ If you are using such panel software,
231
+ please do not enable any form of caching in the reverse proxy settings of such panel software to avoid affecting the normal operation of the service.
232
+ Read more at https://github.com/lobehub/lobe-chat/discussions/5986
229
233
  </Callout>
230
234
 
231
235
  ### Complete Remaining Configuration in Interactive Script
@@ -224,6 +224,9 @@ bash <(curl -fsSL https://lobe.li/setup.sh) -l zh_CN
224
224
  proxy_set_header X-Forwarded-Proto $scheme; # 保留请求协议
225
225
  }
226
226
  ```
227
+
228
+ ⚠️ 请不要在此类面板软件的反向代理设置中开启任何形式的缓存,以免影响服务的正常运行。
229
+ 详情请见 https://github.com/lobehub/lobe-chat/discussions/5986
227
230
  </Callout>
228
231
 
229
232
  ### 在交互式脚本中完成剩余配置
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.52.12",
3
+ "version": "1.52.14",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -5,22 +5,23 @@ import { Flexbox } from 'react-layout-kit';
5
5
 
6
6
  import HeaderContent from '@/app/[variants]/(main)/chat/settings/features/HeaderContent';
7
7
  import Menu from '@/components/Menu';
8
- import { useChatSettingsTab } from '@/hooks/useChatSettingsTab';
9
- import { useQueryRoute } from '@/hooks/useQueryRoute';
8
+ import { ChatSettingsTabs } from '@/store/global/initialState';
10
9
 
11
10
  import { useCategory } from './useCategory';
12
11
 
13
- const CategoryContent = memo(() => {
12
+ interface CategoryContentProps {
13
+ setTab: (tab: ChatSettingsTabs) => void;
14
+ tab: string;
15
+ }
16
+ const CategoryContent = memo<CategoryContentProps>(({ setTab, tab }) => {
14
17
  const cateItems = useCategory();
15
- const tab = useChatSettingsTab();
16
- const router = useQueryRoute();
17
18
 
18
19
  return (
19
20
  <>
20
21
  <Menu
21
22
  items={cateItems}
22
23
  onClick={({ key }) => {
23
- router.replace('/chat/settings/modal', { query: { tab: key } });
24
+ setTab(key as ChatSettingsTabs);
24
25
  }}
25
26
  selectable
26
27
  selectedKeys={[tab as any]}
@@ -0,0 +1,114 @@
1
+ 'use client';
2
+
3
+ import { Drawer } from 'antd';
4
+ import { useResponsive, useTheme } from 'antd-style';
5
+ import isEqual from 'fast-deep-equal';
6
+ import { memo, useRef, useState } from 'react';
7
+ import { useTranslation } from 'react-i18next';
8
+ import { Flexbox } from 'react-layout-kit';
9
+
10
+ import Header from '@/app/[variants]/(main)/settings/_layout/Desktop/Header';
11
+ import AgentChat from '@/features/AgentSetting/AgentChat';
12
+ import AgentMeta from '@/features/AgentSetting/AgentMeta';
13
+ import AgentModal from '@/features/AgentSetting/AgentModal';
14
+ import AgentPlugin from '@/features/AgentSetting/AgentPlugin';
15
+ import AgentPrompt from '@/features/AgentSetting/AgentPrompt';
16
+ import AgentTTS from '@/features/AgentSetting/AgentTTS';
17
+ import StoreUpdater from '@/features/AgentSetting/StoreUpdater';
18
+ import { Provider, createStore } from '@/features/AgentSetting/store';
19
+ import Footer from '@/features/Setting/Footer';
20
+ import { useAgentStore } from '@/store/agent';
21
+ import { agentSelectors } from '@/store/agent/slices/chat';
22
+ import { ChatSettingsTabs } from '@/store/global/initialState';
23
+ import { useSessionStore } from '@/store/session';
24
+ import { sessionMetaSelectors } from '@/store/session/selectors';
25
+
26
+ import CategoryContent from './CategoryContent';
27
+
28
+ const AgentSettings = memo(() => {
29
+ const { t } = useTranslation('setting');
30
+ const id = useSessionStore((s) => s.activeId);
31
+ const config = useAgentStore(agentSelectors.currentAgentConfig, isEqual);
32
+ const meta = useSessionStore(sessionMetaSelectors.currentAgentMeta, isEqual);
33
+ const [showAgentSetting, updateAgentConfig] = useAgentStore((s) => [
34
+ s.showAgentSetting,
35
+ s.updateAgentConfig,
36
+ ]);
37
+ const [updateAgentMeta] = useSessionStore((s) => [
38
+ s.updateSessionMeta,
39
+ sessionMetaSelectors.currentAgentTitle(s),
40
+ ]);
41
+
42
+ const [tab, setTab] = useState(ChatSettingsTabs.Meta);
43
+
44
+ const ref = useRef<any>(null);
45
+ const theme = useTheme();
46
+ const { md = true, mobile = false } = useResponsive();
47
+
48
+ const category = <CategoryContent setTab={setTab} tab={tab} />;
49
+ return (
50
+ <Provider createStore={createStore}>
51
+ <StoreUpdater
52
+ config={config}
53
+ id={id}
54
+ meta={meta}
55
+ onConfigChange={updateAgentConfig}
56
+ onMetaChange={updateAgentMeta}
57
+ />
58
+ <Drawer
59
+ height={'100vh'}
60
+ onClose={() => {
61
+ useAgentStore.setState({ showAgentSetting: false });
62
+ }}
63
+ open={showAgentSetting}
64
+ placement={'bottom'}
65
+ styles={{
66
+ body: { padding: 0 },
67
+ content: {
68
+ background: theme.colorBgContainer,
69
+ },
70
+ }}
71
+ title={t('header.session')}
72
+ >
73
+ <Flexbox height={'100%'} horizontal={md} ref={ref} width={'100%'}>
74
+ {md ? (
75
+ <Flexbox padding={16}>{category}</Flexbox>
76
+ ) : (
77
+ <Header
78
+ getContainer={() => ref.current}
79
+ title={t(`agentTab.${tab as ChatSettingsTabs}`)}
80
+ >
81
+ {category}
82
+ </Header>
83
+ )}
84
+ <Flexbox
85
+ align={'center'}
86
+ gap={mobile ? 0 : 64}
87
+ paddingInline={mobile ? 0 : 56}
88
+ style={{
89
+ background: mobile
90
+ ? theme.colorBgContainer
91
+ : theme.isDarkMode
92
+ ? theme.colorFillQuaternary
93
+ : theme.colorBgElevated,
94
+ minHeight: '100%',
95
+ overflowX: 'hidden',
96
+ overflowY: 'auto',
97
+ paddingTop: mobile ? 0 : 16,
98
+ }}
99
+ width={'100%'}
100
+ >
101
+ {tab === ChatSettingsTabs.Meta && <AgentMeta />}
102
+ {tab === ChatSettingsTabs.Prompt && <AgentPrompt modal />}
103
+ {tab === ChatSettingsTabs.Chat && <AgentChat />}
104
+ {tab === ChatSettingsTabs.Modal && <AgentModal />}
105
+ {tab === ChatSettingsTabs.TTS && <AgentTTS />}
106
+ {tab === ChatSettingsTabs.Plugin && <AgentPlugin />} <Footer />
107
+ </Flexbox>
108
+ </Flexbox>
109
+ </Drawer>
110
+ </Provider>
111
+ );
112
+ });
113
+
114
+ export default AgentSettings;
@@ -2,23 +2,31 @@
2
2
 
3
3
  import { ActionIcon } from '@lobehub/ui';
4
4
  import { AlignJustify } from 'lucide-react';
5
+ import dynamic from 'next/dynamic';
5
6
  import { memo } from 'react';
6
7
  import { useTranslation } from 'react-i18next';
7
8
 
8
9
  import { DESKTOP_HEADER_ICON_SIZE, MOBILE_HEADER_ICON_SIZE } from '@/const/layoutTokens';
9
10
  import { useOpenChatSettings } from '@/hooks/useInterceptingRoutes';
10
11
 
12
+ const AgentSettings = dynamic(() => import('./AgentSettings'), {
13
+ ssr: false,
14
+ });
15
+
11
16
  const SettingButton = memo<{ mobile?: boolean }>(({ mobile }) => {
12
17
  const { t } = useTranslation('common');
13
18
  const openChatSettings = useOpenChatSettings();
14
19
 
15
20
  return (
16
- <ActionIcon
17
- icon={AlignJustify}
18
- onClick={() => openChatSettings()}
19
- size={mobile ? MOBILE_HEADER_ICON_SIZE : DESKTOP_HEADER_ICON_SIZE}
20
- title={t('header.session', { ns: 'setting' })}
21
- />
21
+ <>
22
+ <ActionIcon
23
+ icon={AlignJustify}
24
+ onClick={() => openChatSettings()}
25
+ size={mobile ? MOBILE_HEADER_ICON_SIZE : DESKTOP_HEADER_ICON_SIZE}
26
+ title={t('header.session', { ns: 'setting' })}
27
+ />
28
+ <AgentSettings />
29
+ </>
22
30
  );
23
31
  });
24
32
 
@@ -1,11 +1,10 @@
1
- import { renderHook } from '@testing-library/react';
2
- import urlJoin from 'url-join';
1
+ import { act, renderHook } from '@testing-library/react';
3
2
  import { describe, expect, it, vi } from 'vitest';
4
3
 
5
4
  import { INBOX_SESSION_ID } from '@/const/session';
6
5
  import { useIsMobile } from '@/hooks/useIsMobile';
7
- import { useGlobalStore } from '@/store/global';
8
- import { ChatSettingsTabs, SettingsTabs, SidebarTabKey } from '@/store/global/initialState';
6
+ import { useAgentStore } from '@/store/agent';
7
+ import { ChatSettingsTabs } from '@/store/global/initialState';
9
8
  import { useSessionStore } from '@/store/session';
10
9
 
11
10
  import { useOpenChatSettings } from './useInterceptingRoutes';
@@ -50,7 +49,13 @@ describe('useOpenChatSettings', () => {
50
49
  it('should handle desktop route for chat settings with session and tab', () => {
51
50
  vi.mocked(useSessionStore).mockReturnValue('456');
52
51
  vi.mocked(useIsMobile).mockReturnValue(false);
52
+
53
53
  const { result } = renderHook(() => useOpenChatSettings(ChatSettingsTabs.Meta));
54
- expect(result.current()).toBe('/chat/settings/modal?session=456&tab=meta');
54
+
55
+ act(() => {
56
+ result.current();
57
+ });
58
+
59
+ expect(useAgentStore.getState().showAgentSetting).toBeTruthy();
55
60
  });
56
61
  });
@@ -4,23 +4,25 @@ import urlJoin from 'url-join';
4
4
  import { INBOX_SESSION_ID } from '@/const/session';
5
5
  import { useIsMobile } from '@/hooks/useIsMobile';
6
6
  import { useQueryRoute } from '@/hooks/useQueryRoute';
7
+ import { useAgentStore } from '@/store/agent';
7
8
  import { ChatSettingsTabs, SettingsTabs } from '@/store/global/initialState';
8
9
  import { useSessionStore } from '@/store/session';
9
10
 
10
11
  export const useOpenChatSettings = (tab: ChatSettingsTabs = ChatSettingsTabs.Meta) => {
11
12
  const activeId = useSessionStore((s) => s.activeId);
13
+
14
+ const isMobile = useIsMobile();
12
15
  const router = useQueryRoute();
13
- const mobile = useIsMobile();
14
16
 
15
17
  return useMemo(() => {
16
18
  if (activeId === INBOX_SESSION_ID) {
17
19
  return () => router.push(urlJoin('/settings', SettingsTabs.Agent));
18
20
  }
19
- if (mobile) {
20
- return () => router.push('/chat/settings');
21
- } else {
22
- // use Intercepting Routes on Desktop
23
- return () => router.push('/chat/settings/modal', { query: { session: activeId, tab } });
24
- }
25
- }, [mobile, activeId, router, tab]);
21
+
22
+ if (isMobile) return () => router.push('/chat/settings');
23
+
24
+ return () => {
25
+ useAgentStore.setState({ showAgentSetting: true });
26
+ };
27
+ }, [activeId, router, tab, isMobile]);
26
28
  };
@@ -755,6 +755,225 @@ describe('OpenAIStream', () => {
755
755
  );
756
756
  });
757
757
 
758
+ it('should handle reasoning event in aliyun bailian api', async () => {
759
+ const data = [
760
+ {
761
+ id: '1',
762
+ object: 'chat.completion.chunk',
763
+ created: 1737563070,
764
+ model: 'deepseek-reasoner',
765
+ system_fingerprint: 'fp_1c5d8833bc',
766
+ choices: [
767
+ {
768
+ index: 0,
769
+ delta: { role: 'assistant', content: '', reasoning_content: '' },
770
+ logprobs: null,
771
+ finish_reason: null,
772
+ },
773
+ ],
774
+ },
775
+ {
776
+ id: '1',
777
+ object: 'chat.completion.chunk',
778
+ created: 1737563070,
779
+ model: 'deepseek-reasoner',
780
+ system_fingerprint: 'fp_1c5d8833bc',
781
+ choices: [
782
+ {
783
+ index: 0,
784
+ delta: { content: '', reasoning_content: '您好' },
785
+ logprobs: null,
786
+ finish_reason: null,
787
+ },
788
+ ],
789
+ },
790
+ {
791
+ id: '1',
792
+ object: 'chat.completion.chunk',
793
+ created: 1737563070,
794
+ model: 'deepseek-reasoner',
795
+ system_fingerprint: 'fp_1c5d8833bc',
796
+ choices: [
797
+ {
798
+ index: 0,
799
+ delta: { content: '', reasoning_content: '!' },
800
+ logprobs: null,
801
+ finish_reason: null,
802
+ },
803
+ ],
804
+ },
805
+ {
806
+ id: '1',
807
+ object: 'chat.completion.chunk',
808
+ created: 1737563070,
809
+ model: 'deepseek-reasoner',
810
+ system_fingerprint: 'fp_1c5d8833bc',
811
+ choices: [
812
+ {
813
+ index: 0,
814
+ delta: { content: '', reasoning_content: '' },
815
+ logprobs: null,
816
+ finish_reason: null,
817
+ },
818
+ ],
819
+ },
820
+ {
821
+ id: '1',
822
+ object: 'chat.completion.chunk',
823
+ created: 1737563070,
824
+ model: 'deepseek-reasoner',
825
+ system_fingerprint: 'fp_1c5d8833bc',
826
+ choices: [
827
+ {
828
+ index: 0,
829
+ delta: { content: '你好', reasoning_content: '' },
830
+ logprobs: null,
831
+ finish_reason: null,
832
+ },
833
+ ],
834
+ },
835
+ {
836
+ id: '1',
837
+ object: 'chat.completion.chunk',
838
+ created: 1737563070,
839
+ model: 'deepseek-reasoner',
840
+ system_fingerprint: 'fp_1c5d8833bc',
841
+ choices: [
842
+ {
843
+ index: 0,
844
+ delta: { content: '很高兴', reasoning_cont: '' },
845
+ logprobs: null,
846
+ finish_reason: null,
847
+ },
848
+ ],
849
+ },
850
+ {
851
+ id: '1',
852
+ object: 'chat.completion.chunk',
853
+ created: 1737563070,
854
+ model: 'deepseek-reasoner',
855
+ system_fingerprint: 'fp_1c5d8833bc',
856
+ choices: [
857
+ {
858
+ index: 0,
859
+ delta: { content: '为您', reasoning_content: '' },
860
+ logprobs: null,
861
+ finish_reason: null,
862
+ },
863
+ ],
864
+ },
865
+ {
866
+ id: '1',
867
+ object: 'chat.completion.chunk',
868
+ created: 1737563070,
869
+ model: 'deepseek-reasoner',
870
+ system_fingerprint: 'fp_1c5d8833bc',
871
+ choices: [
872
+ {
873
+ index: 0,
874
+ delta: { content: '提供', reasoning_content: '' },
875
+ logprobs: null,
876
+ finish_reason: null,
877
+ },
878
+ ],
879
+ },
880
+ {
881
+ id: '1',
882
+ object: 'chat.completion.chunk',
883
+ created: 1737563070,
884
+ model: 'deepseek-reasoner',
885
+ system_fingerprint: 'fp_1c5d8833bc',
886
+ choices: [
887
+ {
888
+ index: 0,
889
+ delta: { content: '帮助。', reasoning_content: '' },
890
+ logprobs: null,
891
+ finish_reason: null,
892
+ },
893
+ ],
894
+ },
895
+ {
896
+ id: '1',
897
+ object: 'chat.completion.chunk',
898
+ created: 1737563070,
899
+ model: 'deepseek-reasoner',
900
+ system_fingerprint: 'fp_1c5d8833bc',
901
+ choices: [
902
+ {
903
+ index: 0,
904
+ delta: { content: '', reasoning_content: '' },
905
+ logprobs: null,
906
+ finish_reason: 'stop',
907
+ },
908
+ ],
909
+ usage: {
910
+ prompt_tokens: 6,
911
+ completion_tokens: 104,
912
+ total_tokens: 110,
913
+ prompt_tokens_details: { cached_tokens: 0 },
914
+ completion_tokens_details: { reasoning_tokens: 70 },
915
+ prompt_cache_hit_tokens: 0,
916
+ prompt_cache_miss_tokens: 6,
917
+ },
918
+ },
919
+ ];
920
+
921
+ const mockOpenAIStream = new ReadableStream({
922
+ start(controller) {
923
+ data.forEach((chunk) => {
924
+ controller.enqueue(chunk);
925
+ });
926
+
927
+ controller.close();
928
+ },
929
+ });
930
+
931
+ const protocolStream = OpenAIStream(mockOpenAIStream);
932
+
933
+ const decoder = new TextDecoder();
934
+ const chunks = [];
935
+
936
+ // @ts-ignore
937
+ for await (const chunk of protocolStream) {
938
+ chunks.push(decoder.decode(chunk, { stream: true }));
939
+ }
940
+
941
+ expect(chunks).toEqual(
942
+ [
943
+ 'id: 1',
944
+ 'event: reasoning',
945
+ `data: ""\n`,
946
+ 'id: 1',
947
+ 'event: reasoning',
948
+ `data: "您好"\n`,
949
+ 'id: 1',
950
+ 'event: reasoning',
951
+ `data: "!"\n`,
952
+ 'id: 1',
953
+ 'event: reasoning',
954
+ `data: ""\n`,
955
+ 'id: 1',
956
+ 'event: text',
957
+ `data: "你好"\n`,
958
+ 'id: 1',
959
+ 'event: text',
960
+ `data: "很高兴"\n`,
961
+ 'id: 1',
962
+ 'event: text',
963
+ `data: "为您"\n`,
964
+ 'id: 1',
965
+ 'event: text',
966
+ `data: "提供"\n`,
967
+ 'id: 1',
968
+ 'event: text',
969
+ `data: "帮助。"\n`,
970
+ 'id: 1',
971
+ 'event: stop',
972
+ `data: "stop"\n`,
973
+ ].map((i) => `${i}\n`),
974
+ );
975
+ });
976
+
758
977
  it('should handle reasoning in litellm', async () => {
759
978
  const data = [
760
979
  {
@@ -87,20 +87,31 @@ export const transformOpenAIStream = (
87
87
  return { data: item.finish_reason, id: chunk.id, type: 'stop' };
88
88
  }
89
89
 
90
- // DeepSeek reasoner will put thinking in the reasoning_content field
91
- // litellm will not set content = null when processing reasoning content
92
- // en: siliconflow has encountered a situation where both content and reasoning_content are present, so the parsing order go ahead
93
- // refs: https://github.com/lobehub/lobe-chat/issues/5681
94
- if (
95
- item.delta &&
96
- 'reasoning_content' in item.delta &&
97
- typeof item.delta.reasoning_content === 'string'
98
- ) {
99
- return { data: item.delta.reasoning_content, id: chunk.id, type: 'reasoning' };
100
- }
90
+ if (item.delta) {
91
+ let reasoning_content =
92
+ 'reasoning_content' in item.delta ? item.delta.reasoning_content : null;
93
+ let content = 'content' in item.delta ? item.delta.content : null;
94
+
95
+ // DeepSeek reasoner will put thinking in the reasoning_content field
96
+ // litellm and not set content = null when processing reasoning content
97
+ // en: siliconflow and aliyun bailian has encountered a situation where both content and reasoning_content are present, so need to handle it
98
+ // refs: https://github.com/lobehub/lobe-chat/issues/5681 (siliconflow)
99
+ // refs: https://github.com/lobehub/lobe-chat/issues/5956 (aliyun bailian)
100
+ if (typeof content === 'string' && typeof reasoning_content === 'string') {
101
+ if (content === '' && reasoning_content === '') {
102
+ content = null;
103
+ } else if (reasoning_content === '') {
104
+ reasoning_content = null;
105
+ }
106
+ }
101
107
 
102
- if (typeof item.delta?.content === 'string') {
103
- return { data: item.delta.content, id: chunk.id, type: 'text' };
108
+ if (typeof reasoning_content === 'string') {
109
+ return { data: reasoning_content, id: chunk.id, type: 'reasoning' };
110
+ }
111
+
112
+ if (typeof content === 'string') {
113
+ return { data: content, id: chunk.id, type: 'text' };
114
+ }
104
115
  }
105
116
 
106
117
  // 无内容情况
@@ -61,6 +61,16 @@ export const transformQwenStream = (chunk: OpenAI.ChatCompletionChunk): StreamPr
61
61
  } as StreamProtocolToolCallChunk;
62
62
  }
63
63
 
64
+ // DeepSeek reasoner will put thinking in the reasoning_content field
65
+ if (
66
+ item.delta &&
67
+ 'reasoning_content' in item.delta &&
68
+ typeof item.delta.reasoning_content === 'string' &&
69
+ item.delta.reasoning_content !== ''
70
+ ) {
71
+ return { data: item.delta.reasoning_content, id: chunk.id, type: 'reasoning' };
72
+ }
73
+
64
74
  if (typeof item.delta?.content === 'string') {
65
75
  return { data: item.delta.content, id: chunk.id, type: 'text' };
66
76
  }
@@ -11,6 +11,7 @@ export interface AgentState {
11
11
  agentSettingInstance?: AgentSettingsInstance | null;
12
12
  defaultAgentConfig: LobeAgentConfig;
13
13
  isInboxAgentConfigInit: boolean;
14
+ showAgentSetting: boolean;
14
15
  updateAgentChatConfigSignal?: AbortController;
15
16
  updateAgentConfigSignal?: AbortController;
16
17
  }
@@ -20,4 +21,5 @@ export const initialAgentChatState: AgentState = {
20
21
  agentMap: {},
21
22
  defaultAgentConfig: DEFAULT_AGENT_CONFIG,
22
23
  isInboxAgentConfigInit: false,
24
+ showAgentSetting: false,
23
25
  };
@@ -1,23 +0,0 @@
1
- 'use client';
2
-
3
- import { useLayoutEffect } from 'react';
4
-
5
- import { useQueryRoute } from '@/hooks/useQueryRoute';
6
-
7
- /**
8
- * @description: Chat Settings Modal (intercepting routes fallback when hard refresh)
9
- * @example: /chat/settings/modal?tab=prompt => /chat/settings
10
- * @refs: https://github.com/lobehub/lobe-chat/discussions/2295#discussioncomment-9290942
11
- */
12
-
13
- const ChatSettingsModalFallback = () => {
14
- const router = useQueryRoute();
15
-
16
- useLayoutEffect(() => {
17
- router.replace('/chat/settings', { query: { tab: '' } });
18
- }, []);
19
-
20
- return null;
21
- };
22
-
23
- export default ChatSettingsModalFallback;
@@ -1,61 +0,0 @@
1
- 'use client';
2
-
3
- import { Skeleton } from 'antd';
4
- import isEqual from 'fast-deep-equal';
5
- import dynamic from 'next/dynamic';
6
- import { PropsWithChildren, memo } from 'react';
7
- import { useTranslation } from 'react-i18next';
8
-
9
- import ModalLayout from '@/app/[variants]/@modal/_layout/ModalLayout';
10
- import StoreUpdater from '@/features/AgentSetting/StoreUpdater';
11
- import { Provider, createStore } from '@/features/AgentSetting/store';
12
- import { useChatSettingsTab } from '@/hooks/useChatSettingsTab';
13
- import { useAgentStore } from '@/store/agent';
14
- import { agentSelectors } from '@/store/agent/slices/chat';
15
- import { ChatSettingsTabs } from '@/store/global/initialState';
16
- import { useSessionStore } from '@/store/session';
17
- import { sessionMetaSelectors } from '@/store/session/selectors';
18
-
19
- import SettingModalLayout from '../../../_layout/SettingModalLayout';
20
-
21
- const CategoryContent = dynamic(() => import('./features/CategoryContent'), {
22
- loading: () => <Skeleton paragraph={{ rows: 6 }} title={false} />,
23
- ssr: false,
24
- });
25
-
26
- const Layout = memo<PropsWithChildren>(({ children }) => {
27
- const tab = useChatSettingsTab();
28
- const { t } = useTranslation('setting');
29
- const id = useSessionStore((s) => s.activeId);
30
- const config = useAgentStore(agentSelectors.currentAgentConfig, isEqual);
31
- const meta = useSessionStore(sessionMetaSelectors.currentAgentMeta, isEqual);
32
- const [updateAgentConfig] = useAgentStore((s) => [s.updateAgentConfig]);
33
- const [updateAgentMeta] = useSessionStore((s) => [
34
- s.updateSessionMeta,
35
- sessionMetaSelectors.currentAgentTitle(s),
36
- ]);
37
-
38
- return (
39
- <ModalLayout>
40
- <SettingModalLayout
41
- activeTitle={t(`agentTab.${tab as ChatSettingsTabs}`)}
42
- category={<CategoryContent />}
43
- desc={t('header.sessionDesc')}
44
- title={t('header.session')}
45
- >
46
- <Provider createStore={createStore}>
47
- <StoreUpdater
48
- config={config}
49
- id={id}
50
- meta={meta}
51
- onConfigChange={updateAgentConfig}
52
- onMetaChange={updateAgentMeta}
53
- />
54
- {children}
55
- </Provider>
56
- </SettingModalLayout>
57
- </ModalLayout>
58
- );
59
- });
60
-
61
- export default Layout;
@@ -1,5 +0,0 @@
1
- import { Skeleton } from 'antd';
2
-
3
- export default () => {
4
- return <Skeleton paragraph={{ rows: 6 }} style={{ paddingBlock: 16 }} />;
5
- };
@@ -1,56 +0,0 @@
1
- 'use client';
2
-
3
- import dynamic from 'next/dynamic';
4
-
5
- import { useChatSettingsTab } from '@/hooks/useChatSettingsTab';
6
- import { ChatSettingsTabs } from '@/store/global/initialState';
7
-
8
- import Skeleton from './loading';
9
-
10
- const loading = () => <Skeleton />;
11
-
12
- const AgentMeta = dynamic(() => import('@/features/AgentSetting/AgentMeta'), {
13
- loading,
14
- ssr: false,
15
- });
16
- const AgentChat = dynamic(() => import('@/features/AgentSetting/AgentChat'), {
17
- loading,
18
- ssr: false,
19
- });
20
- const AgentPrompt = dynamic(() => import('@/features/AgentSetting/AgentPrompt'), {
21
- loading,
22
- ssr: false,
23
- });
24
- const AgentPlugin = dynamic(() => import('@/features/AgentSetting/AgentPlugin'), {
25
- loading,
26
- ssr: false,
27
- });
28
- const AgentModal = dynamic(() => import('@/features/AgentSetting/AgentModal'), {
29
- loading,
30
- ssr: false,
31
- });
32
- const AgentTTS = dynamic(() => import('@/features/AgentSetting/AgentTTS'), { loading, ssr: false });
33
-
34
- /**
35
- * @description: Agent Settings Modal (intercepting route: /chat/settings/modal )
36
- * @refs: https://github.com/lobehub/lobe-chat/discussions/2295#discussioncomment-9290942
37
- */
38
-
39
- const Page = () => {
40
- const tab = useChatSettingsTab();
41
-
42
- return (
43
- <>
44
- {tab === ChatSettingsTabs.Meta && <AgentMeta />}
45
- {tab === ChatSettingsTabs.Prompt && <AgentPrompt modal />}
46
- {tab === ChatSettingsTabs.Chat && <AgentChat />}
47
- {tab === ChatSettingsTabs.Modal && <AgentModal />}
48
- {tab === ChatSettingsTabs.TTS && <AgentTTS />}
49
- {tab === ChatSettingsTabs.Plugin && <AgentPlugin />}
50
- </>
51
- );
52
- };
53
-
54
- Page.displayName = 'AgentSettingModal';
55
-
56
- export default Page;
@@ -1,12 +0,0 @@
1
- import { useQueryState } from 'nuqs';
2
-
3
- import { ChatSettingsTabs } from '@/store/global/initialState';
4
-
5
- export const useChatSettingsTab = () => {
6
- const [type] = useQueryState('tab', {
7
- clearOnDefault: true,
8
- defaultValue: ChatSettingsTabs.Meta,
9
- });
10
-
11
- return type as ChatSettingsTabs;
12
- };