@lobehub/chat 0.150.7 → 0.150.9

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 (39) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/docs/self-hosting/advanced/upstream-sync.mdx +74 -0
  3. package/docs/self-hosting/advanced/upstream-sync.zh-CN.mdx +71 -0
  4. package/docs/usage/providers/ollama.mdx +1 -1
  5. package/docs/usage/providers/ollama.zh-CN.mdx +1 -1
  6. package/package.json +1 -1
  7. package/src/app/chat/(desktop)/features/ChatHeader/HeaderAction.tsx +2 -2
  8. package/src/app/chat/(desktop)/features/SideBar/index.tsx +2 -2
  9. package/src/app/chat/(mobile)/features/SessionHeader.tsx +2 -2
  10. package/src/app/chat/(mobile)/mobile/ChatHeader/index.tsx +2 -2
  11. package/src/app/chat/_layout/Desktop/SessionHeader.tsx +2 -2
  12. package/src/app/chat/features/PluginTag/index.tsx +2 -2
  13. package/src/app/chat/features/SessionListContent/List/index.tsx +2 -2
  14. package/src/app/chat/features/TelemetryNotification/index.tsx +5 -3
  15. package/src/app/settings/about/page.tsx +3 -3
  16. package/src/app/settings/features/SettingList/index.tsx +2 -2
  17. package/src/app/settings/llm/OpenAI/index.tsx +2 -2
  18. package/src/components/ModelIcon/index.tsx +1 -0
  19. package/src/components/ModelTag/ModelIcon.tsx +1 -0
  20. package/src/config/modelProviders/ollama.ts +6 -0
  21. package/src/features/AgentSetting/AgentPlugin/index.tsx +2 -2
  22. package/src/features/ChatInput/ActionBar/Tools/index.tsx +2 -2
  23. package/src/features/Conversation/Error/InvalidAccessCode.tsx +3 -3
  24. package/src/layout/GlobalProvider/index.tsx +5 -3
  25. package/src/server/globalConfig/index.ts +119 -0
  26. package/src/server/routers/config/index.ts +3 -112
  27. package/src/store/global/slices/common/selectors.ts +0 -2
  28. package/src/store/serverConfig/Provider.tsx +22 -0
  29. package/src/store/serverConfig/index.ts +3 -0
  30. package/src/store/serverConfig/selectors.test.ts +72 -0
  31. package/src/store/serverConfig/selectors.ts +11 -0
  32. package/src/store/serverConfig/store.test.ts +53 -0
  33. package/src/store/serverConfig/store.ts +61 -0
  34. package/src/store/featureFlags/Provider.tsx +0 -18
  35. package/src/store/featureFlags/index.ts +0 -3
  36. package/src/store/featureFlags/selectors.ts +0 -5
  37. package/src/store/featureFlags/store.ts +0 -42
  38. /package/src/server/{routers/config → globalConfig}/parseDefaultAgent.test.ts +0 -0
  39. /package/src/server/{routers/config → globalConfig}/parseDefaultAgent.ts +0 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 0.150.9](https://github.com/lobehub/lobe-chat/compare/v0.150.8...v0.150.9)
6
+
7
+ <sup>Released on **2024-04-28**</sup>
8
+
9
+ #### ♻ Code Refactoring
10
+
11
+ - **misc**: Refactor feature flags store to server config store.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Code refactoring
19
+
20
+ - **misc**: Refactor feature flags store to server config store, closes [#2263](https://github.com/lobehub/lobe-chat/issues/2263) ([2e991d7](https://github.com/lobehub/lobe-chat/commit/2e991d7))
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 0.150.8](https://github.com/lobehub/lobe-chat/compare/v0.150.7...v0.150.8)
31
+
32
+ <sup>Released on **2024-04-28**</sup>
33
+
34
+ #### 💄 Styles
35
+
36
+ - **ollama**: Phi3 Instruct models and its model icons.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### Styles
44
+
45
+ - **ollama**: Phi3 Instruct models and its model icons, closes [#2254](https://github.com/lobehub/lobe-chat/issues/2254) ([c9b55cc](https://github.com/lobehub/lobe-chat/commit/c9b55cc))
46
+
47
+ </details>
48
+
49
+ <div align="right">
50
+
51
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
52
+
53
+ </div>
54
+
5
55
  ### [Version 0.150.7](https://github.com/lobehub/lobe-chat/compare/v0.150.6...v0.150.7)
6
56
 
7
57
  <sup>Released on **2024-04-28**</sup>
@@ -85,3 +85,77 @@ Ensure that you have sufficient permissions to stop and remove the container bef
85
85
  No need to worry, you won't. All of LobeChat's chat records are stored in your local browser. Therefore, when redeploying LobeChat using Docker, your chat records will not be lost.
86
86
 
87
87
  </Callout>
88
+
89
+ If you wish to automate the above steps, you can follow the method below and use Crontab scheduling to complete it. The specific steps are as follows.
90
+
91
+ <Steps>
92
+
93
+ ### Write automatic update scripts and configuration files
94
+
95
+ First, create a `lobe.env` configuration file with various environment variables, for example:
96
+
97
+ ```env
98
+ OPENAI_API_KEY=sk-xxxx
99
+ OPENAI_PROXY_URL=https://api-proxy.com/v1
100
+ ACCESS_CODE=arthals2333
101
+ OPENAI_MODEL_LIST=-gpt-4,-gpt-4-32k,-gpt-3.5-turbo-16k,gpt-3.5-turbo-1106=gpt-3.5-turbo-16k,gpt-4-0125-preview=gpt-4-turbo,gpt-4-vision-preview=gpt-4-vision
102
+ ```
103
+
104
+ Then, you can use the following script to automate the update:
105
+
106
+ ```bash
107
+ #!/bin/bash
108
+ # auto-update-lobe-chat.sh
109
+
110
+ # Set up proxy (optional)
111
+ export https_proxy=http://127.0.0.1:7890 http_proxy=http://127.0.0.1:7890 all_proxy=socks5://127.0.0.1:7890
112
+
113
+ # Pull the latest image and store the output in a variable
114
+ output=$(docker pull lobehub/lobe-chat:latest 2>&1)
115
+
116
+ # Check if the pull command was executed successfully
117
+ if [ $? -ne 0 ]; then
118
+ exit 1
119
+ fi
120
+
121
+ # Check if the output contains a specific string
122
+ echo "$output" | grep -q "Image is up to date for lobehub/lobe-chat:latest"
123
+
124
+ # If the image is already up to date, do nothing
125
+ if [ $? -eq 0 ]; then
126
+ exit 0
127
+ fi
128
+
129
+ echo "Detected Lobe-Chat update"
130
+
131
+ # Remove the old container
132
+ echo "Removed: $(docker rm -f Lobe-Chat)"
133
+
134
+ # Run the new container
135
+ echo "Started: $(docker run -d --network=host --env-file /path/to/lobe.env --name=Lobe-Chat --restart=always lobehub/lobe-chat)"
136
+
137
+ # Print the update time and version
138
+ echo "Update time: $(date)"
139
+ echo "Version: $(docker inspect lobehub/lobe-chat:latest | grep 'org.opencontainers.image.version' | awk -F'"' '{print $4}')"
140
+
141
+ # Clean up unused images
142
+ docker images | grep 'lobehub/lobe-chat' | grep -v 'latest' | awk '{print $3}' | xargs -r docker rmi > /dev/null 2>&1
143
+ echo "Removed old images."
144
+ ```
145
+
146
+ <Callout type={'warning'}>
147
+ This script can be used in Crontab, but please ensure that your Crontab can find the correct
148
+ Docker command. It is recommended to use absolute paths.
149
+ </Callout>
150
+
151
+ Configure Crontab to execute the script every 5 minutes:
152
+
153
+ ### Configure Crontab to automatically execute scripts
154
+
155
+ The following command configures Crontab to execute scripts every 5 minutes, or as often as you like:
156
+
157
+ ```bash
158
+ */5 * * * * /path/to/auto-update-lobe-chat.sh >> /path/to/auto-update-lobe-chat.log 2>&1
159
+ ```
160
+
161
+ </Steps>
@@ -80,3 +80,74 @@ docker run -d -p 3210:3210 \
80
80
  放心,不会的。LobeChat 的聊天记录全部都存储在你的本地浏览器中。因此使用 Docker 重新部署 LobeChat 时,你的聊天记录并不会丢失。
81
81
 
82
82
  </Callout>
83
+
84
+ 如果你希望自动化执行以上步骤,你可以参照下面的方法,利用 Crontab 定时来完成。具体步骤如下。
85
+
86
+ <Steps>
87
+
88
+ ### 撰写自动更新脚本、配置文件
89
+
90
+ 首先,新建一个 `lobe.env` 配置文件,内容为各种环境变量,例如:
91
+
92
+ ```env
93
+ OPENAI_API_KEY=sk-xxxx
94
+ OPENAI_PROXY_URL=https://api-proxy.com/v1
95
+ ACCESS_CODE=arthals2333
96
+ OPENAI_MODEL_LIST=-gpt-4,-gpt-4-32k,-gpt-3.5-turbo-16k,gpt-3.5-turbo-1106=gpt-3.5-turbo-16k,gpt-4-0125-preview=gpt-4-turbo,gpt-4-vision-preview=gpt-4-vision
97
+ ```
98
+
99
+ 然后,你可以使用以下脚本来自动更新:
100
+
101
+ ```bash
102
+ #!/bin/bash
103
+ # auto-update-lobe-chat.sh
104
+
105
+ # 设置代理(可选)
106
+ export https_proxy=http://127.0.0.1:7890 http_proxy=http://127.0.0.1:7890 all_proxy=socks5://127.0.0.1:7890
107
+
108
+ # 拉取最新的镜像并将输出存储在变量中
109
+ output=$(docker pull lobehub/lobe-chat:latest 2>&1)
110
+
111
+ # 检查拉取命令是否成功执行
112
+ if [ $? -ne 0 ]; then
113
+ exit 1
114
+ fi
115
+
116
+ # 检查输出中是否包含特定的字符串
117
+ echo "$output" | grep -q "Image is up to date for lobehub/lobe-chat:latest"
118
+
119
+ # 如果镜像已经是最新的,则不执行任何操作
120
+ if [ $? -eq 0 ]; then
121
+ exit 0
122
+ fi
123
+
124
+ echo "Detected Lobe-Chat update"
125
+
126
+ # 删除旧的容器
127
+ echo "Removed: $(docker rm -f Lobe-Chat)"
128
+
129
+ # 运行新的容器
130
+ echo "Started: $(docker run -d --network=host --env-file /path/to/lobe.env --name=Lobe-Chat --restart=always lobehub/lobe-chat)"
131
+
132
+ # 打印更新的时间和版本
133
+ echo "Update time: $(date)"
134
+ echo "Version: $(docker inspect lobehub/lobe-chat:latest | grep 'org.opencontainers.image.version' | awk -F'"' '{print $4}')"
135
+
136
+ # 清理不再使用的镜像
137
+ docker images | grep 'lobehub/lobe-chat' | grep -v 'latest' | awk '{print $3}' | xargs -r docker rmi > /dev/null 2>&1
138
+ echo "Removed old images."
139
+ ```
140
+
141
+ <Callout type={'warning'}>
142
+ 此脚本可以在 Crontab 中使用,但请确认你的 Crontab 可以找到正确的 Docker 命令。建议使用绝对路径。
143
+ </Callout>
144
+
145
+ ### 配置 Crontab 自动执行脚本
146
+
147
+ 以下命令可以配置 Crontab 每 5 分钟执行一次脚本,你也可以根据需要调整执行频率:
148
+
149
+ ```bash
150
+ */5 * * * * /path/to/auto-update-lobe-chat.sh >> /path/to/auto-update-lobe-chat.log 2>&1
151
+ ```
152
+
153
+ </Steps>
@@ -25,7 +25,7 @@ This document will guide you on how to use Ollama in LobeChat:
25
25
 
26
26
  <Video
27
27
  alt="demonstration of using Ollama in LobeChat"
28
- height={556}
28
+ height={580}
29
29
  src="https://github.com/lobehub/lobe-chat/assets/28616219/c32b56db-c6a1-4876-9bc3-acbd37ec0c0c"
30
30
  />
31
31
 
@@ -23,7 +23,7 @@ Ollama 是一款强大的本地运行大型语言模型(LLM)的框架,支
23
23
 
24
24
  <Video
25
25
  alt={'在 LobeChat 中使用 Ollama的完整演示'}
26
- height={556}
26
+ height={580}
27
27
  src="https://github.com/lobehub/lobe-chat/assets/28616219/c32b56db-c6a1-4876-9bc3-acbd37ec0c0c"
28
28
  />
29
29
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.150.7",
3
+ "version": "0.150.9",
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",
@@ -4,8 +4,8 @@ import { memo } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
 
6
6
  import { DESKTOP_HEADER_ICON_SIZE } from '@/const/layoutTokens';
7
- import { featureFlagsSelectors, useFeatureFlagStore } from '@/store/featureFlags';
8
7
  import { useGlobalStore } from '@/store/global';
8
+ import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
9
9
 
10
10
  import SettingButton from '../../../features/SettingButton';
11
11
  import ShareButton from '../../../features/ShareButton';
@@ -18,7 +18,7 @@ const HeaderAction = memo(() => {
18
18
  s.toggleChatSideBar,
19
19
  ]);
20
20
 
21
- const { isAgentEditable } = useFeatureFlagStore(featureFlagsSelectors);
21
+ const { isAgentEditable } = useServerConfigStore(featureFlagsSelectors);
22
22
 
23
23
  return (
24
24
  <>
@@ -6,8 +6,8 @@ import { memo } from 'react';
6
6
  import TopicListContent from '@/app/chat/features/TopicListContent';
7
7
  import SafeSpacing from '@/components/SafeSpacing';
8
8
  import { CHAT_SIDEBAR_WIDTH } from '@/const/layoutTokens';
9
- import { featureFlagsSelectors, useFeatureFlagStore } from '@/store/featureFlags';
10
9
  import { useGlobalStore } from '@/store/global';
10
+ import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
11
11
  import { useSessionStore } from '@/store/session';
12
12
  import { sessionSelectors } from '@/store/session/selectors';
13
13
 
@@ -35,7 +35,7 @@ const Desktop = memo(() => {
35
35
  s.toggleChatSideBar,
36
36
  ]);
37
37
 
38
- const { isAgentEditable: showSystemRole } = useFeatureFlagStore(featureFlagsSelectors);
38
+ const { isAgentEditable: showSystemRole } = useServerConfigStore(featureFlagsSelectors);
39
39
  const isInbox = useSessionStore(sessionSelectors.isInboxSession);
40
40
 
41
41
  return (
@@ -7,9 +7,9 @@ import { Flexbox } from 'react-layout-kit';
7
7
 
8
8
  import { MOBILE_HEADER_ICON_SIZE } from '@/const/layoutTokens';
9
9
  import SyncStatusInspector from '@/features/SyncStatusInspector';
10
- import { featureFlagsSelectors, useFeatureFlagStore } from '@/store/featureFlags';
11
10
  import { useGlobalStore } from '@/store/global';
12
11
  import { commonSelectors } from '@/store/global/selectors';
12
+ import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
13
13
  import { useSessionStore } from '@/store/session';
14
14
  import { mobileHeaderSticky } from '@/styles/mobileHeader';
15
15
 
@@ -27,7 +27,7 @@ const Header = memo(() => {
27
27
  const [createSession] = useSessionStore((s) => [s.createSession]);
28
28
  const router = useRouter();
29
29
  const avatar = useGlobalStore(commonSelectors.userAvatar);
30
- const { showCreateSession } = useFeatureFlagStore(featureFlagsSelectors);
30
+ const { showCreateSession } = useServerConfigStore(featureFlagsSelectors);
31
31
 
32
32
  return (
33
33
  <MobileNavBar
@@ -2,7 +2,7 @@ import { MobileNavBar } from '@lobehub/ui';
2
2
  import { useRouter } from 'next/navigation';
3
3
  import { memo, useState } from 'react';
4
4
 
5
- import { featureFlagsSelectors, useFeatureFlagStore } from '@/store/featureFlags';
5
+ import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
6
6
 
7
7
  import SettingButton from '../../../features/SettingButton';
8
8
  import ShareButton from '../../../features/ShareButton';
@@ -12,7 +12,7 @@ const MobileHeader = memo(() => {
12
12
  const router = useRouter();
13
13
  const [open, setOpen] = useState(false);
14
14
 
15
- const { isAgentEditable } = useFeatureFlagStore(featureFlagsSelectors);
15
+ const { isAgentEditable } = useServerConfigStore(featureFlagsSelectors);
16
16
 
17
17
  // const items: MenuProps['items'] = [
18
18
  // {
@@ -8,7 +8,7 @@ import { Flexbox } from 'react-layout-kit';
8
8
  import { DESKTOP_HEADER_ICON_SIZE } from '@/const/layoutTokens';
9
9
  import SyncStatusTag from '@/features/SyncStatusInspector';
10
10
  import { useActionSWR } from '@/libs/swr';
11
- import { featureFlagsSelectors, useFeatureFlagStore } from '@/store/featureFlags';
11
+ import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
12
12
  import { useSessionStore } from '@/store/session';
13
13
 
14
14
  import SessionSearchBar from '../../features/SessionSearchBar';
@@ -27,7 +27,7 @@ const Header = memo(() => {
27
27
  const { styles } = useStyles();
28
28
  const { t } = useTranslation('chat');
29
29
  const [createSession] = useSessionStore((s) => [s.createSession]);
30
- const { enableWebrtc, showCreateSession } = useFeatureFlagStore(featureFlagsSelectors);
30
+ const { enableWebrtc, showCreateSession } = useServerConfigStore(featureFlagsSelectors);
31
31
 
32
32
  const { mutate, isValidating } = useActionSWR('session.createSession', () => createSession());
33
33
 
@@ -5,7 +5,7 @@ import isEqual from 'fast-deep-equal';
5
5
  import { LucideToyBrick } from 'lucide-react';
6
6
  import { memo } from 'react';
7
7
 
8
- import { featureFlagsSelectors, useFeatureFlagStore } from '@/store/featureFlags';
8
+ import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
9
9
  import { pluginHelpers, useToolStore } from '@/store/tool';
10
10
  import { toolSelectors } from '@/store/tool/selectors';
11
11
 
@@ -16,7 +16,7 @@ export interface PluginTagProps {
16
16
  }
17
17
 
18
18
  const PluginTag = memo<PluginTagProps>(({ plugins }) => {
19
- const { showDalle } = useFeatureFlagStore(featureFlagsSelectors);
19
+ const { showDalle } = useServerConfigStore(featureFlagsSelectors);
20
20
  const list = useToolStore(toolSelectors.metaList(showDalle), isEqual);
21
21
  const displayPlugin = useToolStore(toolSelectors.getMetaById(plugins[0]), isEqual);
22
22
 
@@ -7,7 +7,7 @@ import { Center } from 'react-layout-kit';
7
7
  import LazyLoad from 'react-lazy-load';
8
8
 
9
9
  import { SESSION_CHAT_URL } from '@/const/url';
10
- import { featureFlagsSelectors, useFeatureFlagStore } from '@/store/featureFlags';
10
+ import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
11
11
  import { useSessionStore } from '@/store/session';
12
12
  import { sessionSelectors } from '@/store/session/selectors';
13
13
  import { LobeAgentSession } from '@/types/session';
@@ -30,7 +30,7 @@ interface SessionListProps {
30
30
  const SessionList = memo<SessionListProps>(({ dataSource, groupId, showAddButton = true }) => {
31
31
  const { t } = useTranslation('chat');
32
32
  const isInit = useSessionStore((s) => sessionSelectors.isSessionListInit(s));
33
- const { showCreateSession } = useFeatureFlagStore(featureFlagsSelectors);
33
+ const { showCreateSession } = useServerConfigStore(featureFlagsSelectors);
34
34
  const { styles } = useStyles();
35
35
 
36
36
  const { mobile } = useResponsive();
@@ -10,7 +10,8 @@ import { Flexbox } from 'react-layout-kit';
10
10
 
11
11
  import { PRIVACY_URL } from '@/const/url';
12
12
  import { useGlobalStore } from '@/store/global';
13
- import { commonSelectors } from '@/store/global/selectors';
13
+ import { useServerConfigStore } from '@/store/serverConfig';
14
+ import { serverConfigSelectors } from '@/store/serverConfig/selectors';
14
15
 
15
16
  const useStyles = createStyles(({ css, token, isDarkMode }) => ({
16
17
  container: css`
@@ -55,8 +56,9 @@ const TelemetryNotification = memo<{ mobile?: boolean }>(({ mobile }) => {
55
56
  const { styles, theme, cx } = useStyles();
56
57
 
57
58
  const { t } = useTranslation('common');
58
- const [shouldCheck, useCheckTrace, updatePreference] = useGlobalStore((s) => [
59
- commonSelectors.enabledTelemetryChat(s),
59
+ const shouldCheck = useServerConfigStore(serverConfigSelectors.enabledTelemetryChat);
60
+
61
+ const [useCheckTrace, updatePreference] = useGlobalStore((s) => [
60
62
  s.useCheckTrace,
61
63
  s.updatePreference,
62
64
  ]);
@@ -6,8 +6,8 @@ import { useTranslation } from 'react-i18next';
6
6
  import { Flexbox } from 'react-layout-kit';
7
7
 
8
8
  import PageTitle from '@/components/PageTitle';
9
- import { useGlobalStore } from '@/store/global';
10
- import { commonSelectors } from '@/store/global/selectors';
9
+ import { useServerConfigStore } from '@/store/serverConfig';
10
+ import { serverConfigSelectors } from '@/store/serverConfig/selectors';
11
11
 
12
12
  import AboutList from './AboutList';
13
13
  import Analytics from './Analytics';
@@ -23,7 +23,7 @@ export default memo(() => {
23
23
  const { t } = useTranslation('setting');
24
24
 
25
25
  const { styles } = useStyles();
26
- const enabledTelemetryChat = useGlobalStore(commonSelectors.enabledTelemetryChat);
26
+ const enabledTelemetryChat = useServerConfigStore(serverConfigSelectors.enabledTelemetryChat);
27
27
 
28
28
  return (
29
29
  <>
@@ -3,8 +3,8 @@ import Link from 'next/link';
3
3
  import { memo } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
 
6
- import { featureFlagsSelectors, useFeatureFlagStore } from '@/store/featureFlags';
7
6
  import { SettingsTabs } from '@/store/global/initialState';
7
+ import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
8
8
 
9
9
  import Item from './Item';
10
10
 
@@ -21,7 +21,7 @@ export interface SettingListProps {
21
21
 
22
22
  const SettingList = memo<SettingListProps>(({ activeTab, mobile }) => {
23
23
  const { t } = useTranslation('setting');
24
- const { enableWebrtc, showLLM } = useFeatureFlagStore(featureFlagsSelectors);
24
+ const { enableWebrtc, showLLM } = useServerConfigStore(featureFlagsSelectors);
25
25
 
26
26
  const items = [
27
27
  { icon: Settings2, label: t('tab.common'), value: SettingsTabs.Common },
@@ -1,12 +1,12 @@
1
1
  import { OpenAI } from '@lobehub/icons';
2
2
  import { memo } from 'react';
3
3
 
4
- import { featureFlagsSelectors, useFeatureFlagStore } from '@/store/featureFlags';
4
+ import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
5
5
 
6
6
  import ProviderConfig from '../components/ProviderConfig';
7
7
 
8
8
  const OpenAIProvider = memo(() => {
9
- const { showOpenAIProxyUrl, showOpenAIApiKey } = useFeatureFlagStore(featureFlagsSelectors);
9
+ const { showOpenAIProxyUrl, showOpenAIApiKey } = useServerConfigStore(featureFlagsSelectors);
10
10
 
11
11
  return (
12
12
  <ProviderConfig
@@ -95,6 +95,7 @@ const ModelIcon = memo<ModelProviderIconProps>(({ model: originModel, size = 12
95
95
  return <Stability.Avatar size={size} />;
96
96
 
97
97
  if (model.includes('wizardlm')) return <Azure.Avatar size={size} />;
98
+ if (model.includes('phi3')) return <Azure.Avatar size={size} />;
98
99
  if (model.includes('firefly')) return <Adobe.Avatar size={size} />;
99
100
  if (model.includes('jamba') ||
100
101
  model.includes('j2-'))
@@ -82,6 +82,7 @@ const ModelIcon = memo<ModelIconProps>(({ model, size = 12 }) => {
82
82
  return <Stability size={size} />;
83
83
 
84
84
  if (model.includes('wizardlm')) return <Azure size={size} />;
85
+ if (model.includes('phi3')) return <Azure size={size} />;
85
86
  if (model.includes('firefly')) return <AdobeFirefly size={size} />;
86
87
  if (model.includes('jamba') || model.includes('j2-')) return <Ai21 size={size} />;
87
88
  });
@@ -91,6 +91,12 @@ const Ollama: ModelProviderCard = {
91
91
  id: 'codellama:python',
92
92
  tokens: 16_000,
93
93
  },
94
+ {
95
+ displayName: 'Phi3-Instruct 3.8B',
96
+ enabled: true,
97
+ id: 'phi3:instruct',
98
+ tokens: 128_000,
99
+ },
94
100
  {
95
101
  displayName: 'Mistral',
96
102
  enabled: true,
@@ -9,7 +9,7 @@ import { Center, Flexbox } from 'react-layout-kit';
9
9
  import { FORM_STYLE } from '@/const/layoutTokens';
10
10
  import PluginStore from '@/features/PluginStore';
11
11
  import PluginTag from '@/features/PluginStore/PluginItem/PluginTag';
12
- import { featureFlagsSelectors, useFeatureFlagStore } from '@/store/featureFlags';
12
+ import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
13
13
  import { pluginHelpers, useToolStore } from '@/store/tool';
14
14
  import { toolSelectors } from '@/store/tool/selectors';
15
15
 
@@ -29,7 +29,7 @@ const AgentPlugin = memo(() => {
29
29
  s.toggleAgentPlugin,
30
30
  ]);
31
31
 
32
- const { showDalle } = useFeatureFlagStore(featureFlagsSelectors);
32
+ const { showDalle } = useServerConfigStore(featureFlagsSelectors);
33
33
  const installedPlugins = useToolStore(toolSelectors.metaList(showDalle), isEqual);
34
34
  const useFetchInstalledPlugins = useToolStore((s) => s.useFetchInstalledPlugins);
35
35
 
@@ -11,9 +11,9 @@ import { Flexbox } from 'react-layout-kit';
11
11
  import PluginStore from '@/features/PluginStore';
12
12
  import { useAgentStore } from '@/store/agent';
13
13
  import { agentSelectors } from '@/store/agent/selectors';
14
- import { featureFlagsSelectors, useFeatureFlagStore } from '@/store/featureFlags';
15
14
  import { useGlobalStore } from '@/store/global';
16
15
  import { modelProviderSelectors } from '@/store/global/selectors';
16
+ import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
17
17
  import { pluginHelpers, useToolStore } from '@/store/tool';
18
18
  import { builtinToolSelectors, pluginSelectors } from '@/store/tool/selectors';
19
19
 
@@ -35,7 +35,7 @@ const useStyles = createStyles(({ css, prefixCls }) => ({
35
35
  const Tools = memo(() => {
36
36
  const { t } = useTranslation('setting');
37
37
  const list = useToolStore(pluginSelectors.installedPluginMetaList, isEqual);
38
- const { showDalle } = useFeatureFlagStore(featureFlagsSelectors);
38
+ const { showDalle } = useServerConfigStore(featureFlagsSelectors);
39
39
  const builtinList = useToolStore(builtinToolSelectors.metaList(showDalle), isEqual);
40
40
 
41
41
  const enablePluginCount = useAgentStore(
@@ -6,8 +6,8 @@ import { memo, useState } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
  import { Flexbox } from 'react-layout-kit';
8
8
 
9
- import { useGlobalStore } from '@/store/global';
10
- import { commonSelectors } from '@/store/global/selectors';
9
+ import { useServerConfigStore } from '@/store/serverConfig';
10
+ import { serverConfigSelectors } from '@/store/serverConfig/selectors';
11
11
 
12
12
  import APIKeyForm from './APIKeyForm';
13
13
  import AccessCodeForm from './AccessCodeForm';
@@ -27,7 +27,7 @@ interface InvalidAccessCodeProps {
27
27
 
28
28
  const InvalidAccessCode = memo<InvalidAccessCodeProps>(({ id, provider }) => {
29
29
  const { t } = useTranslation('error');
30
- const isEnabledOAuth = useGlobalStore(commonSelectors.enabledOAuthSSO);
30
+ const isEnabledOAuth = useServerConfigStore(serverConfigSelectors.enabledOAuthSSO);
31
31
  const defaultTab = isEnabledOAuth ? Tab.Oauth : Tab.Password;
32
32
  const [mode, setMode] = useState<Tab>(defaultTab);
33
33
 
@@ -10,7 +10,8 @@ import {
10
10
  LOBE_THEME_NEUTRAL_COLOR,
11
11
  LOBE_THEME_PRIMARY_COLOR,
12
12
  } from '@/const/theme';
13
- import { FeatureFlagStoreProvider } from '@/store/featureFlags';
13
+ import { getServerGlobalConfig } from '@/server/globalConfig';
14
+ import { ServerConfigStoreProvider } from '@/store/serverConfig';
14
15
  import { getAntdLocale } from '@/utils/locale';
15
16
 
16
17
  import AppTheme from './AppTheme';
@@ -46,6 +47,7 @@ const GlobalLayout = async ({ children }: GlobalLayoutProps) => {
46
47
 
47
48
  // get default feature flags to use with ssr
48
49
  const serverFeatureFlags = getServerFeatureFlagsValue();
50
+ const serverConfig = getServerGlobalConfig();
49
51
  return (
50
52
  <StyleRegistry>
51
53
  <Locale antdLocale={antdLocale} defaultLang={defaultLang?.value}>
@@ -55,9 +57,9 @@ const GlobalLayout = async ({ children }: GlobalLayoutProps) => {
55
57
  defaultPrimaryColor={primaryColor?.value as any}
56
58
  >
57
59
  <StoreInitialization />
58
- <FeatureFlagStoreProvider featureFlags={serverFeatureFlags}>
60
+ <ServerConfigStoreProvider featureFlags={serverFeatureFlags} serverConfig={serverConfig}>
59
61
  {children}
60
- </FeatureFlagStoreProvider>
62
+ </ServerConfigStoreProvider>
61
63
  <DebugUI />
62
64
  </AppTheme>
63
65
  </Locale>
@@ -0,0 +1,119 @@
1
+ import {
2
+ OllamaProviderCard,
3
+ OpenAIProviderCard,
4
+ OpenRouterProviderCard,
5
+ TogetherAIProviderCard,
6
+ } from '@/config/modelProviders';
7
+ import { getServerConfig } from '@/config/server';
8
+ import { GlobalServerConfig } from '@/types/serverConfig';
9
+ import { extractEnabledModels, transformToChatModelCards } from '@/utils/parseModels';
10
+
11
+ import { parseAgentConfig } from './parseDefaultAgent';
12
+
13
+ export const getServerGlobalConfig = () => {
14
+ const {
15
+ ENABLE_LANGFUSE,
16
+ ENABLE_OAUTH_SSO,
17
+
18
+ DEFAULT_AGENT_CONFIG,
19
+ OPENAI_MODEL_LIST,
20
+
21
+ ENABLED_MOONSHOT,
22
+ ENABLED_ZHIPU,
23
+ ENABLED_AWS_BEDROCK,
24
+ ENABLED_GOOGLE,
25
+ ENABLED_GROQ,
26
+ ENABLED_PERPLEXITY,
27
+ ENABLED_ANTHROPIC,
28
+ ENABLED_MISTRAL,
29
+
30
+ ENABLED_AZURE_OPENAI,
31
+ AZURE_MODEL_LIST,
32
+
33
+ ENABLE_OLLAMA,
34
+ OLLAMA_MODEL_LIST,
35
+ OLLAMA_PROXY_URL,
36
+
37
+ ENABLED_OPENROUTER,
38
+ OPENROUTER_MODEL_LIST,
39
+
40
+ ENABLED_ZEROONE,
41
+ ENABLED_TOGETHERAI,
42
+ TOGETHERAI_MODEL_LIST,
43
+ } = getServerConfig();
44
+
45
+ const config: GlobalServerConfig = {
46
+ defaultAgent: {
47
+ config: parseAgentConfig(DEFAULT_AGENT_CONFIG),
48
+ },
49
+
50
+ enabledOAuthSSO: ENABLE_OAUTH_SSO,
51
+ languageModel: {
52
+ anthropic: {
53
+ enabled: ENABLED_ANTHROPIC,
54
+ },
55
+ azure: {
56
+ enabled: ENABLED_AZURE_OPENAI,
57
+ enabledModels: extractEnabledModels(AZURE_MODEL_LIST, true),
58
+ serverModelCards: transformToChatModelCards({
59
+ defaultChatModels: [],
60
+ modelString: AZURE_MODEL_LIST,
61
+ withDeploymentName: true,
62
+ }),
63
+ },
64
+ bedrock: { enabled: ENABLED_AWS_BEDROCK },
65
+ google: { enabled: ENABLED_GOOGLE },
66
+ groq: { enabled: ENABLED_GROQ },
67
+ mistral: { enabled: ENABLED_MISTRAL },
68
+ moonshot: { enabled: ENABLED_MOONSHOT },
69
+ ollama: {
70
+ enabled: ENABLE_OLLAMA,
71
+ fetchOnClient: !OLLAMA_PROXY_URL,
72
+ serverModelCards: transformToChatModelCards({
73
+ defaultChatModels: OllamaProviderCard.chatModels,
74
+ modelString: OLLAMA_MODEL_LIST,
75
+ }),
76
+ },
77
+ openai: {
78
+ enabledModels: extractEnabledModels(OPENAI_MODEL_LIST),
79
+ serverModelCards: transformToChatModelCards({
80
+ defaultChatModels: OpenAIProviderCard.chatModels,
81
+ modelString: OPENAI_MODEL_LIST,
82
+ }),
83
+ },
84
+
85
+ openrouter: {
86
+ enabled: ENABLED_OPENROUTER,
87
+ enabledModels: extractEnabledModels(OPENROUTER_MODEL_LIST),
88
+ serverModelCards: transformToChatModelCards({
89
+ defaultChatModels: OpenRouterProviderCard.chatModels,
90
+ modelString: OPENROUTER_MODEL_LIST,
91
+ }),
92
+ },
93
+ perplexity: { enabled: ENABLED_PERPLEXITY },
94
+
95
+ togetherai: {
96
+ enabled: ENABLED_TOGETHERAI,
97
+ enabledModels: extractEnabledModels(TOGETHERAI_MODEL_LIST),
98
+ serverModelCards: transformToChatModelCards({
99
+ defaultChatModels: TogetherAIProviderCard.chatModels,
100
+ modelString: TOGETHERAI_MODEL_LIST,
101
+ }),
102
+ },
103
+
104
+ zeroone: { enabled: ENABLED_ZEROONE },
105
+ zhipu: { enabled: ENABLED_ZHIPU },
106
+ },
107
+ telemetry: {
108
+ langfuse: ENABLE_LANGFUSE,
109
+ },
110
+ };
111
+
112
+ return config;
113
+ };
114
+
115
+ export const getServerDefaultAgentConfig = () => {
116
+ const { DEFAULT_AGENT_CONFIG } = getServerConfig();
117
+
118
+ return parseAgentConfig(DEFAULT_AGENT_CONFIG) || {};
119
+ };
@@ -1,121 +1,12 @@
1
- import {
2
- OllamaProviderCard,
3
- OpenAIProviderCard,
4
- OpenRouterProviderCard,
5
- TogetherAIProviderCard,
6
- } from '@/config/modelProviders';
7
- import { getServerConfig } from '@/config/server';
8
1
  import { publicProcedure, router } from '@/libs/trpc';
9
- import { parseAgentConfig } from '@/server/routers/config/parseDefaultAgent';
10
- import { GlobalServerConfig } from '@/types/serverConfig';
11
- import { extractEnabledModels, transformToChatModelCards } from '@/utils/parseModels';
2
+ import { getServerDefaultAgentConfig, getServerGlobalConfig } from '@/server/globalConfig';
12
3
 
13
4
  export const configRouter = router({
14
5
  getDefaultAgentConfig: publicProcedure.query(async () => {
15
- const { DEFAULT_AGENT_CONFIG } = getServerConfig();
16
-
17
- return parseAgentConfig(DEFAULT_AGENT_CONFIG) || {};
6
+ return getServerDefaultAgentConfig();
18
7
  }),
19
8
 
20
9
  getGlobalConfig: publicProcedure.query(async () => {
21
- const {
22
- ENABLE_LANGFUSE,
23
- ENABLE_OAUTH_SSO,
24
-
25
- DEFAULT_AGENT_CONFIG,
26
- OPENAI_MODEL_LIST,
27
-
28
- ENABLED_MOONSHOT,
29
- ENABLED_ZHIPU,
30
- ENABLED_AWS_BEDROCK,
31
- ENABLED_GOOGLE,
32
- ENABLED_GROQ,
33
- ENABLED_PERPLEXITY,
34
- ENABLED_ANTHROPIC,
35
- ENABLED_MISTRAL,
36
-
37
- ENABLED_AZURE_OPENAI,
38
- AZURE_MODEL_LIST,
39
-
40
- ENABLE_OLLAMA,
41
- OLLAMA_MODEL_LIST,
42
- OLLAMA_PROXY_URL,
43
-
44
- ENABLED_OPENROUTER,
45
- OPENROUTER_MODEL_LIST,
46
-
47
- ENABLED_ZEROONE,
48
- ENABLED_TOGETHERAI,
49
- TOGETHERAI_MODEL_LIST,
50
- } = getServerConfig();
51
-
52
- const config: GlobalServerConfig = {
53
- defaultAgent: {
54
- config: parseAgentConfig(DEFAULT_AGENT_CONFIG),
55
- },
56
-
57
- enabledOAuthSSO: ENABLE_OAUTH_SSO,
58
- languageModel: {
59
- anthropic: {
60
- enabled: ENABLED_ANTHROPIC,
61
- },
62
- azure: {
63
- enabled: ENABLED_AZURE_OPENAI,
64
- enabledModels: extractEnabledModels(AZURE_MODEL_LIST, true),
65
- serverModelCards: transformToChatModelCards({
66
- defaultChatModels: [],
67
- modelString: AZURE_MODEL_LIST,
68
- withDeploymentName: true,
69
- }),
70
- },
71
- bedrock: { enabled: ENABLED_AWS_BEDROCK },
72
- google: { enabled: ENABLED_GOOGLE },
73
- groq: { enabled: ENABLED_GROQ },
74
- mistral: { enabled: ENABLED_MISTRAL },
75
- moonshot: { enabled: ENABLED_MOONSHOT },
76
- ollama: {
77
- enabled: ENABLE_OLLAMA,
78
- fetchOnClient: !OLLAMA_PROXY_URL,
79
- serverModelCards: transformToChatModelCards({
80
- defaultChatModels: OllamaProviderCard.chatModels,
81
- modelString: OLLAMA_MODEL_LIST,
82
- }),
83
- },
84
- openai: {
85
- enabledModels: extractEnabledModels(OPENAI_MODEL_LIST),
86
- serverModelCards: transformToChatModelCards({
87
- defaultChatModels: OpenAIProviderCard.chatModels,
88
- modelString: OPENAI_MODEL_LIST,
89
- }),
90
- },
91
-
92
- openrouter: {
93
- enabled: ENABLED_OPENROUTER,
94
- enabledModels: extractEnabledModels(OPENROUTER_MODEL_LIST),
95
- serverModelCards: transformToChatModelCards({
96
- defaultChatModels: OpenRouterProviderCard.chatModels,
97
- modelString: OPENROUTER_MODEL_LIST,
98
- }),
99
- },
100
- perplexity: { enabled: ENABLED_PERPLEXITY },
101
-
102
- togetherai: {
103
- enabled: ENABLED_TOGETHERAI,
104
- enabledModels: extractEnabledModels(TOGETHERAI_MODEL_LIST),
105
- serverModelCards: transformToChatModelCards({
106
- defaultChatModels: TogetherAIProviderCard.chatModels,
107
- modelString: TOGETHERAI_MODEL_LIST,
108
- }),
109
- },
110
-
111
- zeroone: { enabled: ENABLED_ZEROONE },
112
- zhipu: { enabled: ENABLED_ZHIPU },
113
- },
114
- telemetry: {
115
- langfuse: ENABLE_LANGFUSE,
116
- },
117
- };
118
-
119
- return config;
10
+ return getServerGlobalConfig();
120
11
  }),
121
12
  });
@@ -1,8 +1,6 @@
1
1
  import { GlobalStore } from '@/store/global';
2
2
 
3
3
  export const commonSelectors = {
4
- enabledOAuthSSO: (s: GlobalStore) => s.serverConfig.enabledOAuthSSO,
5
- enabledTelemetryChat: (s: GlobalStore) => s.serverConfig.telemetry.langfuse || false,
6
4
  userAvatar: (s: GlobalStore) => s.avatar || '',
7
5
  userId: (s: GlobalStore) => s.userId,
8
6
  };
@@ -0,0 +1,22 @@
1
+ 'use client';
2
+
3
+ import { ReactNode, memo } from 'react';
4
+
5
+ import { IFeatureFlags } from '@/config/featureFlags';
6
+ import { GlobalServerConfig } from '@/types/serverConfig';
7
+
8
+ import { Provider, createServerConfigStore } from './store';
9
+
10
+ interface GlobalStoreProviderProps {
11
+ children: ReactNode;
12
+ featureFlags?: Partial<IFeatureFlags>;
13
+ serverConfig?: GlobalServerConfig;
14
+ }
15
+
16
+ export const ServerConfigStoreProvider = memo<GlobalStoreProviderProps>(
17
+ ({ children, featureFlags, serverConfig }) => (
18
+ <Provider createStore={() => createServerConfigStore({ featureFlags, serverConfig })}>
19
+ {children}
20
+ </Provider>
21
+ ),
22
+ );
@@ -0,0 +1,3 @@
1
+ export { ServerConfigStoreProvider } from './Provider';
2
+ export { featureFlagsSelectors } from './selectors';
3
+ export { useServerConfigStore } from './store';
@@ -0,0 +1,72 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+
3
+ import { featureFlagsSelectors, serverConfigSelectors } from './selectors';
4
+ import { initServerConfigStore } from './store';
5
+
6
+ vi.mock('zustand/traditional');
7
+
8
+ describe('featureFlagsSelectors', () => {
9
+ it('should return mapped feature flags from store', () => {
10
+ const store = initServerConfigStore({
11
+ featureFlags: {
12
+ language_model_settings: false,
13
+ edit_agent: false,
14
+ },
15
+ });
16
+
17
+ const result = featureFlagsSelectors(store.getState());
18
+
19
+ expect(result).toEqual({
20
+ enableWebrtc: true,
21
+ isAgentEditable: false,
22
+ showCreateSession: true,
23
+ showDalle: true,
24
+ showLLM: false,
25
+ showOpenAIApiKey: true,
26
+ showOpenAIProxyUrl: true,
27
+ });
28
+ });
29
+ });
30
+
31
+ describe('serverConfigSelectors', () => {
32
+ describe('enabledOAuthSSO', () => {
33
+ it('should return enabledOAuthSSO value from store', () => {
34
+ const store = initServerConfigStore({
35
+ serverConfig: {
36
+ enabledOAuthSSO: true,
37
+ telemetry: {},
38
+ },
39
+ });
40
+
41
+ const result = serverConfigSelectors.enabledOAuthSSO(store.getState());
42
+
43
+ expect(result).toBe(true);
44
+ });
45
+ });
46
+
47
+ describe('enabledTelemetryChat', () => {
48
+ it('should return langfuse value from store when defined', () => {
49
+ const store = initServerConfigStore({
50
+ serverConfig: {
51
+ telemetry: { langfuse: true },
52
+ },
53
+ });
54
+
55
+ const result = serverConfigSelectors.enabledTelemetryChat(store.getState());
56
+
57
+ expect(result).toBe(true);
58
+ });
59
+
60
+ it('should return false when langfuse is not defined', () => {
61
+ const store = initServerConfigStore({
62
+ serverConfig: {
63
+ telemetry: {},
64
+ },
65
+ });
66
+
67
+ const result = serverConfigSelectors.enabledTelemetryChat(store.getState());
68
+
69
+ expect(result).toBe(false);
70
+ });
71
+ });
72
+ });
@@ -0,0 +1,11 @@
1
+ import { mapFeatureFlagsEnvToState } from '@/config/featureFlags';
2
+
3
+ import { ServerConfigStore } from './store';
4
+
5
+ export const featureFlagsSelectors = (s: ServerConfigStore) =>
6
+ mapFeatureFlagsEnvToState(s.featureFlags);
7
+
8
+ export const serverConfigSelectors = {
9
+ enabledOAuthSSO: (s: ServerConfigStore) => s.serverConfig.enabledOAuthSSO,
10
+ enabledTelemetryChat: (s: ServerConfigStore) => s.serverConfig.telemetry.langfuse || false,
11
+ };
@@ -0,0 +1,53 @@
1
+ import { act } from '@testing-library/react';
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
+
4
+ import { DEFAULT_FEATURE_FLAGS } from '@/config/featureFlags';
5
+
6
+ import { ServerConfigStore, createServerConfigStore, initServerConfigStore } from './store';
7
+
8
+ describe('createServerConfigStore', () => {
9
+ beforeEach(() => {
10
+ // 每个测试用例前重置模块状态
11
+ vi.resetModules();
12
+ });
13
+
14
+ it('should create a singleton store', () => {
15
+ const store1 = createServerConfigStore();
16
+ const store2 = createServerConfigStore();
17
+
18
+ expect(store1).toBe(store2);
19
+ });
20
+
21
+ it('should initialize store with default state', () => {
22
+ const store = createServerConfigStore();
23
+
24
+ expect(store.getState()).toEqual({
25
+ featureFlags: DEFAULT_FEATURE_FLAGS,
26
+ serverConfig: { telemetry: {} },
27
+ });
28
+ });
29
+
30
+ it('should initialize store with custom initial state', () => {
31
+ const initialState: Partial<ServerConfigStore> = {
32
+ featureFlags: { edit_agent: false },
33
+ serverConfig: { telemetry: { langfuse: true } },
34
+ };
35
+
36
+ const store = initServerConfigStore(initialState);
37
+
38
+ expect(store.getState().featureFlags.edit_agent).toBeFalsy();
39
+ expect(store.getState().serverConfig).toEqual({
40
+ telemetry: { langfuse: true },
41
+ });
42
+ });
43
+
44
+ it('should update store state correctly', () => {
45
+ const store = createServerConfigStore();
46
+
47
+ act(() => {
48
+ store.setState({ featureFlags: { dalle: false } });
49
+ });
50
+
51
+ expect(store.getState().featureFlags.dalle).toBeFalsy();
52
+ });
53
+ });
@@ -0,0 +1,61 @@
1
+ import { StoreApi } from 'zustand';
2
+ import { createContext } from 'zustand-utils';
3
+ import { devtools } from 'zustand/middleware';
4
+ import { shallow } from 'zustand/shallow';
5
+ import { createWithEqualityFn } from 'zustand/traditional';
6
+ import { StateCreator } from 'zustand/vanilla';
7
+
8
+ import { DEFAULT_FEATURE_FLAGS, IFeatureFlags } from '@/config/featureFlags';
9
+ import { GlobalServerConfig } from '@/types/serverConfig';
10
+ import { isDev } from '@/utils/env';
11
+ import { merge } from '@/utils/merge';
12
+ import { StoreApiWithSelector } from '@/utils/zustand';
13
+
14
+ const initialState: ServerConfigStore = {
15
+ featureFlags: DEFAULT_FEATURE_FLAGS,
16
+ serverConfig: { telemetry: {} },
17
+ };
18
+
19
+ // =============== 聚合 createStoreFn ============ //
20
+
21
+ export interface ServerConfigStore {
22
+ featureFlags: IFeatureFlags;
23
+ serverConfig: GlobalServerConfig;
24
+ }
25
+
26
+ type CreateStore = (
27
+ initState: Partial<ServerConfigStore>,
28
+ ) => StateCreator<ServerConfigStore, [['zustand/devtools', never]]>;
29
+
30
+ const createStore: CreateStore = (runtimeState) => () => ({
31
+ ...merge(initialState, runtimeState),
32
+ });
33
+
34
+ // =============== 实装 useStore ============ //
35
+
36
+ let store: StoreApi<ServerConfigStore>;
37
+
38
+ export const initServerConfigStore = (initState: Partial<ServerConfigStore>) =>
39
+ createWithEqualityFn<ServerConfigStore>()(
40
+ devtools(createStore(initState || {}), {
41
+ name: 'LobeChat_ServerConfig' + (isDev ? '_DEV' : ''),
42
+ }),
43
+ shallow,
44
+ );
45
+
46
+ export const createServerConfigStore = (initState?: Partial<ServerConfigStore>) => {
47
+ // make sure there is only one store
48
+ if (!store) {
49
+ store = createWithEqualityFn<ServerConfigStore>()(
50
+ devtools(createStore(initState || {}), {
51
+ name: 'LobeChat_ServerConfig' + (isDev ? '_DEV' : ''),
52
+ }),
53
+ shallow,
54
+ );
55
+ }
56
+
57
+ return store;
58
+ };
59
+
60
+ export const { useStore: useServerConfigStore, Provider } =
61
+ createContext<StoreApiWithSelector<ServerConfigStore>>();
@@ -1,18 +0,0 @@
1
- 'use client';
2
-
3
- import { ReactNode, memo } from 'react';
4
-
5
- import { IFeatureFlags } from '@/config/featureFlags';
6
-
7
- import { Provider, createFeatureFlagsStore } from './store';
8
-
9
- interface GlobalStoreProviderProps {
10
- children: ReactNode;
11
- featureFlags?: Partial<IFeatureFlags>;
12
- }
13
-
14
- export const FeatureFlagStoreProvider = memo<GlobalStoreProviderProps>(
15
- ({ children, featureFlags }) => (
16
- <Provider createStore={() => createFeatureFlagsStore(featureFlags)}>{children}</Provider>
17
- ),
18
- );
@@ -1,3 +0,0 @@
1
- export { FeatureFlagStoreProvider } from './Provider';
2
- export { featureFlagsSelectors } from './selectors';
3
- export { useFeatureFlagStore } from './store';
@@ -1,5 +0,0 @@
1
- import { mapFeatureFlagsEnvToState } from '@/config/featureFlags';
2
-
3
- import { FeatureFlagStore } from './store';
4
-
5
- export const featureFlagsSelectors = (s: FeatureFlagStore) => mapFeatureFlagsEnvToState(s);
@@ -1,42 +0,0 @@
1
- import { StoreApi } from 'zustand';
2
- import { createContext } from 'zustand-utils';
3
- import { devtools } from 'zustand/middleware';
4
- import { shallow } from 'zustand/shallow';
5
- import { createWithEqualityFn } from 'zustand/traditional';
6
- import { StateCreator } from 'zustand/vanilla';
7
-
8
- import { DEFAULT_FEATURE_FLAGS, IFeatureFlags } from '@/config/featureFlags';
9
- import { isDev } from '@/utils/env';
10
- import { StoreApiWithSelector } from '@/utils/zustand';
11
-
12
- // =============== 聚合 createStoreFn ============ //
13
-
14
- export type FeatureFlagStore = IFeatureFlags;
15
-
16
- const createStore: (
17
- initState: Partial<FeatureFlagStore>,
18
- ) => StateCreator<FeatureFlagStore, [['zustand/devtools', never]]> = (runtimeState) => () => ({
19
- ...DEFAULT_FEATURE_FLAGS,
20
- ...runtimeState,
21
- });
22
-
23
- // =============== 实装 useStore ============ //
24
-
25
- let store: StoreApi<FeatureFlagStore>;
26
-
27
- export const createFeatureFlagsStore = (initState?: Partial<FeatureFlagStore>) => {
28
- // make sure there is only one store
29
- if (!store) {
30
- store = createWithEqualityFn<FeatureFlagStore>()(
31
- devtools(createStore(initState || {}), {
32
- name: 'LobeChat_FeatureFlags' + (isDev ? '_DEV' : ''),
33
- }),
34
- shallow,
35
- );
36
- }
37
-
38
- return store;
39
- };
40
-
41
- export const { useStore: useFeatureFlagStore, Provider } =
42
- createContext<StoreApiWithSelector<FeatureFlagStore>>();