diff --git a/controller/ratio_sync.go b/controller/ratio_sync.go index 490a2a74..368f92dd 100644 --- a/controller/ratio_sync.go +++ b/controller/ratio_sync.go @@ -29,7 +29,6 @@ type DifferenceItem struct { Upstreams map[string]interface{} `json:"upstreams"` // 上游值:具体值/"same"/null } -// SyncableChannel 可同步的渠道信息 type SyncableChannel struct { ID int `json:"id"` Name string `json:"name"` @@ -37,7 +36,6 @@ type SyncableChannel struct { Status int `json:"status"` } -// FetchUpstreamRatios 后端并发拉取上游倍率 func FetchUpstreamRatios(c *gin.Context) { var req dto.UpstreamRequest if err := c.ShouldBindJSON(&req); err != nil { @@ -49,10 +47,8 @@ func FetchUpstreamRatios(c *gin.Context) { req.Timeout = 10 } - // build upstream list from ids var upstreams []dto.UpstreamDTO if len(req.ChannelIDs) > 0 { - // convert []int64 -> []int for model function intIds := make([]int, 0, len(req.ChannelIDs)) for _, id64 := range req.ChannelIDs { intIds = append(intIds, int(id64)) @@ -62,7 +58,7 @@ func FetchUpstreamRatios(c *gin.Context) { upstreams = append(upstreams, dto.UpstreamDTO{ Name: ch.Name, BaseURL: ch.GetBaseURL(), - Endpoint: "", // assume default endpoint + Endpoint: "", }) } } @@ -110,7 +106,6 @@ func FetchUpstreamRatios(c *gin.Context) { wg.Wait() close(ch) - // 本地倍率配置 localData := ratio_setting.GetExposedData() var testResults []dto.TestResult @@ -138,7 +133,6 @@ func FetchUpstreamRatios(c *gin.Context) { } } - // 构建差异化数据 differences := buildDifferences(localData, successfulChannels) c.JSON(http.StatusOK, gin.H{ @@ -150,7 +144,6 @@ func FetchUpstreamRatios(c *gin.Context) { }) } -// buildDifferences 构建差异化数据,只返回有意义的差异 func buildDifferences(localData map[string]any, successfulChannels []struct { name string data map[string]any @@ -158,10 +151,8 @@ func buildDifferences(localData map[string]any, successfulChannels []struct { differences := make(map[string]map[string]dto.DifferenceItem) ratioTypes := []string{"model_ratio", "completion_ratio", "cache_ratio", "model_price"} - // 收集所有模型名称 allModels := make(map[string]struct{}) - // 从本地数据收集模型名称 for _, ratioType := range ratioTypes { if localRatioAny, ok := localData[ratioType]; ok { if localRatio, ok := localRatioAny.(map[string]float64); ok { @@ -172,7 +163,6 @@ func buildDifferences(localData map[string]any, successfulChannels []struct { } } - // 从上游数据收集模型名称 for _, channel := range successfulChannels { for _, ratioType := range ratioTypes { if upstreamRatio, ok := channel.data[ratioType].(map[string]any); ok { @@ -183,10 +173,8 @@ func buildDifferences(localData map[string]any, successfulChannels []struct { } } - // 对每个模型和每个比率类型进行分析 for modelName := range allModels { for _, ratioType := range ratioTypes { - // 获取本地值 var localValue interface{} = nil if localRatioAny, ok := localData[ratioType]; ok { if localRatio, ok := localRatioAny.(map[string]float64); ok { @@ -196,7 +184,6 @@ func buildDifferences(localData map[string]any, successfulChannels []struct { } } - // 收集上游值 upstreamValues := make(map[string]interface{}) hasUpstreamValue := false hasDifference := false @@ -209,7 +196,6 @@ func buildDifferences(localData map[string]any, successfulChannels []struct { upstreamValue = val hasUpstreamValue = true - // 检查是否与本地值不同 if localValue != nil && localValue != val { hasDifference = true } else if localValue == val { @@ -217,8 +203,10 @@ func buildDifferences(localData map[string]any, successfulChannels []struct { } } } + if upstreamValue == nil && localValue == nil { + upstreamValue = "same" + } - // 如果本地值为空但上游有值,这也是差异 if localValue == nil && upstreamValue != nil && upstreamValue != "same" { hasDifference = true } @@ -226,17 +214,13 @@ func buildDifferences(localData map[string]any, successfulChannels []struct { upstreamValues[channel.name] = upstreamValue } - // 应用过滤逻辑 shouldInclude := false if localValue != nil { - // 规则1: 本地值存在,至少有一个上游与本地值不同 if hasDifference { shouldInclude = true } - // 规则2: 本地值存在,但所有上游都未设置 - 不包含 } else { - // 规则3: 本地值不存在,至少有一个上游设置了值 if hasUpstreamValue { shouldInclude = true } @@ -254,10 +238,31 @@ func buildDifferences(localData map[string]any, successfulChannels []struct { } } + channelHasDiff := make(map[string]bool) + for _, ratioMap := range differences { + for _, item := range ratioMap { + for chName, val := range item.Upstreams { + if val != nil && val != "same" { + channelHasDiff[chName] = true + } + } + } + } + + for modelName, ratioMap := range differences { + for ratioType, item := range ratioMap { + for chName := range item.Upstreams { + if !channelHasDiff[chName] { + delete(item.Upstreams, chName) + } + } + differences[modelName][ratioType] = item + } + } + return differences } -// GetSyncableChannels 获取可用于倍率同步的渠道(base_url 不为空的渠道) func GetSyncableChannels(c *gin.Context) { channels, err := model.GetAllChannels(0, 0, true, false) if err != nil { @@ -270,7 +275,6 @@ func GetSyncableChannels(c *gin.Context) { var syncableChannels []dto.SyncableChannel for _, channel := range channels { - // 只返回 base_url 不为空的渠道 if channel.GetBaseURL() != "" { syncableChannels = append(syncableChannels, dto.SyncableChannel{ ID: channel.Id, diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index fc80f9c1..b8e1afd8 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -1665,5 +1665,27 @@ "确定清除所有失效兑换码?": "Are you sure you want to clear all invalid redemption codes?", "将删除已使用、已禁用及过期的兑换码,此操作不可撤销。": "This will delete all used, disabled, and expired redemption codes, this operation cannot be undone.", "选择过期时间(可选,留空为永久)": "Select expiration time (optional, leave blank for permanent)", - "请输入备注(仅管理员可见)": "Please enter a remark (only visible to administrators)" + "请输入备注(仅管理员可见)": "Please enter a remark (only visible to administrators)", + "上游倍率同步": "Upstream ratio synchronization", + "获取渠道失败:": "Failed to get channels: ", + "请至少选择一个渠道": "Please select at least one channel", + "获取倍率失败:": "Failed to get ratios: ", + "后端请求失败": "Backend request failed", + "部分渠道测试失败:": "Some channels failed to test: ", + "已与上游倍率完全一致,无需同步": "The upstream ratio is completely consistent, no synchronization is required", + "请求后端接口失败:": "Failed to request the backend interface: ", + "同步成功": "Synchronization successful", + "部分保存失败": "Some settings failed to save", + "保存失败": "Save failed", + "选择同步渠道": "Select synchronization channel", + "应用同步": "Apply synchronization", + "倍率类型": "Ratio type", + "当前值": "Current value", + "上游值": "Upstream value", + "差异": "Difference", + "搜索渠道名称或地址": "Search channel name or address", + "缓存倍率": "Cache ratio", + "暂无差异化倍率显示": "No differential ratio display", + "请先选择同步渠道": "Please select the synchronization channel first", + "与本地相同": "Same as local" } \ No newline at end of file diff --git a/web/src/pages/Setting/Ratio/UpstreamRatioSync.js b/web/src/pages/Setting/Ratio/UpstreamRatioSync.js index 2e12fd3b..aae6d9f3 100644 --- a/web/src/pages/Setting/Ratio/UpstreamRatioSync.js +++ b/web/src/pages/Setting/Ratio/UpstreamRatioSync.js @@ -11,7 +11,7 @@ import { RefreshCcw, CheckSquare, } from 'lucide-react'; -import { API, showError, showSuccess, showWarning } from '../../../helpers'; +import { API, showError, showSuccess, showWarning, stringToColor } from '../../../helpers'; import { DEFAULT_ENDPOINT } from '../../../constants'; import { useTranslation } from 'react-i18next'; import { @@ -35,14 +35,12 @@ export default function UpstreamRatioSync(props) { // 差异数据和测试结果 const [differences, setDifferences] = useState({}); - const [testResults, setTestResults] = useState([]); const [resolutions, setResolutions] = useState({}); // 分页相关状态 const [currentPage, setCurrentPage] = useState(1); const [pageSize, setPageSize] = useState(10); - // 获取所有渠道 const fetchAllChannels = async () => { setLoading(true); try { @@ -51,18 +49,16 @@ export default function UpstreamRatioSync(props) { if (res.data.success) { const channels = res.data.data || []; - // 转换为Transfer组件所需格式 const transferData = channels.map(channel => ({ key: channel.id, label: channel.name, value: channel.id, - disabled: false, // 所有渠道都可以选择 + disabled: false, _originalData: channel, })); setAllChannels(transferData); - // 初始化端点配置 const initialEndpoints = {}; transferData.forEach(channel => { initialEndpoints[channel.key] = DEFAULT_ENDPOINT; @@ -78,7 +74,6 @@ export default function UpstreamRatioSync(props) { } }; - // 确认选择渠道 const confirmChannelSelection = () => { const selected = allChannels .filter(ch => selectedChannelIds.includes(ch.value)) @@ -93,7 +88,6 @@ export default function UpstreamRatioSync(props) { fetchRatiosFromChannels(selected); }; - // 从选定渠道获取倍率 const fetchRatiosFromChannels = async (channelList) => { setSyncLoading(true); @@ -113,17 +107,14 @@ export default function UpstreamRatioSync(props) { const { differences = {}, test_results = [] } = res.data.data; - // 显示测试结果 const errorResults = test_results.filter(r => r.status === 'error'); if (errorResults.length > 0) { showWarning(t('部分渠道测试失败:') + errorResults.map(r => `${r.name}: ${r.error}`).join(', ')); } setDifferences(differences); - setTestResults(test_results); setResolutions({}); - // 判断是否有差异 if (Object.keys(differences).length === 0) { showSuccess(t('已与上游倍率完全一致,无需同步')); } @@ -134,7 +125,6 @@ export default function UpstreamRatioSync(props) { } }; - // 解决冲突/选择值 const selectValue = (model, ratioType, value) => { setResolutions(prev => ({ ...prev, @@ -145,7 +135,6 @@ export default function UpstreamRatioSync(props) { })); }; - // 应用同步 const applySync = async () => { const currentRatios = { ModelRatio: JSON.parse(props.options.ModelRatio || '{}'), @@ -154,7 +143,6 @@ export default function UpstreamRatioSync(props) { ModelPrice: JSON.parse(props.options.ModelPrice || '{}'), }; - // 应用已选择的值 Object.entries(resolutions).forEach(([model, ratios]) => { Object.entries(ratios).forEach(([ratioType, value]) => { const optionKey = ratioType @@ -165,7 +153,6 @@ export default function UpstreamRatioSync(props) { }); }); - // 保存到后端 setLoading(true); try { const updates = Object.entries(currentRatios).map(([key, value]) => @@ -180,11 +167,26 @@ export default function UpstreamRatioSync(props) { if (results.every(res => res.data.success)) { showSuccess(t('同步成功')); props.refresh(); - // 清空状态 - setDifferences({}); - setTestResults([]); + + setDifferences(prevDifferences => { + const newDifferences = { ...prevDifferences }; + + Object.entries(resolutions).forEach(([model, ratios]) => { + Object.keys(ratios).forEach(ratioType => { + if (newDifferences[model] && newDifferences[model][ratioType]) { + delete newDifferences[model][ratioType]; + + if (Object.keys(newDifferences[model]).length === 0) { + delete newDifferences[model]; + } + } + }); + }); + + return newDifferences; + }); + setResolutions({}); - setSelectedChannelIds([]); } else { showError(t('部分保存失败')); } @@ -195,14 +197,12 @@ export default function UpstreamRatioSync(props) { } }; - // 计算当前页显示的数据 const getCurrentPageData = (dataSource) => { const startIndex = (currentPage - 1) * pageSize; const endIndex = startIndex + pageSize; return dataSource.slice(startIndex, endIndex); }; - // 渲染表格头部 const renderHeader = () => (