ChannelsActions.jsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  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 from 'react';
  16. import {
  17. Button,
  18. Dropdown,
  19. Modal,
  20. Switch,
  21. Typography,
  22. Select,
  23. } from '@douyinfe/semi-ui';
  24. import CompactModeToggle from '../../common/ui/CompactModeToggle';
  25. const ChannelsActions = ({
  26. enableBatchDelete,
  27. batchDeleteChannels,
  28. setShowBatchSetTag,
  29. testAllChannels,
  30. fixChannelsAbilities,
  31. updateAllChannelsBalance,
  32. deleteAllDisabledChannels,
  33. applyAllUpstreamUpdates,
  34. detectAllUpstreamUpdates,
  35. detectAllUpstreamUpdatesLoading,
  36. applyAllUpstreamUpdatesLoading,
  37. compactMode,
  38. setCompactMode,
  39. idSort,
  40. setIdSort,
  41. setEnableBatchDelete,
  42. enableTagMode,
  43. setEnableTagMode,
  44. statusFilter,
  45. setStatusFilter,
  46. getFormValues,
  47. loadChannels,
  48. searchChannels,
  49. activeTypeKey,
  50. activePage,
  51. pageSize,
  52. setActivePage,
  53. t,
  54. }) => {
  55. return (
  56. <div className='flex flex-col gap-2'>
  57. {/* 第一行:批量操作按钮 + 设置开关 */}
  58. <div className='flex flex-col md:flex-row justify-between gap-2'>
  59. {/* 左侧:批量操作按钮 */}
  60. <div className='flex flex-wrap md:flex-nowrap items-center gap-2 w-full md:w-auto order-2 md:order-1'>
  61. <Button
  62. size='small'
  63. disabled={!enableBatchDelete}
  64. type='danger'
  65. className='w-full md:w-auto'
  66. onClick={() => {
  67. Modal.confirm({
  68. title: t('确定是否要删除所选通道?'),
  69. content: t('此修改将不可逆'),
  70. onOk: () => batchDeleteChannels(),
  71. });
  72. }}
  73. >
  74. {t('删除所选通道')}
  75. </Button>
  76. <Button
  77. size='small'
  78. disabled={!enableBatchDelete}
  79. type='tertiary'
  80. onClick={() => setShowBatchSetTag(true)}
  81. className='w-full md:w-auto'
  82. >
  83. {t('批量设置标签')}
  84. </Button>
  85. <Dropdown
  86. size='small'
  87. trigger='click'
  88. render={
  89. <Dropdown.Menu>
  90. <Dropdown.Item>
  91. <Button
  92. size='small'
  93. type='tertiary'
  94. className='w-full'
  95. loading={detectAllUpstreamUpdatesLoading}
  96. disabled={detectAllUpstreamUpdatesLoading}
  97. onClick={() => {
  98. Modal.confirm({
  99. title: t('确定?'),
  100. content: t('确定要测试所有未手动禁用渠道吗?'),
  101. onOk: () => testAllChannels(),
  102. size: 'small',
  103. centered: true,
  104. });
  105. }}
  106. >
  107. {t('测试所有未手动禁用渠道')}
  108. </Button>
  109. </Dropdown.Item>
  110. <Dropdown.Item>
  111. <Button
  112. size='small'
  113. className='w-full'
  114. onClick={() => {
  115. Modal.confirm({
  116. title: t('确定是否要修复数据库一致性?'),
  117. content: t(
  118. '进行该操作时,可能导致渠道访问错误,请仅在数据库出现问题时使用',
  119. ),
  120. onOk: () => fixChannelsAbilities(),
  121. size: 'sm',
  122. centered: true,
  123. });
  124. }}
  125. >
  126. {t('修复数据库一致性')}
  127. </Button>
  128. </Dropdown.Item>
  129. <Dropdown.Item>
  130. <Button
  131. size='small'
  132. type='secondary'
  133. className='w-full'
  134. onClick={() => {
  135. Modal.confirm({
  136. title: t('确定?'),
  137. content: t('确定要更新所有已启用通道余额吗?'),
  138. onOk: () => updateAllChannelsBalance(),
  139. size: 'sm',
  140. centered: true,
  141. });
  142. }}
  143. >
  144. {t('更新所有已启用通道余额')}
  145. </Button>
  146. </Dropdown.Item>
  147. <Dropdown.Item>
  148. <Button
  149. size='small'
  150. type='tertiary'
  151. className='w-full'
  152. onClick={() => {
  153. Modal.confirm({
  154. title: t('确定?'),
  155. content: t(
  156. '确定要仅检测全部渠道上游模型更新吗?(不执行新增/删除)',
  157. ),
  158. onOk: () => detectAllUpstreamUpdates(),
  159. size: 'sm',
  160. centered: true,
  161. });
  162. }}
  163. >
  164. {t('检测全部渠道上游更新')}
  165. </Button>
  166. </Dropdown.Item>
  167. <Dropdown.Item>
  168. <Button
  169. size='small'
  170. type='primary'
  171. className='w-full'
  172. loading={applyAllUpstreamUpdatesLoading}
  173. disabled={applyAllUpstreamUpdatesLoading}
  174. onClick={() => {
  175. Modal.confirm({
  176. title: t('确定?'),
  177. content: t('确定要对全部渠道执行上游模型更新吗?'),
  178. onOk: () => applyAllUpstreamUpdates(),
  179. size: 'sm',
  180. centered: true,
  181. });
  182. }}
  183. >
  184. {t('处理全部渠道上游更新')}
  185. </Button>
  186. </Dropdown.Item>
  187. <Dropdown.Item>
  188. <Button
  189. size='small'
  190. type='danger'
  191. className='w-full'
  192. onClick={() => {
  193. Modal.confirm({
  194. title: t('确定是否要删除禁用通道?'),
  195. content: t('此修改将不可逆'),
  196. onOk: () => deleteAllDisabledChannels(),
  197. size: 'sm',
  198. centered: true,
  199. });
  200. }}
  201. >
  202. {t('删除禁用通道')}
  203. </Button>
  204. </Dropdown.Item>
  205. </Dropdown.Menu>
  206. }
  207. >
  208. <Button
  209. size='small'
  210. theme='light'
  211. type='tertiary'
  212. className='w-full md:w-auto'
  213. >
  214. {t('批量操作')}
  215. </Button>
  216. </Dropdown>
  217. <CompactModeToggle
  218. compactMode={compactMode}
  219. setCompactMode={setCompactMode}
  220. t={t}
  221. />
  222. </div>
  223. {/* 右侧:设置开关区域 */}
  224. <div className='flex flex-col md:flex-row items-start md:items-center gap-2 w-full md:w-auto order-1 md:order-2'>
  225. <div className='flex items-center justify-between w-full md:w-auto'>
  226. <Typography.Text strong className='mr-2'>
  227. {t('使用ID排序')}
  228. </Typography.Text>
  229. <Switch
  230. size='small'
  231. checked={idSort}
  232. onChange={(v) => {
  233. localStorage.setItem('id-sort', v + '');
  234. setIdSort(v);
  235. const { searchKeyword, searchGroup, searchModel } =
  236. getFormValues();
  237. if (
  238. searchKeyword === '' &&
  239. searchGroup === '' &&
  240. searchModel === ''
  241. ) {
  242. loadChannels(activePage, pageSize, v, enableTagMode);
  243. } else {
  244. searchChannels(
  245. enableTagMode,
  246. activeTypeKey,
  247. statusFilter,
  248. activePage,
  249. pageSize,
  250. v,
  251. );
  252. }
  253. }}
  254. />
  255. </div>
  256. <div className='flex items-center justify-between w-full md:w-auto'>
  257. <Typography.Text strong className='mr-2'>
  258. {t('开启批量操作')}
  259. </Typography.Text>
  260. <Switch
  261. size='small'
  262. checked={enableBatchDelete}
  263. onChange={(v) => {
  264. localStorage.setItem('enable-batch-delete', v + '');
  265. setEnableBatchDelete(v);
  266. }}
  267. />
  268. </div>
  269. <div className='flex items-center justify-between w-full md:w-auto'>
  270. <Typography.Text strong className='mr-2'>
  271. {t('标签聚合模式')}
  272. </Typography.Text>
  273. <Switch
  274. size='small'
  275. checked={enableTagMode}
  276. onChange={(v) => {
  277. localStorage.setItem('enable-tag-mode', v + '');
  278. setEnableTagMode(v);
  279. setActivePage(1);
  280. loadChannels(1, pageSize, idSort, v);
  281. }}
  282. />
  283. </div>
  284. <div className='flex items-center justify-between w-full md:w-auto'>
  285. <Typography.Text strong className='mr-2'>
  286. {t('状态筛选')}
  287. </Typography.Text>
  288. <Select
  289. size='small'
  290. value={statusFilter}
  291. onChange={(v) => {
  292. localStorage.setItem('channel-status-filter', v);
  293. setStatusFilter(v);
  294. setActivePage(1);
  295. loadChannels(
  296. 1,
  297. pageSize,
  298. idSort,
  299. enableTagMode,
  300. activeTypeKey,
  301. v,
  302. );
  303. }}
  304. >
  305. <Select.Option value='all'>{t('全部')}</Select.Option>
  306. <Select.Option value='enabled'>{t('已启用')}</Select.Option>
  307. <Select.Option value='disabled'>{t('已禁用')}</Select.Option>
  308. </Select>
  309. </div>
  310. </div>
  311. </div>
  312. </div>
  313. );
  314. };
  315. export default ChannelsActions;