useChannelUpstreamUpdates.jsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  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 { useRef, useState } from 'react';
  16. import { API, showError, showInfo, showSuccess } from '../../helpers';
  17. import { normalizeModelList } from './upstreamUpdateUtils';
  18. const getManualIgnoredModelCountFromSettings = (settings) => {
  19. let parsed = null;
  20. if (settings && typeof settings === 'object') {
  21. parsed = settings;
  22. } else if (typeof settings === 'string') {
  23. try {
  24. parsed = JSON.parse(settings);
  25. } catch (error) {
  26. parsed = null;
  27. }
  28. }
  29. if (!parsed || typeof parsed !== 'object') {
  30. return 0;
  31. }
  32. return normalizeModelList(parsed.upstream_model_update_ignored_models).length;
  33. };
  34. export const useChannelUpstreamUpdates = ({ t, refresh }) => {
  35. const [showUpstreamUpdateModal, setShowUpstreamUpdateModal] = useState(false);
  36. const [upstreamUpdateChannel, setUpstreamUpdateChannel] = useState(null);
  37. const [upstreamUpdateAddModels, setUpstreamUpdateAddModels] = useState([]);
  38. const [upstreamUpdateRemoveModels, setUpstreamUpdateRemoveModels] = useState(
  39. [],
  40. );
  41. const [upstreamUpdatePreferredTab, setUpstreamUpdatePreferredTab] =
  42. useState('add');
  43. const [upstreamApplyLoading, setUpstreamApplyLoading] = useState(false);
  44. const [detectAllUpstreamUpdatesLoading, setDetectAllUpstreamUpdatesLoading] =
  45. useState(false);
  46. const [applyAllUpstreamUpdatesLoading, setApplyAllUpstreamUpdatesLoading] =
  47. useState(false);
  48. const applyUpstreamUpdatesInFlightRef = useRef(false);
  49. const detectChannelUpstreamUpdatesInFlightRef = useRef(false);
  50. const detectAllUpstreamUpdatesInFlightRef = useRef(false);
  51. const applyAllUpstreamUpdatesInFlightRef = useRef(false);
  52. const openUpstreamUpdateModal = (
  53. record,
  54. pendingAddModels = [],
  55. pendingRemoveModels = [],
  56. preferredTab = 'add',
  57. ) => {
  58. const normalizedAddModels = normalizeModelList(pendingAddModels);
  59. const normalizedRemoveModels = normalizeModelList(pendingRemoveModels);
  60. if (
  61. !record?.id ||
  62. (normalizedAddModels.length === 0 && normalizedRemoveModels.length === 0)
  63. ) {
  64. showInfo(t('该渠道暂无可处理的上游模型更新'));
  65. return;
  66. }
  67. setUpstreamUpdateChannel(record);
  68. setUpstreamUpdateAddModels(normalizedAddModels);
  69. setUpstreamUpdateRemoveModels(normalizedRemoveModels);
  70. const normalizedPreferredTab = preferredTab === 'remove' ? 'remove' : 'add';
  71. setUpstreamUpdatePreferredTab(normalizedPreferredTab);
  72. setShowUpstreamUpdateModal(true);
  73. };
  74. const closeUpstreamUpdateModal = () => {
  75. setShowUpstreamUpdateModal(false);
  76. setUpstreamUpdateChannel(null);
  77. setUpstreamUpdateAddModels([]);
  78. setUpstreamUpdateRemoveModels([]);
  79. setUpstreamUpdatePreferredTab('add');
  80. };
  81. const applyUpstreamUpdates = async ({
  82. addModels: selectedAddModels = [],
  83. removeModels: selectedRemoveModels = [],
  84. } = {}) => {
  85. if (applyUpstreamUpdatesInFlightRef.current) {
  86. showInfo(t('正在处理,请稍候'));
  87. return;
  88. }
  89. if (!upstreamUpdateChannel?.id) {
  90. closeUpstreamUpdateModal();
  91. return;
  92. }
  93. applyUpstreamUpdatesInFlightRef.current = true;
  94. setUpstreamApplyLoading(true);
  95. try {
  96. const normalizedSelectedAddModels = normalizeModelList(selectedAddModels);
  97. const normalizedSelectedRemoveModels =
  98. normalizeModelList(selectedRemoveModels);
  99. const selectedAddSet = new Set(normalizedSelectedAddModels);
  100. const ignoreModels = upstreamUpdateAddModels.filter(
  101. (model) => !selectedAddSet.has(model),
  102. );
  103. const res = await API.post(
  104. '/api/channel/upstream_updates/apply',
  105. {
  106. id: upstreamUpdateChannel.id,
  107. add_models: normalizedSelectedAddModels,
  108. ignore_models: ignoreModels,
  109. remove_models: normalizedSelectedRemoveModels,
  110. },
  111. { skipErrorHandler: true },
  112. );
  113. const { success, message, data } = res.data || {};
  114. if (!success) {
  115. showError(message || t('操作失败'));
  116. return;
  117. }
  118. const addedCount = data?.added_models?.length || 0;
  119. const removedCount = data?.removed_models?.length || 0;
  120. const totalIgnoredCount = getManualIgnoredModelCountFromSettings(
  121. data?.settings,
  122. );
  123. const ignoredCount = normalizeModelList(ignoreModels).length;
  124. showSuccess(
  125. t(
  126. '已处理上游模型更新:加入 {{added}} 个,删除 {{removed}} 个,本次忽略 {{ignored}} 个,当前已忽略模型 {{totalIgnored}} 个',
  127. {
  128. added: addedCount,
  129. removed: removedCount,
  130. ignored: ignoredCount,
  131. totalIgnored: totalIgnoredCount,
  132. },
  133. ),
  134. );
  135. closeUpstreamUpdateModal();
  136. await refresh();
  137. } catch (error) {
  138. showError(
  139. error?.response?.data?.message || error?.message || t('操作失败'),
  140. );
  141. } finally {
  142. applyUpstreamUpdatesInFlightRef.current = false;
  143. setUpstreamApplyLoading(false);
  144. }
  145. };
  146. const applyAllUpstreamUpdates = async () => {
  147. if (applyAllUpstreamUpdatesInFlightRef.current) {
  148. showInfo(t('正在批量处理,请稍候'));
  149. return;
  150. }
  151. applyAllUpstreamUpdatesInFlightRef.current = true;
  152. setApplyAllUpstreamUpdatesLoading(true);
  153. try {
  154. const res = await API.post(
  155. '/api/channel/upstream_updates/apply_all',
  156. {},
  157. { skipErrorHandler: true },
  158. );
  159. const { success, message, data } = res.data || {};
  160. if (!success) {
  161. showError(message || t('批量处理失败'));
  162. return;
  163. }
  164. const channelCount = data?.processed_channels || 0;
  165. const addedCount = data?.added_models || 0;
  166. const removedCount = data?.removed_models || 0;
  167. const failedCount = (data?.failed_channel_ids || []).length;
  168. showSuccess(
  169. t(
  170. '已批量处理上游模型更新:渠道 {{channels}} 个,加入 {{added}} 个,删除 {{removed}} 个,失败 {{fails}} 个',
  171. {
  172. channels: channelCount,
  173. added: addedCount,
  174. removed: removedCount,
  175. fails: failedCount,
  176. },
  177. ),
  178. );
  179. await refresh();
  180. } catch (error) {
  181. showError(
  182. error?.response?.data?.message || error?.message || t('批量处理失败'),
  183. );
  184. } finally {
  185. applyAllUpstreamUpdatesInFlightRef.current = false;
  186. setApplyAllUpstreamUpdatesLoading(false);
  187. }
  188. };
  189. const detectChannelUpstreamUpdates = async (channel) => {
  190. if (detectChannelUpstreamUpdatesInFlightRef.current) {
  191. showInfo(t('正在检测,请稍候'));
  192. return;
  193. }
  194. if (!channel?.id) {
  195. return;
  196. }
  197. detectChannelUpstreamUpdatesInFlightRef.current = true;
  198. try {
  199. const res = await API.post(
  200. '/api/channel/upstream_updates/detect',
  201. {
  202. id: channel.id,
  203. },
  204. { skipErrorHandler: true },
  205. );
  206. const { success, message, data } = res.data || {};
  207. if (!success) {
  208. showError(message || t('检测失败'));
  209. return;
  210. }
  211. const addCount = data?.add_models?.length || 0;
  212. const removeCount = data?.remove_models?.length || 0;
  213. showSuccess(
  214. t('检测完成:新增 {{add}} 个,删除 {{remove}} 个', {
  215. add: addCount,
  216. remove: removeCount,
  217. }),
  218. );
  219. await refresh();
  220. } catch (error) {
  221. showError(
  222. error?.response?.data?.message || error?.message || t('检测失败'),
  223. );
  224. } finally {
  225. detectChannelUpstreamUpdatesInFlightRef.current = false;
  226. }
  227. };
  228. const detectAllUpstreamUpdates = async () => {
  229. if (detectAllUpstreamUpdatesInFlightRef.current) {
  230. showInfo(t('正在批量检测,请稍候'));
  231. return;
  232. }
  233. detectAllUpstreamUpdatesInFlightRef.current = true;
  234. setDetectAllUpstreamUpdatesLoading(true);
  235. try {
  236. const res = await API.post(
  237. '/api/channel/upstream_updates/detect_all',
  238. {},
  239. { skipErrorHandler: true },
  240. );
  241. const { success, message, data } = res.data || {};
  242. if (!success) {
  243. showError(message || t('批量检测失败'));
  244. return;
  245. }
  246. const channelCount = data?.processed_channels || 0;
  247. const addCount = data?.detected_add_models || 0;
  248. const removeCount = data?.detected_remove_models || 0;
  249. const failedCount = (data?.failed_channel_ids || []).length;
  250. showSuccess(
  251. t(
  252. '批量检测完成:渠道 {{channels}} 个,新增 {{add}} 个,删除 {{remove}} 个,失败 {{fails}} 个',
  253. {
  254. channels: channelCount,
  255. add: addCount,
  256. remove: removeCount,
  257. fails: failedCount,
  258. },
  259. ),
  260. );
  261. await refresh();
  262. } catch (error) {
  263. showError(
  264. error?.response?.data?.message || error?.message || t('批量检测失败'),
  265. );
  266. } finally {
  267. detectAllUpstreamUpdatesInFlightRef.current = false;
  268. setDetectAllUpstreamUpdatesLoading(false);
  269. }
  270. };
  271. return {
  272. showUpstreamUpdateModal,
  273. setShowUpstreamUpdateModal,
  274. upstreamUpdateChannel,
  275. upstreamUpdateAddModels,
  276. upstreamUpdateRemoveModels,
  277. upstreamUpdatePreferredTab,
  278. upstreamApplyLoading,
  279. detectAllUpstreamUpdatesLoading,
  280. applyAllUpstreamUpdatesLoading,
  281. openUpstreamUpdateModal,
  282. closeUpstreamUpdateModal,
  283. applyUpstreamUpdates,
  284. applyAllUpstreamUpdates,
  285. detectChannelUpstreamUpdates,
  286. detectAllUpstreamUpdates,
  287. };
  288. };