| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import React from 'react'; |
| import { |
| Button, |
| Space, |
| Tag, |
| Typography, |
| Modal, |
| Tooltip, |
| } from '@douyinfe/semi-ui'; |
| import { |
| timestamp2string, |
| getLobeHubIcon, |
| stringToColor, |
| } from '../../../helpers'; |
| import { |
| renderLimitedItems, |
| renderDescription, |
| } from '../../common/ui/RenderUtils'; |
|
|
| const { Text } = Typography; |
|
|
| |
| function renderTimestamp(timestamp) { |
| return <>{timestamp2string(timestamp)}</>; |
| } |
|
|
| |
| const renderModelIconCol = (record, vendorMap) => { |
| const iconKey = record?.icon || vendorMap[record?.vendor_id]?.icon; |
| if (!iconKey) return '-'; |
| return ( |
| <div className='flex items-center justify-center'> |
| {getLobeHubIcon(iconKey, 20)} |
| </div> |
| ); |
| }; |
|
|
| |
| const renderVendorTag = (vendorId, vendorMap, t) => { |
| if (!vendorId || !vendorMap[vendorId]) return '-'; |
| const v = vendorMap[vendorId]; |
| return ( |
| <Tag |
| color='white' |
| shape='circle' |
| prefixIcon={getLobeHubIcon(v.icon || 'Layers', 14)} |
| > |
| {v.name} |
| </Tag> |
| ); |
| }; |
|
|
| |
| const renderGroups = (groups) => { |
| if (!groups || groups.length === 0) return '-'; |
| return renderLimitedItems({ |
| items: groups, |
| renderItem: (g, idx) => ( |
| <Tag key={idx} size='small' shape='circle' color={stringToColor(g)}> |
| {g} |
| </Tag> |
| ), |
| }); |
| }; |
|
|
| |
| const renderTags = (text) => { |
| if (!text) return '-'; |
| const tagsArr = text.split(',').filter(Boolean); |
| return renderLimitedItems({ |
| items: tagsArr, |
| renderItem: (tag, idx) => ( |
| <Tag key={idx} size='small' shape='circle' color={stringToColor(tag)}> |
| {tag} |
| </Tag> |
| ), |
| }); |
| }; |
|
|
| |
| const renderEndpoints = (value) => { |
| try { |
| const parsed = typeof value === 'string' ? JSON.parse(value) : value; |
| if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { |
| const keys = Object.keys(parsed || {}); |
| if (keys.length === 0) return '-'; |
| return renderLimitedItems({ |
| items: keys, |
| renderItem: (key, idx) => ( |
| <Tag key={idx} size='small' shape='circle' color={stringToColor(key)}> |
| {key} |
| </Tag> |
| ), |
| maxDisplay: 3, |
| }); |
| } |
| if (Array.isArray(parsed)) { |
| if (parsed.length === 0) return '-'; |
| return renderLimitedItems({ |
| items: parsed, |
| renderItem: (ep, idx) => ( |
| <Tag key={idx} color='white' size='small' shape='circle'> |
| {ep} |
| </Tag> |
| ), |
| maxDisplay: 3, |
| }); |
| } |
| return value || '-'; |
| } catch (_) { |
| return value || '-'; |
| } |
| }; |
|
|
| |
| const renderQuotaTypes = (arr, t) => { |
| if (!Array.isArray(arr) || arr.length === 0) return '-'; |
| return renderLimitedItems({ |
| items: arr, |
| renderItem: (qt, idx) => { |
| if (qt === 1) { |
| return ( |
| <Tag key={`${qt}-${idx}`} color='teal' size='small' shape='circle'> |
| {t('按次计费')} |
| </Tag> |
| ); |
| } |
| if (qt === 0) { |
| return ( |
| <Tag key={`${qt}-${idx}`} color='violet' size='small' shape='circle'> |
| {t('按量计费')} |
| </Tag> |
| ); |
| } |
| return ( |
| <Tag key={`${qt}-${idx}`} color='white' size='small' shape='circle'> |
| {qt} |
| </Tag> |
| ); |
| }, |
| maxDisplay: 3, |
| }); |
| }; |
|
|
| |
| const renderBoundChannels = (channels) => { |
| if (!channels || channels.length === 0) return '-'; |
| return renderLimitedItems({ |
| items: channels, |
| renderItem: (c, idx) => ( |
| <Tag key={idx} color='white' size='small' shape='circle'> |
| {c.name}({c.type}) |
| </Tag> |
| ), |
| }); |
| }; |
|
|
| |
| const renderOperations = ( |
| text, |
| record, |
| setEditingModel, |
| setShowEdit, |
| manageModel, |
| refresh, |
| t, |
| ) => { |
| return ( |
| <Space wrap> |
| {record.status === 1 ? ( |
| <Button |
| type='danger' |
| size='small' |
| onClick={() => manageModel(record.id, 'disable', record)} |
| > |
| {t('禁用')} |
| </Button> |
| ) : ( |
| <Button |
| size='small' |
| onClick={() => manageModel(record.id, 'enable', record)} |
| > |
| {t('启用')} |
| </Button> |
| )} |
| |
| <Button |
| type='tertiary' |
| size='small' |
| onClick={() => { |
| setEditingModel(record); |
| setShowEdit(true); |
| }} |
| > |
| {t('编辑')} |
| </Button> |
| |
| <Button |
| type='danger' |
| size='small' |
| onClick={() => { |
| Modal.confirm({ |
| title: t('确定是否要删除此模型?'), |
| content: t('此修改将不可逆'), |
| onOk: () => { |
| (async () => { |
| await manageModel(record.id, 'delete', record); |
| await refresh(); |
| })(); |
| }, |
| }); |
| }} |
| > |
| {t('删除')} |
| </Button> |
| </Space> |
| ); |
| }; |
|
|
| |
| const renderNameRule = (rule, record, t) => { |
| const map = { |
| 0: { color: 'green', label: t('精确') }, |
| 1: { color: 'blue', label: t('前缀') }, |
| 2: { color: 'orange', label: t('包含') }, |
| 3: { color: 'purple', label: t('后缀') }, |
| }; |
| const cfg = map[rule]; |
| if (!cfg) return '-'; |
|
|
| let label = cfg.label; |
| if (rule !== 0 && record.matched_count) { |
| label = `${cfg.label} ${record.matched_count}${t('个模型')}`; |
| } |
|
|
| const tagElement = ( |
| <Tag color={cfg.color} size='small' shape='circle'> |
| {label} |
| </Tag> |
| ); |
|
|
| if ( |
| rule === 0 || |
| !record.matched_models || |
| record.matched_models.length === 0 |
| ) { |
| return tagElement; |
| } |
|
|
| return ( |
| <Tooltip content={record.matched_models.join(', ')} showArrow> |
| {tagElement} |
| </Tooltip> |
| ); |
| }; |
|
|
| export const getModelsColumns = ({ |
| t, |
| manageModel, |
| setEditingModel, |
| setShowEdit, |
| refresh, |
| vendorMap, |
| }) => { |
| return [ |
| { |
| title: t('图标'), |
| dataIndex: 'icon', |
| width: 70, |
| align: 'center', |
| render: (text, record) => renderModelIconCol(record, vendorMap), |
| }, |
| { |
| title: t('模型名称'), |
| dataIndex: 'model_name', |
| render: (text) => ( |
| <Text copyable onClick={(e) => e.stopPropagation()}> |
| {text} |
| </Text> |
| ), |
| }, |
| { |
| title: t('匹配类型'), |
| dataIndex: 'name_rule', |
| render: (val, record) => renderNameRule(val, record, t), |
| }, |
| { |
| title: t('参与官方同步'), |
| dataIndex: 'sync_official', |
| render: (val) => ( |
| <Tag size='small' shape='circle' color={val === 1 ? 'green' : 'orange'}> |
| {val === 1 ? t('是') : t('否')} |
| </Tag> |
| ), |
| }, |
| { |
| title: t('描述'), |
| dataIndex: 'description', |
| render: (text) => renderDescription(text, 200), |
| }, |
| { |
| title: t('供应商'), |
| dataIndex: 'vendor_id', |
| render: (vendorId, record) => renderVendorTag(vendorId, vendorMap, t), |
| }, |
| { |
| title: t('标签'), |
| dataIndex: 'tags', |
| render: renderTags, |
| }, |
| { |
| title: t('端点'), |
| dataIndex: 'endpoints', |
| render: renderEndpoints, |
| }, |
| { |
| title: t('已绑定渠道'), |
| dataIndex: 'bound_channels', |
| render: renderBoundChannels, |
| }, |
| { |
| title: t('可用分组'), |
| dataIndex: 'enable_groups', |
| render: renderGroups, |
| }, |
| { |
| title: t('计费类型'), |
| dataIndex: 'quota_types', |
| render: (qts) => renderQuotaTypes(qts, t), |
| }, |
| { |
| title: t('创建时间'), |
| dataIndex: 'created_time', |
| render: (text, record, index) => { |
| return <div>{renderTimestamp(text)}</div>; |
| }, |
| }, |
| { |
| title: t('更新时间'), |
| dataIndex: 'updated_time', |
| render: (text, record, index) => { |
| return <div>{renderTimestamp(text)}</div>; |
| }, |
| }, |
| { |
| title: '', |
| dataIndex: 'operate', |
| fixed: 'right', |
| render: (text, record, index) => |
| renderOperations( |
| text, |
| record, |
| setEditingModel, |
| setShowEdit, |
| manageModel, |
| refresh, |
| t, |
| ), |
| }, |
| ]; |
| }; |
|
|