/* Copyright (c) 2025 Tethys Plex This file is part of Veloera. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ import React, { useEffect, useState, useRef } from 'react'; import { Button, Col, Form, Row, Spin, Modal, Select, Space, Typography, } from '@douyinfe/semi-ui'; import { API, showError, showSuccess, } from '../../../helpers'; import { useTranslation } from 'react-i18next'; const { Text } = Typography; export default function ModelCommonRatioSettings(props) { const [loading, setLoading] = useState(false); const [presetModalVisible, setPresetModalVisible] = useState(false); const [resetModalVisible, setResetModalVisible] = useState(false); const [presetLoading, setPresetLoading] = useState(false); const [resetLoading, setResetLoading] = useState(false); const [selectedPresetSource, setSelectedPresetSource] = useState('flexible'); const [selectedResetSource, setSelectedResetSource] = useState('flexible'); const [inputs, setInputs] = useState({ fallback_pricing_enabled: false, fallback_single_price: '', fallback_input_ratio: '', fallback_completion_ratio: '', redirect_billing_enabled: false, }); const { t } = useTranslation(); const refForm = useRef(); function handleFieldChange(fieldName) { return (value) => { if (fieldName === 'fallback_single_price') { setInputs((inputs) => ({ ...inputs, fallback_single_price: typeof value === 'number' ? String(value) : value, fallback_input_ratio: value ? '' : inputs.fallback_input_ratio, fallback_completion_ratio: value ? '' : inputs.fallback_completion_ratio, })); } else if (fieldName === 'fallback_input_ratio' || fieldName === 'fallback_completion_ratio') { setInputs((inputs) => ({ ...inputs, [fieldName]: typeof value === 'number' ? String(value) : value, fallback_single_price: value ? '' : inputs.fallback_single_price, })); } else if (fieldName === 'redirect_billing_enabled') { const newInputs = { ...inputs, [fieldName]: value }; setInputs(newInputs); // Auto-save redirect billing setting saveRedirectBillingSetting(value); } else if (fieldName === 'fallback_pricing_enabled') { const newInputs = { ...inputs, [fieldName]: value }; setInputs(newInputs); // Auto-save fallback pricing enabled setting saveFallbackPricingEnabled(value); } else { setInputs((inputs) => ({ ...inputs, [fieldName]: typeof value === 'number' ? String(value) : value })); } }; } const presetSources = [ { value: 'flexible', label: '通用(默认,推荐)' }, { value: 'openrouter', label: 'OpenRouter' }, { value: 'mixed', label: '混合' }, { value: 'legacy', label: '传统(不推荐)' }, ]; const resetPresetRatios = async () => { setResetLoading(true); try { let promptUrl, completionUrl; if (selectedResetSource === 'legacy') { completionUrl = 'https://public-assets.veloera.org/defaults/model-ratios/completion.json'; promptUrl = null; } else { promptUrl = `https://public-assets.veloera.org/defaults/model-ratios/${selectedResetSource}/prompt.json`; completionUrl = `https://public-assets.veloera.org/defaults/model-ratios/${selectedResetSource}/completion.json`; } const requests = []; if (promptUrl) { requests.push(fetch(promptUrl).then(res => res.json()).catch(() => ({}))); } requests.push(fetch(completionUrl).then(res => res.json()).catch(() => ({}))); const [promptRatios, completionRatios] = promptUrl ? await Promise.all(requests) : [null, await requests[0]]; const currentModelRatio = JSON.parse(props.options.ModelRatio || '{}'); const currentCompletionRatio = JSON.parse(props.options.CompletionRatio || '{}'); const filteredModelRatio = { ...currentModelRatio }; const filteredCompletionRatio = { ...currentCompletionRatio }; if (promptRatios) { Object.keys(promptRatios).forEach(model => { delete filteredModelRatio[model]; }); } if (completionRatios) { Object.keys(completionRatios).forEach(model => { delete filteredCompletionRatio[model]; }); } const requestQueue = [ API.put('/api/option/', { key: 'ModelRatio', value: JSON.stringify(filteredModelRatio, null, 2) }), API.put('/api/option/', { key: 'CompletionRatio', value: JSON.stringify(filteredCompletionRatio, null, 2) }) ]; const results = await Promise.all(requestQueue); if (results.includes(undefined)) { return showError(t('重置预设倍率失败,请重试')); } for (const res of results) { if (!res.data.success) { return showError(res.data.message); } } showSuccess(t('预设倍率重置成功')); setResetModalVisible(false); props.refresh(); } catch (error) { console.error('重置预设倍率失败:', error); showError(t('重置预设倍率失败,请重试')); } finally { setResetLoading(false); } }; const fetchPresetRatios = async () => { setPresetLoading(true); try { let promptUrl, completionUrl; if (selectedPresetSource === 'legacy') { completionUrl = 'https://public-assets.veloera.org/defaults/model-ratios/completion.json'; promptUrl = null; } else { promptUrl = `https://public-assets.veloera.org/defaults/model-ratios/${selectedPresetSource}/prompt.json`; completionUrl = `https://public-assets.veloera.org/defaults/model-ratios/${selectedPresetSource}/completion.json`; } const requests = []; if (promptUrl) { requests.push(fetch(promptUrl).then(res => res.json()).catch(() => ({}))); } requests.push(fetch(completionUrl).then(res => res.json()).catch(() => ({}))); const [promptRatios, completionRatios] = promptUrl ? await Promise.all(requests) : [null, await requests[0]]; const currentModelRatio = JSON.parse(props.options.ModelRatio || '{}'); const currentCompletionRatio = JSON.parse(props.options.CompletionRatio || '{}'); const mergedModelRatio = { ...currentModelRatio }; const mergedCompletionRatio = { ...currentCompletionRatio }; if (promptRatios) { Object.keys(promptRatios).forEach(model => { mergedModelRatio[model] = promptRatios[model]; }); } if (completionRatios) { Object.keys(completionRatios).forEach(model => { mergedCompletionRatio[model] = completionRatios[model]; }); } const requestQueue = [ API.put('/api/option/', { key: 'ModelRatio', value: JSON.stringify(mergedModelRatio, null, 2) }), API.put('/api/option/', { key: 'CompletionRatio', value: JSON.stringify(mergedCompletionRatio, null, 2) }) ]; const results = await Promise.all(requestQueue); if (results.includes(undefined)) { return showError(t('获取预设倍率失败,请重试')); } for (const res of results) { if (!res.data.success) { return showError(res.data.message); } } showSuccess(t('预设倍率获取成功')); setPresetModalVisible(false); props.refresh(); await saveFallbackPricing(); } catch (error) { console.error('获取预设倍率失败:', error); showError(t('获取预设倍率失败,请重试')); } finally { setPresetLoading(false); } }; const handleFallbackPriceChange = (type, value) => { const newInputs = {...inputs}; if (type === 'single') { newInputs.fallback_single_price = value; if (value) { newInputs.fallback_input_ratio = ''; newInputs.fallback_completion_ratio = ''; } } else { newInputs[`fallback_${type}_ratio`] = value; if (value) { newInputs.fallback_single_price = ''; } } setInputs(newInputs); }; const validateFallbackPricing = () => { const { fallback_single_price, fallback_input_ratio, fallback_completion_ratio } = inputs; if (fallback_single_price) { return !fallback_input_ratio && !fallback_completion_ratio; } if (fallback_input_ratio || fallback_completion_ratio) { return fallback_input_ratio && fallback_completion_ratio && !fallback_single_price; } return true; }; const saveFallbackPricing = async () => { if (!validateFallbackPricing()) { showError(t('请检查兜底倍率配置:使用单次价格时不能设置倍率,使用倍率时需要同时设置输入和补全倍率')); return; } const fallbackOptions = [ { key: 'fallback_pricing_enabled', value: String(inputs.fallback_pricing_enabled) }, { key: 'fallback_single_price', value: String(inputs.fallback_single_price || '') }, { key: 'fallback_input_ratio', value: String(inputs.fallback_input_ratio || '') }, { key: 'fallback_completion_ratio', value: String(inputs.fallback_completion_ratio || '') } ]; try { setLoading(true); const requestQueue = fallbackOptions.map((option) => API.put('/api/option/', option) ); const res = await Promise.all(requestQueue); if (res.includes(undefined)) { return showError(t('保存失败,请重试')); } for (let i = 0; i < res.length; i++) { if (!res[i].data.success) { return showError(res[i].data.message); } } showSuccess(t('兜底倍率保存成功')); props.refresh(); } catch (error) { console.error('Unexpected error:', error); showError(t('保存失败,请重试')); } finally { setLoading(false); } }; const saveRedirectBillingSetting = async (enabled) => { try { setLoading(true); const res = await API.put('/api/option/', { key: 'redirect_billing_enabled', value: String(enabled) }); if (!res.data.success) { showError(res.data.message); return; } showSuccess(t('重定向计费设置保存成功')); props.refresh(); } catch (error) { console.error('保存重定向计费设置失败:', error); showError(t('保存失败,请重试')); } finally { setLoading(false); } }; const saveFallbackPricingEnabled = async (enabled) => { try { setLoading(true); const res = await API.put('/api/option/', { key: 'fallback_pricing_enabled', value: String(enabled) }); if (!res.data.success) { showError(res.data.message); return; } showSuccess(t('启用兜底倍率设置保存成功')); props.refresh(); } catch (error) { console.error('保存启用兜底倍率设置失败:', error); showError(t('保存失败,请重试')); } finally { setLoading(false); } }; useEffect(() => { const currentInputs = { fallback_pricing_enabled: false, fallback_single_price: '', fallback_input_ratio: '', fallback_completion_ratio: '', redirect_billing_enabled: false, }; for (let key in props.options) { if (Object.keys(currentInputs).includes(key)) { if (key === 'fallback_pricing_enabled' || key === 'redirect_billing_enabled') { currentInputs[key] = props.options[key] === 'true'; } else { currentInputs[key] = props.options[key] || ''; } } } setInputs(currentInputs); if (refForm.current) { refForm.current.setValues(currentInputs); } }, [props.options]); return (
(refForm.current = formAPI)} style={{ marginBottom: 15 }} > {inputs.fallback_pricing_enabled && ( )}
setPresetModalVisible(false)} onOk={fetchPresetRatios} okText={t('保存')} confirmLoading={presetLoading} width={500} >
{t('选择预设源:')}
{t('警告:')}
  • {t('将从本地配置中移除选定预设源中包含的所有模型')}
  • {t('此操作不可撤销,请谨慎操作')}
  • {t('建议在操作前备份当前配置')}
{t('由 Veloera Public Assets 提供支持,')} {t('条款和条件')} {t('适用。')}
); }