@lobehub/chat 1.52.13 → 1.52.15

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 CHANGED
@@ -2,6 +2,64 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 1.52.15](https://github.com/lobehub/lobe-chat/compare/v1.52.14...v1.52.15)
6
+
7
+ <sup>Released on **2025-02-10**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: Fix lmstudio baseURL.
12
+
13
+ #### 💄 Styles
14
+
15
+ - **misc**: Optimized MaxToken Slider.
16
+
17
+ <br/>
18
+
19
+ <details>
20
+ <summary><kbd>Improvements and Fixes</kbd></summary>
21
+
22
+ #### What's fixed
23
+
24
+ - **misc**: Fix lmstudio baseURL, closes [#5988](https://github.com/lobehub/lobe-chat/issues/5988) ([1d19aa6](https://github.com/lobehub/lobe-chat/commit/1d19aa6))
25
+
26
+ #### Styles
27
+
28
+ - **misc**: Optimized MaxToken Slider, closes [#5952](https://github.com/lobehub/lobe-chat/issues/5952) ([3cdcb95](https://github.com/lobehub/lobe-chat/commit/3cdcb95))
29
+
30
+ </details>
31
+
32
+ <div align="right">
33
+
34
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
35
+
36
+ </div>
37
+
38
+ ### [Version 1.52.14](https://github.com/lobehub/lobe-chat/compare/v1.52.13...v1.52.14)
39
+
40
+ <sup>Released on **2025-02-10**</sup>
41
+
42
+ #### 💄 Styles
43
+
44
+ - **misc**: Refactor agent settings modal.
45
+
46
+ <br/>
47
+
48
+ <details>
49
+ <summary><kbd>Improvements and Fixes</kbd></summary>
50
+
51
+ #### Styles
52
+
53
+ - **misc**: Refactor agent settings modal, closes [#5987](https://github.com/lobehub/lobe-chat/issues/5987) ([6482f8a](https://github.com/lobehub/lobe-chat/commit/6482f8a))
54
+
55
+ </details>
56
+
57
+ <div align="right">
58
+
59
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
60
+
61
+ </div>
62
+
5
63
  ### [Version 1.52.13](https://github.com/lobehub/lobe-chat/compare/v1.52.12...v1.52.13)
6
64
 
7
65
  <sup>Released on **2025-02-10**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,25 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "Fix lmstudio baseURL."
6
+ ],
7
+ "improvements": [
8
+ "Optimized MaxToken Slider."
9
+ ]
10
+ },
11
+ "date": "2025-02-10",
12
+ "version": "1.52.15"
13
+ },
14
+ {
15
+ "children": {
16
+ "improvements": [
17
+ "Refactor agent settings modal."
18
+ ]
19
+ },
20
+ "date": "2025-02-10",
21
+ "version": "1.52.14"
22
+ },
2
23
  {
3
24
  "children": {
4
25
  "fixes": [
@@ -103,6 +103,30 @@ services:
103
103
  env_file:
104
104
  - .env
105
105
  restart: always
106
+ entrypoint: >
107
+ /bin/sh -c "
108
+ /bin/node /app/startServer.js &
109
+ LOBE_PID=\$!
110
+ sleep 3
111
+ if [ $(wget --timeout=5 --spider --server-response ${AUTH_CASDOOR_ISSUER}/.well-known/openid-configuration 2>&1 | grep -c 'HTTP/1.1 200 OK') -eq 0 ]; then
112
+ echo '⚠️Warining: Unable to fetch OIDC configuration from Casdoor'
113
+ echo 'Request URL: ${AUTH_CASDOOR_ISSUER}/.well-known/openid-configuration'
114
+ echo 'Read more at: https://lobehub.com/docs/self-hosting/server-database/docker-compose#necessary-configuration'
115
+ else
116
+ if ! wget -O - --timeout=5 ${AUTH_CASDOOR_ISSUER}/.well-known/openid-configuration 2>&1 | grep 'issuer' | grep ${AUTH_CASDOOR_ISSUER}; then
117
+ printf '❌Error: The Auth issuer is conflict, Issuer in OIDC configuration is: %s' \$(wget -O - --timeout=5 ${AUTH_CASDOOR_ISSUER}/.well-known/openid-configuration 2>&1 | grep -E 'issuer.*' | awk -F '\"' '{print \$4}')
118
+ echo ' , but the issuer in .env file is: ${AUTH_CASDOOR_ISSUER} '
119
+ echo 'Request URL: ${AUTH_CASDOOR_ISSUER}/.well-known/openid-configuration'
120
+ echo 'Read more at: https://lobehub.com/docs/self-hosting/server-database/docker-compose#necessary-configuration'
121
+ fi
122
+ fi
123
+ if [ $(wget --timeout=5 --spider --server-response ${S3_ENDPOINT}/minio/health/live 2>&1 | grep -c 'HTTP/1.1 200 OK') -eq 0 ]; then
124
+ echo '⚠️Warining: Unable to fetch MinIO health status'
125
+ echo 'Request URL: ${S3_ENDPOINT}/minio/health/live'
126
+ echo 'Read more at: https://lobehub.com/docs/self-hosting/server-database/docker-compose#necessary-configuration'
127
+ fi
128
+ wait \$LOBE_PID
129
+ "
106
130
 
107
131
  volumes:
108
132
  data:
@@ -107,13 +107,13 @@ If you are deploying using a public network, the following assumptions apply:
107
107
 
108
108
  Configure the Casdoor webhook so that LobeChat can receive notifications when user information is updated.
109
109
 
110
- Go to `Admin ` -> `Webhooks`, add a webhook, and fill in the following fields:
110
+ Go to `Admin` -> `Webhooks`, add a webhook, and fill in the following fields:
111
111
 
112
112
  - URL: `https://lobe.example.com/api/auth/webhooks/casdoor`
113
113
  - Method: `POST`
114
114
  - Content Type: `application/json`
115
115
  - Headers: `casdoor-secret`: `Your Webhook Secret`
116
- > The webhook is generated by yourself, you can visit https://generate-secret.vercel.app/10 to generate a 10 bit secret.
116
+ > The secret is generated by yourself, you can visit https://generate-secret.vercel.app/10 to generate a 10 bit secret.
117
117
 
118
118
  - Event: `update-user`
119
119
  - Is user extented: `true`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.52.13",
3
+ "version": "1.52.15",
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
 
@@ -35,7 +35,7 @@ const MaxTokenSlider = memo<MaxTokenSliderProps>(({ value, onChange, defaultValu
35
35
  const updateWithPowValue = (value: number) => {
36
36
  setPowValue(value);
37
37
 
38
- setTokens(getRealValue(value) === 1 ? 0 : powerKibi(value));
38
+ setTokens(getRealValue(value) <= 2 ? 0 : powerKibi(value));
39
39
  };
40
40
 
41
41
  const updateWithRealValue = (value: number) => {
@@ -48,16 +48,16 @@ const MaxTokenSlider = memo<MaxTokenSliderProps>(({ value, onChange, defaultValu
48
48
 
49
49
  const marks = useMemo(() => {
50
50
  return {
51
- [exponent(1)]: '0',
52
- [exponent(2)]: isMobile ? '2' : '2K', // 2 Kibi = 2048
53
- [exponent(4)]: isMobile ? '4' : '4K',
51
+ [exponent(2)]: '0',
52
+ [exponent(4)]: isMobile ? '4' : '4K', // 4 Kibi = 4096
54
53
  [exponent(8)]: isMobile ? '8' : '8K',
55
54
  [exponent(16)]: isMobile ? '16' : '16K',
56
55
  [exponent(32)]: isMobile ? '32' : '32K',
57
56
  [exponent(64)]: isMobile ? '64' : '64K',
58
57
  [exponent((128 / Kibi) * 1000)]: ' ', // hide tick mark
59
58
  [exponent((200 / Kibi) * 1000)]: isMobile ? '200' : '200k', // 200,000
60
- [exponent(Kibi)]: isMobile ? '1024' : '1M',
59
+ [exponent(Kibi)]: '1M',
60
+ [exponent(2 * Kibi)]: '2M',
61
61
  };
62
62
  }, [isMobile]);
63
63
 
@@ -66,14 +66,14 @@ const MaxTokenSlider = memo<MaxTokenSliderProps>(({ value, onChange, defaultValu
66
66
  <Flexbox flex={1}>
67
67
  <Slider
68
68
  marks={marks}
69
- max={exponent(Kibi)}
70
- min={0}
69
+ max={exponent(2 * Kibi)}
70
+ min={exponent(2)}
71
71
  onChange={updateWithPowValue}
72
72
  step={null}
73
73
  tooltip={{
74
74
  formatter: (x) => {
75
75
  if (typeof x === 'undefined') return;
76
- if (x === 0) return t('MaxTokenSlider.unlimited');
76
+ if (x <= exponent(2)) return t('MaxTokenSlider.unlimited');
77
77
 
78
78
  let value = getRealValue(x);
79
79
  if (value < 125) return value.toFixed(0) + 'K';
@@ -86,13 +86,14 @@ const MaxTokenSlider = memo<MaxTokenSliderProps>(({ value, onChange, defaultValu
86
86
  </Flexbox>
87
87
  <div>
88
88
  <InputNumber
89
+ changeOnWheel
89
90
  min={0}
90
91
  onChange={(e) => {
91
92
  if (!e && e !== 0) return;
92
93
 
93
94
  updateWithRealValue(e);
94
95
  }}
95
- step={2 * Kibi}
96
+ step={4 * Kibi}
96
97
  value={token}
97
98
  />
98
99
  </div>
@@ -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
  };
@@ -12,7 +12,7 @@ import * as debugStreamModule from '../utils/debugStream';
12
12
  import { LobeLMStudioAI } from './index';
13
13
 
14
14
  const provider = ModelProvider.LMStudio;
15
- const defaultBaseURL = 'http://localhost:1234/v1';
15
+ const defaultBaseURL = 'http://127.0.0.1:1234/v1';
16
16
 
17
17
  const bizErrorType = 'ProviderBizError';
18
18
  const invalidErrorType = 'InvalidProviderAPIKey';
@@ -3,7 +3,7 @@ import { LobeOpenAICompatibleFactory } from '../utils/openaiCompatibleFactory';
3
3
 
4
4
  export const LobeLMStudioAI = LobeOpenAICompatibleFactory({
5
5
  apiKey: 'placeholder-to-avoid-error',
6
- baseURL: 'http://localhost:1234/v1',
6
+ baseURL: 'http://127.0.0.1:1234/v1',
7
7
  debug: {
8
8
  chatCompletion: () => process.env.DEBUG_LMSTUDIO_CHAT_COMPLETION === '1',
9
9
  },
@@ -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,7 +1,9 @@
1
1
  import { changeLanguage } from 'i18next';
2
2
  import { describe, expect, it, vi } from 'vitest';
3
3
 
4
+ import { LOBE_LOCALE_COOKIE } from '@/const/locale';
4
5
  import { LocaleMode } from '@/types/locale';
6
+ import { setCookie } from '@/utils/client/cookie';
5
7
 
6
8
  import { switchLang } from './switchLang';
7
9
 
@@ -9,6 +11,10 @@ vi.mock('i18next', () => ({
9
11
  changeLanguage: vi.fn(),
10
12
  }));
11
13
 
14
+ vi.mock('./cookie', () => ({
15
+ setCookie: vi.fn(),
16
+ }));
17
+
12
18
  describe('switchLang', () => {
13
19
  afterEach(() => {
14
20
  vi.resetAllMocks();
@@ -20,6 +26,7 @@ describe('switchLang', () => {
20
26
 
21
27
  expect(changeLanguage).toHaveBeenCalledWith(locale);
22
28
  expect(document.documentElement.lang).toBe(locale);
29
+ expect(setCookie).toHaveBeenCalledWith(LOBE_LOCALE_COOKIE, locale, 365);
23
30
  });
24
31
 
25
32
  it('should change language based on navigator.language when locale is "auto"', () => {
@@ -30,5 +37,6 @@ describe('switchLang', () => {
30
37
 
31
38
  expect(changeLanguage).toHaveBeenCalledWith(navigatorLanguage);
32
39
  expect(document.documentElement.lang).toBe(navigatorLanguage);
40
+ expect(setCookie).toHaveBeenCalledWith(LOBE_LOCALE_COOKIE, undefined, 365);
33
41
  });
34
42
  });
@@ -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
- };