|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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, |
|
|
), |
|
|
}, |
|
|
]; |
|
|
}; |
|
|
|