index.jsx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  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 } from 'react';
  16. import { Banner, Button, Modal } from '@douyinfe/semi-ui';
  17. import { IconAlertTriangle, IconClose } from '@douyinfe/semi-icons';
  18. import CardPro from '../../common/ui/CardPro';
  19. import ModelsTable from './ModelsTable';
  20. import ModelsActions from './ModelsActions';
  21. import ModelsFilters from './ModelsFilters';
  22. import ModelsTabs from './ModelsTabs';
  23. import EditModelModal from './modals/EditModelModal';
  24. import EditVendorModal from './modals/EditVendorModal';
  25. import { useModelsData } from '../../../hooks/models/useModelsData';
  26. import { useIsMobile } from '../../../hooks/common/useIsMobile';
  27. import { createCardProPagination } from '../../../helpers/utils';
  28. const MARKETPLACE_DISPLAY_NOTICE_STORAGE_KEY =
  29. 'models_marketplace_display_notice_dismissed';
  30. const ModelsPage = () => {
  31. const modelsData = useModelsData();
  32. const isMobile = useIsMobile();
  33. const {
  34. // Edit state
  35. showEdit,
  36. editingModel,
  37. closeEdit,
  38. refresh,
  39. // Actions state
  40. selectedKeys,
  41. setSelectedKeys,
  42. setEditingModel,
  43. setShowEdit,
  44. batchDeleteModels,
  45. // Filters state
  46. formInitValues,
  47. setFormApi,
  48. searchModels,
  49. loading,
  50. searching,
  51. // Description state
  52. compactMode,
  53. setCompactMode,
  54. // Vendor state
  55. showAddVendor,
  56. setShowAddVendor,
  57. showEditVendor,
  58. setShowEditVendor,
  59. editingVendor,
  60. setEditingVendor,
  61. loadVendors,
  62. // Translation
  63. t,
  64. } = modelsData;
  65. const [showMarketplaceDisplayNotice, setShowMarketplaceDisplayNotice] =
  66. useState(() => {
  67. try {
  68. return (
  69. localStorage.getItem(MARKETPLACE_DISPLAY_NOTICE_STORAGE_KEY) !== '1'
  70. );
  71. } catch (_) {
  72. return true;
  73. }
  74. });
  75. const confirmCloseMarketplaceDisplayNotice = () => {
  76. Modal.confirm({
  77. title: t('确认关闭提示'),
  78. content: t(
  79. '关闭后将不再显示此提示(仅对当前浏览器生效)。确定要关闭吗?',
  80. ),
  81. okText: t('关闭提示'),
  82. cancelText: t('取消'),
  83. okButtonProps: {
  84. type: 'danger',
  85. },
  86. onOk: () => {
  87. try {
  88. localStorage.setItem(MARKETPLACE_DISPLAY_NOTICE_STORAGE_KEY, '1');
  89. } catch (_) {}
  90. setShowMarketplaceDisplayNotice(false);
  91. },
  92. });
  93. };
  94. return (
  95. <>
  96. <EditModelModal
  97. refresh={refresh}
  98. editingModel={editingModel}
  99. visiable={showEdit}
  100. handleClose={closeEdit}
  101. />
  102. <EditVendorModal
  103. visible={showAddVendor || showEditVendor}
  104. handleClose={() => {
  105. setShowAddVendor(false);
  106. setShowEditVendor(false);
  107. setEditingVendor({ id: undefined });
  108. }}
  109. editingVendor={showEditVendor ? editingVendor : { id: undefined }}
  110. refresh={() => {
  111. loadVendors();
  112. refresh();
  113. }}
  114. />
  115. {showMarketplaceDisplayNotice ? (
  116. <div style={{ position: 'relative', marginBottom: 12 }}>
  117. <Banner
  118. type='warning'
  119. closeIcon={null}
  120. icon={
  121. <IconAlertTriangle
  122. size='large'
  123. style={{ color: 'var(--semi-color-warning)' }}
  124. />
  125. }
  126. description={t(
  127. '提示:此处配置仅用于控制「模型广场」对用户的展示效果,不会影响模型的实际调用与路由。若需配置真实调用行为,请前往「渠道管理」进行设置。',
  128. )}
  129. style={{ marginBottom: 0 }}
  130. />
  131. <Button
  132. theme='borderless'
  133. size='small'
  134. type='tertiary'
  135. icon={<IconClose aria-hidden={true} />}
  136. onClick={confirmCloseMarketplaceDisplayNotice}
  137. style={{ position: 'absolute', top: 8, right: 8 }}
  138. aria-label={t('关闭')}
  139. />
  140. </div>
  141. ) : null}
  142. <CardPro
  143. type='type3'
  144. tabsArea={<ModelsTabs {...modelsData} />}
  145. actionsArea={
  146. <div className='flex flex-col md:flex-row justify-between items-center gap-2 w-full'>
  147. <ModelsActions
  148. selectedKeys={selectedKeys}
  149. setSelectedKeys={setSelectedKeys}
  150. setEditingModel={setEditingModel}
  151. setShowEdit={setShowEdit}
  152. batchDeleteModels={batchDeleteModels}
  153. syncing={modelsData.syncing}
  154. syncUpstream={modelsData.syncUpstream}
  155. previewing={modelsData.previewing}
  156. previewUpstreamDiff={modelsData.previewUpstreamDiff}
  157. applyUpstreamOverwrite={modelsData.applyUpstreamOverwrite}
  158. compactMode={compactMode}
  159. setCompactMode={setCompactMode}
  160. t={t}
  161. />
  162. <div className='w-full md:w-full lg:w-auto order-1 md:order-2'>
  163. <ModelsFilters
  164. formInitValues={formInitValues}
  165. setFormApi={setFormApi}
  166. searchModels={searchModels}
  167. loading={loading}
  168. searching={searching}
  169. t={t}
  170. />
  171. </div>
  172. </div>
  173. }
  174. paginationArea={createCardProPagination({
  175. currentPage: modelsData.activePage,
  176. pageSize: modelsData.pageSize,
  177. total: modelsData.modelCount,
  178. onPageChange: modelsData.handlePageChange,
  179. onPageSizeChange: modelsData.handlePageSizeChange,
  180. isMobile: isMobile,
  181. t: modelsData.t,
  182. })}
  183. t={modelsData.t}
  184. >
  185. <ModelsTable {...modelsData} />
  186. </CardPro>
  187. </>
  188. );
  189. };
  190. export default ModelsPage;