ModelsColumnDefs.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  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. Space,
  19. Tag,
  20. Typography,
  21. Modal,
  22. Popover
  23. } from '@douyinfe/semi-ui';
  24. import {
  25. timestamp2string,
  26. getLobeHubIcon,
  27. stringToColor
  28. } from '../../../helpers';
  29. const { Text } = Typography;
  30. // Render timestamp
  31. function renderTimestamp(timestamp) {
  32. return <>{timestamp2string(timestamp)}</>;
  33. }
  34. // Render vendor column with icon
  35. const renderVendorTag = (vendorId, vendorMap, t) => {
  36. if (!vendorId || !vendorMap[vendorId]) return '-';
  37. const v = vendorMap[vendorId];
  38. return (
  39. <Tag
  40. color='white'
  41. shape='circle'
  42. prefixIcon={getLobeHubIcon(v.icon || 'Layers', 14)}
  43. >
  44. {v.name}
  45. </Tag>
  46. );
  47. };
  48. // Render description with ellipsis
  49. const renderDescription = (text) => {
  50. return (
  51. <Text ellipsis={{ showTooltip: true }} style={{ maxWidth: 200 }}>
  52. {text || '-'}
  53. </Text>
  54. );
  55. };
  56. // Render tags
  57. const renderTags = (text) => {
  58. if (!text) return '-';
  59. const tagsArr = text.split(',').filter(Boolean);
  60. const maxDisplayTags = 3;
  61. const displayTags = tagsArr.slice(0, maxDisplayTags);
  62. const remainingTags = tagsArr.slice(maxDisplayTags);
  63. return (
  64. <Space spacing={1} wrap>
  65. {displayTags.map((tag, index) => (
  66. <Tag key={index} size="small" shape='circle' color={stringToColor(tag)}>
  67. {tag}
  68. </Tag>
  69. ))}
  70. {remainingTags.length > 0 && (
  71. <Popover
  72. content={
  73. <div className='p-2'>
  74. <Space spacing={1} wrap>
  75. {remainingTags.map((tag, index) => (
  76. <Tag key={index} size="small" shape='circle' color={stringToColor(tag)}>
  77. {tag}
  78. </Tag>
  79. ))}
  80. </Space>
  81. </div>
  82. }
  83. position="top"
  84. >
  85. <Tag size="small" shape='circle' color="grey">
  86. +{remainingTags.length}
  87. </Tag>
  88. </Popover>
  89. )}
  90. </Space>
  91. );
  92. };
  93. // Render endpoints
  94. const renderEndpoints = (text) => {
  95. try {
  96. const arr = JSON.parse(text);
  97. if (Array.isArray(arr)) {
  98. return (
  99. <Space spacing={1} wrap>
  100. {arr.map((ep) => (
  101. <Tag key={ep} color="blue" size="small" shape='circle'>
  102. {ep}
  103. </Tag>
  104. ))}
  105. </Space>
  106. );
  107. }
  108. } catch (_) { }
  109. return text || '-';
  110. };
  111. // Render bound channels
  112. const renderBoundChannels = (channels) => {
  113. if (!channels || channels.length === 0) return '-';
  114. return (
  115. <Space spacing={1} wrap>
  116. {channels.map((c, idx) => (
  117. <Tag key={idx} color="purple" size="small" shape='circle'>
  118. {c.name}({c.type})
  119. </Tag>
  120. ))}
  121. </Space>
  122. );
  123. };
  124. // Render operations column
  125. const renderOperations = (text, record, setEditingModel, setShowEdit, manageModel, refresh, t) => {
  126. return (
  127. <Space wrap>
  128. {record.status === 1 ? (
  129. <Button
  130. type='danger'
  131. size="small"
  132. onClick={() => manageModel(record.id, 'disable', record)}
  133. >
  134. {t('禁用')}
  135. </Button>
  136. ) : (
  137. <Button
  138. size="small"
  139. onClick={() => manageModel(record.id, 'enable', record)}
  140. >
  141. {t('启用')}
  142. </Button>
  143. )}
  144. <Button
  145. type='tertiary'
  146. size="small"
  147. onClick={() => {
  148. setEditingModel(record);
  149. setShowEdit(true);
  150. }}
  151. >
  152. {t('编辑')}
  153. </Button>
  154. <Button
  155. type='danger'
  156. size="small"
  157. onClick={() => {
  158. Modal.confirm({
  159. title: t('确定是否要删除此模型?'),
  160. content: t('此修改将不可逆'),
  161. onOk: () => {
  162. (async () => {
  163. await manageModel(record.id, 'delete', record);
  164. await refresh();
  165. })();
  166. },
  167. });
  168. }}
  169. >
  170. {t('删除')}
  171. </Button>
  172. </Space>
  173. );
  174. };
  175. export const getModelsColumns = ({
  176. t,
  177. manageModel,
  178. setEditingModel,
  179. setShowEdit,
  180. refresh,
  181. vendorMap,
  182. }) => {
  183. return [
  184. {
  185. title: t('模型名称'),
  186. dataIndex: 'model_name',
  187. },
  188. {
  189. title: t('描述'),
  190. dataIndex: 'description',
  191. render: renderDescription,
  192. },
  193. {
  194. title: t('供应商'),
  195. dataIndex: 'vendor_id',
  196. render: (vendorId, record) => renderVendorTag(vendorId, vendorMap, t),
  197. },
  198. {
  199. title: t('标签'),
  200. dataIndex: 'tags',
  201. render: renderTags,
  202. },
  203. {
  204. title: t('端点'),
  205. dataIndex: 'endpoints',
  206. render: renderEndpoints,
  207. },
  208. {
  209. title: t('已绑定渠道'),
  210. dataIndex: 'bound_channels',
  211. render: renderBoundChannels,
  212. },
  213. {
  214. title: t('创建时间'),
  215. dataIndex: 'created_time',
  216. render: (text, record, index) => {
  217. return <div>{renderTimestamp(text)}</div>;
  218. },
  219. },
  220. {
  221. title: t('更新时间'),
  222. dataIndex: 'updated_time',
  223. render: (text, record, index) => {
  224. return <div>{renderTimestamp(text)}</div>;
  225. },
  226. },
  227. {
  228. title: '',
  229. dataIndex: 'operate',
  230. fixed: 'right',
  231. render: (text, record, index) => renderOperations(
  232. text,
  233. record,
  234. setEditingModel,
  235. setShowEdit,
  236. manageModel,
  237. refresh,
  238. t
  239. ),
  240. },
  241. ];
  242. };