Merge pull request #1267 from t0ng7u/feature/upstream-ratio-sync

🔄 feat(ratio-sync): introduce upstream ratio synchronisation feature #1220
This commit is contained in:
Calcium-Ion
2025-06-20 16:22:00 +08:00
committed by GitHub
18 changed files with 1320 additions and 12 deletions

View File

@@ -0,0 +1,143 @@
import React, { useState } from 'react';
import {
Modal,
Transfer,
Input,
Space,
Checkbox,
Avatar,
Highlight,
} from '@douyinfe/semi-ui';
import { IconClose } from '@douyinfe/semi-icons';
const CHANNEL_STATUS_CONFIG = {
1: { color: 'green', text: '启用' },
2: { color: 'red', text: '禁用' },
3: { color: 'amber', text: '自禁' },
default: { color: 'grey', text: '未知' }
};
const getChannelStatusConfig = (status) => {
return CHANNEL_STATUS_CONFIG[status] || CHANNEL_STATUS_CONFIG.default;
};
export default function ChannelSelectorModal({
t,
visible,
onCancel,
onOk,
allChannels = [],
selectedChannelIds = [],
setSelectedChannelIds,
channelEndpoints,
updateChannelEndpoint,
}) {
const [searchText, setSearchText] = useState('');
const ChannelInfo = ({ item, showEndpoint = false, isSelected = false }) => {
const channelId = item.key || item.value;
const currentEndpoint = channelEndpoints[channelId];
const baseUrl = item._originalData?.base_url || '';
const status = item._originalData?.status || 0;
const statusConfig = getChannelStatusConfig(status);
return (
<>
<Avatar color={statusConfig.color} size="small">
{statusConfig.text}
</Avatar>
<div className="info">
<div className="name">
{isSelected ? (
item.label
) : (
<Highlight sourceString={item.label} searchWords={[searchText]} />
)}
</div>
<div className="email" style={showEndpoint ? { display: 'flex', alignItems: 'center', gap: '4px' } : {}}>
<span className="text-xs text-gray-500 truncate max-w-[200px]" title={baseUrl}>
{isSelected ? (
baseUrl
) : (
<Highlight sourceString={baseUrl} searchWords={[searchText]} />
)}
</span>
{showEndpoint && (
<Input
size="small"
value={currentEndpoint}
onChange={(value) => updateChannelEndpoint(channelId, value)}
placeholder="/api/ratio_config"
className="flex-1 text-xs"
style={{ fontSize: '12px' }}
/>
)}
{isSelected && !showEndpoint && (
<span className="text-xs text-gray-700 font-mono bg-gray-100 px-2 py-1 rounded ml-2">
{currentEndpoint}
</span>
)}
</div>
</div>
</>
);
};
const renderSourceItem = (item) => {
return (
<div className="components-transfer-source-item" key={item.key}>
<Checkbox
onChange={item.onChange}
checked={item.checked}
style={{ height: 52, alignItems: 'center' }}
>
<ChannelInfo item={item} showEndpoint={true} />
</Checkbox>
</div>
);
};
const renderSelectedItem = (item) => {
return (
<div className="components-transfer-selected-item" key={item.key}>
<ChannelInfo item={item} isSelected={true} />
<IconClose style={{ cursor: 'pointer' }} onClick={item.onRemove} />
</div>
);
};
const channelFilter = (input, item) => {
const searchLower = input.toLowerCase();
return item.label.toLowerCase().includes(searchLower) ||
(item._originalData?.base_url || '').toLowerCase().includes(searchLower);
};
return (
<Modal
visible={visible}
onCancel={onCancel}
onOk={onOk}
title={<span className="text-lg font-semibold">{t('选择同步渠道')}</span>}
width={1000}
>
<Space vertical style={{ width: '100%' }}>
<Transfer
style={{ width: '100%' }}
dataSource={allChannels}
value={selectedChannelIds}
onChange={setSelectedChannelIds}
renderSourceItem={renderSourceItem}
renderSelectedItem={renderSelectedItem}
filter={channelFilter}
inputProps={{ placeholder: t('搜索渠道名称或地址') }}
onSearch={setSearchText}
emptyContent={{
left: t('暂无渠道'),
right: t('暂无选择'),
search: t('无搜索结果'),
}}
/>
</Space>
</Modal>
);
}

View File

@@ -6,6 +6,7 @@ import GroupRatioSettings from '../../pages/Setting/Ratio/GroupRatioSettings.js'
import ModelRatioSettings from '../../pages/Setting/Ratio/ModelRatioSettings.js';
import ModelSettingsVisualEditor from '../../pages/Setting/Ratio/ModelSettingsVisualEditor.js';
import ModelRatioNotSetEditor from '../../pages/Setting/Ratio/ModelRationNotSetEditor.js';
import UpstreamRatioSync from '../../pages/Setting/Ratio/UpstreamRatioSync.js';
import { API, showError } from '../../helpers';
@@ -21,6 +22,7 @@ const RatioSetting = () => {
GroupGroupRatio: '',
AutoGroups: '',
DefaultUseAutoGroup: false,
ExposeRatioEnabled: false,
UserUsableGroups: '',
});
@@ -48,7 +50,7 @@ const RatioSetting = () => {
// 如果后端返回的不是合法 JSON直接展示
}
}
if (['DefaultUseAutoGroup'].includes(item.key)) {
if (['DefaultUseAutoGroup', 'ExposeRatioEnabled'].includes(item.key)) {
newInputs[item.key] = item.value === 'true' ? true : false;
} else {
newInputs[item.key] = item.value;
@@ -78,10 +80,6 @@ const RatioSetting = () => {
return (
<Spin spinning={loading} size='large'>
{/* 分组倍率设置 */}
<Card style={{ marginTop: '10px' }}>
<GroupRatioSettings options={inputs} refresh={onRefresh} />
</Card>
{/* 模型倍率设置以及可视化编辑器 */}
<Card style={{ marginTop: '10px' }}>
<Tabs type='line'>
@@ -100,8 +98,18 @@ const RatioSetting = () => {
refresh={onRefresh}
/>
</Tabs.TabPane>
<Tabs.TabPane tab={t('上游倍率同步')} itemKey='upstream_sync'>
<UpstreamRatioSync
options={inputs}
refresh={onRefresh}
/>
</Tabs.TabPane>
</Tabs>
</Card>
{/* 分组倍率设置 */}
<Card style={{ marginTop: '10px' }}>
<GroupRatioSettings options={inputs} refresh={onRefresh} />
</Card>
</Spin>
);
};