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