useSidebar.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. /*
  2. Copyright (C) 2025 QuantumNous
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <https://www.gnu.org/licenses/>.
  13. For commercial licensing, please contact support@quantumnous.com
  14. */
  15. import { useState, useEffect, useMemo, useContext, useRef } from 'react';
  16. import { StatusContext } from '../../context/Status';
  17. import { API } from '../../helpers';
  18. // 创建一个全局事件系统来同步所有useSidebar实例
  19. const sidebarEventTarget = new EventTarget();
  20. const SIDEBAR_REFRESH_EVENT = 'sidebar-refresh';
  21. export const useSidebar = () => {
  22. const [statusState] = useContext(StatusContext);
  23. const [userConfig, setUserConfig] = useState(null);
  24. const [loading, setLoading] = useState(true);
  25. const instanceIdRef = useRef(null);
  26. const hasLoadedOnceRef = useRef(false);
  27. if (!instanceIdRef.current) {
  28. const randomPart = Math.random().toString(16).slice(2);
  29. instanceIdRef.current = `sidebar-${Date.now()}-${randomPart}`;
  30. }
  31. // 默认配置
  32. const defaultAdminConfig = {
  33. chat: {
  34. enabled: true,
  35. playground: true,
  36. chat: true,
  37. },
  38. console: {
  39. enabled: true,
  40. detail: true,
  41. token: true,
  42. log: true,
  43. midjourney: true,
  44. task: true,
  45. },
  46. personal: {
  47. enabled: true,
  48. topup: true,
  49. personal: true,
  50. },
  51. admin: {
  52. enabled: true,
  53. channel: true,
  54. models: true,
  55. deployment: true,
  56. redemption: true,
  57. user: true,
  58. setting: true,
  59. },
  60. };
  61. // 获取管理员配置
  62. const adminConfig = useMemo(() => {
  63. if (statusState?.status?.SidebarModulesAdmin) {
  64. try {
  65. const config = JSON.parse(statusState.status.SidebarModulesAdmin);
  66. return config;
  67. } catch (error) {
  68. return defaultAdminConfig;
  69. }
  70. }
  71. return defaultAdminConfig;
  72. }, [statusState?.status?.SidebarModulesAdmin]);
  73. // 加载用户配置的通用方法
  74. const loadUserConfig = async ({ withLoading } = {}) => {
  75. const shouldShowLoader =
  76. typeof withLoading === 'boolean'
  77. ? withLoading
  78. : !hasLoadedOnceRef.current;
  79. try {
  80. if (shouldShowLoader) {
  81. setLoading(true);
  82. }
  83. const res = await API.get('/api/user/self');
  84. if (res.data.success && res.data.data.sidebar_modules) {
  85. let config;
  86. // 检查sidebar_modules是字符串还是对象
  87. if (typeof res.data.data.sidebar_modules === 'string') {
  88. config = JSON.parse(res.data.data.sidebar_modules);
  89. } else {
  90. config = res.data.data.sidebar_modules;
  91. }
  92. setUserConfig(config);
  93. } else {
  94. // 当用户没有配置时,生成一个基于管理员配置的默认用户配置
  95. // 这样可以确保权限控制正确生效
  96. const defaultUserConfig = {};
  97. Object.keys(adminConfig).forEach((sectionKey) => {
  98. if (adminConfig[sectionKey]?.enabled) {
  99. defaultUserConfig[sectionKey] = { enabled: true };
  100. // 为每个管理员允许的模块设置默认值为true
  101. Object.keys(adminConfig[sectionKey]).forEach((moduleKey) => {
  102. if (
  103. moduleKey !== 'enabled' &&
  104. adminConfig[sectionKey][moduleKey]
  105. ) {
  106. defaultUserConfig[sectionKey][moduleKey] = true;
  107. }
  108. });
  109. }
  110. });
  111. setUserConfig(defaultUserConfig);
  112. }
  113. } catch (error) {
  114. // 出错时也生成默认配置,而不是设置为空对象
  115. const defaultUserConfig = {};
  116. Object.keys(adminConfig).forEach((sectionKey) => {
  117. if (adminConfig[sectionKey]?.enabled) {
  118. defaultUserConfig[sectionKey] = { enabled: true };
  119. Object.keys(adminConfig[sectionKey]).forEach((moduleKey) => {
  120. if (moduleKey !== 'enabled' && adminConfig[sectionKey][moduleKey]) {
  121. defaultUserConfig[sectionKey][moduleKey] = true;
  122. }
  123. });
  124. }
  125. });
  126. setUserConfig(defaultUserConfig);
  127. } finally {
  128. if (shouldShowLoader) {
  129. setLoading(false);
  130. }
  131. hasLoadedOnceRef.current = true;
  132. }
  133. };
  134. // 刷新用户配置的方法(供外部调用)
  135. const refreshUserConfig = async () => {
  136. if (Object.keys(adminConfig).length > 0) {
  137. await loadUserConfig({ withLoading: false });
  138. }
  139. // 触发全局刷新事件,通知所有useSidebar实例更新
  140. sidebarEventTarget.dispatchEvent(
  141. new CustomEvent(SIDEBAR_REFRESH_EVENT, {
  142. detail: { sourceId: instanceIdRef.current, skipLoader: true },
  143. }),
  144. );
  145. };
  146. // 加载用户配置
  147. useEffect(() => {
  148. // 只有当管理员配置加载完成后才加载用户配置
  149. if (Object.keys(adminConfig).length > 0) {
  150. loadUserConfig();
  151. }
  152. }, [adminConfig]);
  153. // 监听全局刷新事件
  154. useEffect(() => {
  155. const handleRefresh = (event) => {
  156. if (event?.detail?.sourceId === instanceIdRef.current) {
  157. return;
  158. }
  159. if (Object.keys(adminConfig).length > 0) {
  160. loadUserConfig({
  161. withLoading: event?.detail?.skipLoader ? false : undefined,
  162. });
  163. }
  164. };
  165. sidebarEventTarget.addEventListener(SIDEBAR_REFRESH_EVENT, handleRefresh);
  166. return () => {
  167. sidebarEventTarget.removeEventListener(
  168. SIDEBAR_REFRESH_EVENT,
  169. handleRefresh,
  170. );
  171. };
  172. }, [adminConfig]);
  173. // 计算最终的显示配置
  174. const finalConfig = useMemo(() => {
  175. const result = {};
  176. // 确保adminConfig已加载
  177. if (!adminConfig || Object.keys(adminConfig).length === 0) {
  178. return result;
  179. }
  180. // 如果userConfig未加载,等待加载完成
  181. if (!userConfig) {
  182. return result;
  183. }
  184. // 遍历所有区域
  185. Object.keys(adminConfig).forEach((sectionKey) => {
  186. const adminSection = adminConfig[sectionKey];
  187. const userSection = userConfig[sectionKey];
  188. // 如果管理员禁用了整个区域,则该区域不显示
  189. if (!adminSection?.enabled) {
  190. result[sectionKey] = { enabled: false };
  191. return;
  192. }
  193. // 区域级别:用户可以选择隐藏管理员允许的区域
  194. // 当userSection存在时检查enabled状态,否则默认为true
  195. const sectionEnabled = userSection ? userSection.enabled !== false : true;
  196. result[sectionKey] = { enabled: sectionEnabled };
  197. // 功能级别:只有管理员和用户都允许的功能才显示
  198. Object.keys(adminSection).forEach((moduleKey) => {
  199. if (moduleKey === 'enabled') return;
  200. const adminAllowed = adminSection[moduleKey];
  201. // 当userSection存在时检查模块状态,否则默认为true
  202. const userAllowed = userSection
  203. ? userSection[moduleKey] !== false
  204. : true;
  205. result[sectionKey][moduleKey] =
  206. adminAllowed && userAllowed && sectionEnabled;
  207. });
  208. });
  209. return result;
  210. }, [adminConfig, userConfig]);
  211. // 检查特定功能是否应该显示
  212. const isModuleVisible = (sectionKey, moduleKey = null) => {
  213. if (moduleKey) {
  214. return finalConfig[sectionKey]?.[moduleKey] === true;
  215. } else {
  216. return finalConfig[sectionKey]?.enabled === true;
  217. }
  218. };
  219. // 检查区域是否有任何可见的功能
  220. const hasSectionVisibleModules = (sectionKey) => {
  221. const section = finalConfig[sectionKey];
  222. if (!section?.enabled) return false;
  223. return Object.keys(section).some(
  224. (key) => key !== 'enabled' && section[key] === true,
  225. );
  226. };
  227. // 获取区域的可见功能列表
  228. const getVisibleModules = (sectionKey) => {
  229. const section = finalConfig[sectionKey];
  230. if (!section?.enabled) return [];
  231. return Object.keys(section).filter(
  232. (key) => key !== 'enabled' && section[key] === true,
  233. );
  234. };
  235. return {
  236. loading,
  237. adminConfig,
  238. userConfig,
  239. finalConfig,
  240. isModuleVisible,
  241. hasSectionVisibleModules,
  242. getVisibleModules,
  243. refreshUserConfig,
  244. };
  245. };