You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

447 lines
13 KiB

  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 React, { useState, useEffect, useContext } from 'react';
  16. import { useTranslation } from 'react-i18next';
  17. import {
  18. Card,
  19. Form,
  20. Button,
  21. Switch,
  22. Row,
  23. Col,
  24. Typography,
  25. } from '@douyinfe/semi-ui';
  26. import { API, showSuccess, showError } from '../../../helpers';
  27. import { StatusContext } from '../../../context/Status';
  28. const { Text } = Typography;
  29. export default function SettingsSidebarModulesAdmin(props) {
  30. const { t } = useTranslation();
  31. const [loading, setLoading] = useState(false);
  32. const [statusState, statusDispatch] = useContext(StatusContext);
  33. // 左侧边栏模块管理状态(管理员全局控制)
  34. const [sidebarModulesAdmin, setSidebarModulesAdmin] = useState({
  35. chat: {
  36. enabled: true,
  37. playground: true,
  38. chat: true,
  39. },
  40. console: {
  41. enabled: true,
  42. detail: true,
  43. token: true,
  44. log: true,
  45. midjourney: true,
  46. task: true,
  47. },
  48. personal: {
  49. enabled: true,
  50. topup: true,
  51. personal: true,
  52. },
  53. admin: {
  54. enabled: true,
  55. channel: true,
  56. models: true,
  57. deployment: true,
  58. redemption: true,
  59. user: true,
  60. subscription: true,
  61. setting: true,
  62. monitoring: true,
  63. },
  64. });
  65. // 处理区域级别开关变更
  66. function handleSectionChange(sectionKey) {
  67. return (checked) => {
  68. const newModules = {
  69. ...sidebarModulesAdmin,
  70. [sectionKey]: {
  71. ...sidebarModulesAdmin[sectionKey],
  72. enabled: checked,
  73. },
  74. };
  75. setSidebarModulesAdmin(newModules);
  76. };
  77. }
  78. // 处理功能级别开关变更
  79. function handleModuleChange(sectionKey, moduleKey) {
  80. return (checked) => {
  81. const newModules = {
  82. ...sidebarModulesAdmin,
  83. [sectionKey]: {
  84. ...sidebarModulesAdmin[sectionKey],
  85. [moduleKey]: checked,
  86. },
  87. };
  88. setSidebarModulesAdmin(newModules);
  89. };
  90. }
  91. // 重置为默认配置
  92. function resetSidebarModules() {
  93. const defaultModules = {
  94. chat: {
  95. enabled: true,
  96. playground: true,
  97. chat: true,
  98. },
  99. console: {
  100. enabled: true,
  101. detail: true,
  102. token: true,
  103. log: true,
  104. midjourney: true,
  105. task: true,
  106. },
  107. personal: {
  108. enabled: true,
  109. topup: true,
  110. personal: true,
  111. },
  112. admin: {
  113. enabled: true,
  114. channel: true,
  115. models: true,
  116. deployment: true,
  117. redemption: true,
  118. user: true,
  119. subscription: true,
  120. setting: true,
  121. monitoring: true,
  122. },
  123. };
  124. setSidebarModulesAdmin(defaultModules);
  125. showSuccess(t('已重置为默认配置'));
  126. }
  127. // 保存配置
  128. async function onSubmit() {
  129. setLoading(true);
  130. try {
  131. const res = await API.put('/api/option/', {
  132. key: 'SidebarModulesAdmin',
  133. value: JSON.stringify(sidebarModulesAdmin),
  134. });
  135. const { success, message } = res.data;
  136. if (success) {
  137. showSuccess(t('保存成功'));
  138. // 立即更新StatusContext中的状态
  139. statusDispatch({
  140. type: 'set',
  141. payload: {
  142. ...statusState.status,
  143. SidebarModulesAdmin: JSON.stringify(sidebarModulesAdmin),
  144. },
  145. });
  146. // 刷新父组件状态
  147. if (props.refresh) {
  148. await props.refresh();
  149. }
  150. } else {
  151. showError(message);
  152. }
  153. } catch (error) {
  154. showError(t('保存失败,请重试'));
  155. } finally {
  156. setLoading(false);
  157. }
  158. }
  159. useEffect(() => {
  160. // 从 props.options 中获取配置
  161. if (props.options && props.options.SidebarModulesAdmin) {
  162. try {
  163. const modules = JSON.parse(props.options.SidebarModulesAdmin);
  164. setSidebarModulesAdmin(modules);
  165. } catch (error) {
  166. // 使用默认配置
  167. const defaultModules = {
  168. chat: { enabled: true, playground: true, chat: true },
  169. console: {
  170. enabled: true,
  171. detail: true,
  172. token: true,
  173. log: true,
  174. midjourney: true,
  175. task: true,
  176. },
  177. personal: { enabled: true, topup: true, personal: true },
  178. admin: {
  179. enabled: true,
  180. channel: true,
  181. models: true,
  182. deployment: true,
  183. redemption: true,
  184. user: true,
  185. subscription: true,
  186. setting: true,
  187. monitoring: true,
  188. },
  189. };
  190. setSidebarModulesAdmin(defaultModules);
  191. }
  192. }
  193. }, [props.options]);
  194. // 区域配置数据
  195. const sectionConfigs = [
  196. {
  197. key: 'chat',
  198. title: t('聊天区域'),
  199. description: t('操练场和聊天功能'),
  200. modules: [
  201. {
  202. key: 'playground',
  203. title: t('操练场'),
  204. description: t('AI模型测试环境'),
  205. },
  206. { key: 'chat', title: t('聊天'), description: t('聊天会话管理') },
  207. ],
  208. },
  209. {
  210. key: 'console',
  211. title: t('控制台区域'),
  212. description: t('数据管理和日志查看'),
  213. modules: [
  214. { key: 'detail', title: t('数据看板'), description: t('系统数据统计') },
  215. { key: 'token', title: t('令牌管理'), description: t('API令牌管理') },
  216. { key: 'log', title: t('使用日志'), description: t('API使用记录') },
  217. {
  218. key: 'midjourney',
  219. title: t('绘图日志'),
  220. description: t('绘图任务记录'),
  221. },
  222. { key: 'task', title: t('任务日志'), description: t('系统任务记录') },
  223. ],
  224. },
  225. {
  226. key: 'personal',
  227. title: t('个人中心区域'),
  228. description: t('用户个人功能'),
  229. modules: [
  230. { key: 'topup', title: t('钱包管理'), description: t('余额充值管理') },
  231. {
  232. key: 'personal',
  233. title: t('个人设置'),
  234. description: t('个人信息设置'),
  235. },
  236. ],
  237. },
  238. {
  239. key: 'admin',
  240. title: t('管理员区域'),
  241. description: t('系统管理功能'),
  242. modules: [
  243. { key: 'channel', title: t('渠道管理'), description: t('API渠道配置') },
  244. { key: 'models', title: t('模型管理'), description: t('AI模型配置') },
  245. {
  246. key: 'deployment',
  247. title: t('模型部署'),
  248. description: t('模型部署管理'),
  249. },
  250. {
  251. key: 'subscription',
  252. title: t('订阅管理'),
  253. description: t('订阅套餐管理'),
  254. },
  255. {
  256. key: 'redemption',
  257. title: t('兑换码管理'),
  258. description: t('兑换码生成管理'),
  259. },
  260. { key: 'user', title: t('用户管理'), description: t('用户账户管理') },
  261. {
  262. key: 'setting',
  263. title: t('系统设置'),
  264. description: t('系统参数配置'),
  265. },
  266. {
  267. key: 'monitoring',
  268. title: t('监控'),
  269. description: t('外部监控面板'),
  270. },
  271. ],
  272. },
  273. ];
  274. return (
  275. <Card>
  276. <Form.Section
  277. text={t('侧边栏管理(全局控制)')}
  278. extraText={t(
  279. '全局控制侧边栏区域和功能显示,管理员隐藏的功能用户无法启用',
  280. )}
  281. >
  282. {sectionConfigs.map((section) => (
  283. <div key={section.key} style={{ marginBottom: '32px' }}>
  284. {/* 区域标题和总开关 */}
  285. <div
  286. style={{
  287. display: 'flex',
  288. justifyContent: 'space-between',
  289. alignItems: 'center',
  290. marginBottom: '16px',
  291. padding: '12px 16px',
  292. backgroundColor: 'var(--semi-color-fill-0)',
  293. borderRadius: '8px',
  294. border: '1px solid var(--semi-color-border)',
  295. }}
  296. >
  297. <div>
  298. <div
  299. style={{
  300. fontWeight: '600',
  301. fontSize: '16px',
  302. color: 'var(--semi-color-text-0)',
  303. marginBottom: '4px',
  304. }}
  305. >
  306. {section.title}
  307. </div>
  308. <Text
  309. type='secondary'
  310. size='small'
  311. style={{
  312. fontSize: '12px',
  313. color: 'var(--semi-color-text-2)',
  314. lineHeight: '1.4',
  315. }}
  316. >
  317. {section.description}
  318. </Text>
  319. </div>
  320. <Switch
  321. checked={sidebarModulesAdmin[section.key]?.enabled}
  322. onChange={handleSectionChange(section.key)}
  323. size='default'
  324. />
  325. </div>
  326. {/* 功能模块网格 */}
  327. <Row gutter={[16, 16]}>
  328. {section.modules.map((module) => (
  329. <Col key={module.key} xs={24} sm={12} md={8} lg={6} xl={6}>
  330. <Card
  331. bodyStyle={{ padding: '16px' }}
  332. hoverable
  333. style={{
  334. opacity: sidebarModulesAdmin[section.key]?.enabled
  335. ? 1
  336. : 0.5,
  337. transition: 'opacity 0.2s',
  338. }}
  339. >
  340. <div
  341. style={{
  342. display: 'flex',
  343. justifyContent: 'space-between',
  344. alignItems: 'center',
  345. height: '100%',
  346. }}
  347. >
  348. <div style={{ flex: 1, textAlign: 'left' }}>
  349. <div
  350. style={{
  351. fontWeight: '600',
  352. fontSize: '14px',
  353. color: 'var(--semi-color-text-0)',
  354. marginBottom: '4px',
  355. }}
  356. >
  357. {module.title}
  358. </div>
  359. <Text
  360. type='secondary'
  361. size='small'
  362. style={{
  363. fontSize: '12px',
  364. color: 'var(--semi-color-text-2)',
  365. lineHeight: '1.4',
  366. display: 'block',
  367. }}
  368. >
  369. {module.description}
  370. </Text>
  371. </div>
  372. <div style={{ marginLeft: '16px' }}>
  373. <Switch
  374. checked={
  375. sidebarModulesAdmin[section.key]?.[module.key]
  376. }
  377. onChange={handleModuleChange(section.key, module.key)}
  378. size='default'
  379. disabled={!sidebarModulesAdmin[section.key]?.enabled}
  380. />
  381. </div>
  382. </div>
  383. </Card>
  384. </Col>
  385. ))}
  386. </Row>
  387. </div>
  388. ))}
  389. <div
  390. style={{
  391. display: 'flex',
  392. gap: '12px',
  393. justifyContent: 'flex-start',
  394. alignItems: 'center',
  395. paddingTop: '8px',
  396. borderTop: '1px solid var(--semi-color-border)',
  397. }}
  398. >
  399. <Button
  400. size='default'
  401. type='tertiary'
  402. onClick={resetSidebarModules}
  403. style={{
  404. borderRadius: '6px',
  405. fontWeight: '500',
  406. }}
  407. >
  408. {t('重置为默认')}
  409. </Button>
  410. <Button
  411. size='default'
  412. type='primary'
  413. onClick={onSubmit}
  414. loading={loading}
  415. style={{
  416. borderRadius: '6px',
  417. fontWeight: '500',
  418. minWidth: '100px',
  419. }}
  420. >
  421. {t('保存设置')}
  422. </Button>
  423. </div>
  424. </Form.Section>
  425. </Card>
  426. );
  427. }