diff --git a/controller/misc.go b/controller/misc.go index 1caaf640..4ffe86f4 100644 --- a/controller/misc.go +++ b/controller/misc.go @@ -76,6 +76,7 @@ func GetStatus(c *gin.Context) { "demo_site_enabled": operation_setting.DemoSiteEnabled, "self_use_mode_enabled": operation_setting.SelfUseModeEnabled, "default_use_auto_group": setting.DefaultUseAutoGroup, + "pay_methods": setting.PayMethods, // 面板启用开关 "api_info_enabled": cs.ApiInfoEnabled, diff --git a/dto/openai_request.go b/dto/openai_request.go index 299171ba..42c290ca 100644 --- a/dto/openai_request.go +++ b/dto/openai_request.go @@ -53,6 +53,7 @@ type GeneralOpenAIRequest struct { Modalities json.RawMessage `json:"modalities,omitempty"` Audio json.RawMessage `json:"audio,omitempty"` EnableThinking any `json:"enable_thinking,omitempty"` // ali + THINKING json.RawMessage `json:"thinking,omitempty"` // doubao ExtraBody json.RawMessage `json:"extra_body,omitempty"` WebSearchOptions *WebSearchOptions `json:"web_search_options,omitempty"` // OpenRouter Params diff --git a/model/option.go b/model/option.go index 43c0a644..ec386b29 100644 --- a/model/option.go +++ b/model/option.go @@ -79,6 +79,7 @@ func InitOptionMap() { common.OptionMap["Chats"] = setting.Chats2JsonString() common.OptionMap["AutoGroups"] = setting.AutoGroups2JsonString() common.OptionMap["DefaultUseAutoGroup"] = strconv.FormatBool(setting.DefaultUseAutoGroup) + common.OptionMap["PayMethods"] = setting.PayMethods2JsonString() common.OptionMap["GitHubClientId"] = "" common.OptionMap["GitHubClientSecret"] = "" common.OptionMap["TelegramBotToken"] = "" @@ -388,6 +389,8 @@ func updateOptionMap(key string, value string) (err error) { operation_setting.AutomaticDisableKeywordsFromString(value) case "StreamCacheQueueLength": setting.StreamCacheQueueLength, _ = strconv.Atoi(value) + case "PayMethods": + err = setting.UpdatePayMethodsByJsonString(value) } return err } diff --git a/relay/channel/gemini/relay-gemini.go b/relay/channel/gemini/relay-gemini.go index 635041d7..d4b7c209 100644 --- a/relay/channel/gemini/relay-gemini.go +++ b/relay/channel/gemini/relay-gemini.go @@ -374,7 +374,9 @@ func CovertGemini2OpenAI(textRequest dto.GeneralOpenAIRequest, info *relaycommon if content.Role == "assistant" { content.Role = "model" } - geminiRequest.Contents = append(geminiRequest.Contents, content) + if len(content.Parts) > 0 { + geminiRequest.Contents = append(geminiRequest.Contents, content) + } } if len(system_content) > 0 { diff --git a/setting/payment.go b/setting/payment.go index f50723c3..4ffa4381 100644 --- a/setting/payment.go +++ b/setting/payment.go @@ -1,8 +1,45 @@ package setting +import "encoding/json" + var PayAddress = "" var CustomCallbackAddress = "" var EpayId = "" var EpayKey = "" var Price = 7.3 var MinTopUp = 1 + +var PayMethods = []map[string]string{ + { + "name": "支付宝", + "color": "rgba(var(--semi-blue-5), 1)", + "type": "zfb", + }, + { + "name": "微信", + "color": "rgba(var(--semi-green-5), 1)", + "type": "wx", + }, +} + +func UpdatePayMethodsByJsonString(jsonString string) error { + PayMethods = make([]map[string]string, 0) + return json.Unmarshal([]byte(jsonString), &PayMethods) +} + +func PayMethods2JsonString() string { + jsonBytes, err := json.Marshal(PayMethods) + if err != nil { + return "[]" + } + return string(jsonBytes) +} + +func ContainsPayMethod(method string) bool { + for _, payMethod := range PayMethods { + if payMethod["type"] == method { + return true + } + } + return false +} diff --git a/web/src/components/settings/SystemSetting.js b/web/src/components/settings/SystemSetting.js index 8219159b..1236ef2e 100644 --- a/web/src/components/settings/SystemSetting.js +++ b/web/src/components/settings/SystemSetting.js @@ -17,7 +17,7 @@ import { removeTrailingSlash, showError, showSuccess, - verifyJSON + verifyJSON, } from '../../helpers'; import axios from 'axios'; @@ -73,6 +73,7 @@ const SystemSetting = () => { LinuxDOOAuthEnabled: '', LinuxDOClientId: '', LinuxDOClientSecret: '', + PayMethods: '', }); const [originInputs, setOriginInputs] = useState({}); @@ -230,6 +231,12 @@ const SystemSetting = () => { return; } } + if (originInputs['PayMethods'] !== inputs.PayMethods) { + if (!verifyJSON(inputs.PayMethods)) { + showError('充值方式设置不是合法的 JSON 字符串'); + return; + } + } const options = [ { key: 'PayAddress', value: removeTrailingSlash(inputs.PayAddress) }, @@ -256,6 +263,9 @@ const SystemSetting = () => { if (originInputs['TopupGroupRatio'] !== inputs.TopupGroupRatio) { options.push({ key: 'TopupGroupRatio', value: inputs.TopupGroupRatio }); } + if (originInputs['PayMethods'] !== inputs.PayMethods) { + options.push({ key: 'PayMethods', value: inputs.PayMethods }); + } await updateOptions(options); }; @@ -658,6 +668,12 @@ const SystemSetting = () => { placeholder='为一个 JSON 文本,键为组名称,值为倍率' autosize /> + diff --git a/web/src/components/table/TaskLogsTable.js b/web/src/components/table/TaskLogsTable.js index 449b3d55..b3d0ab7b 100644 --- a/web/src/components/table/TaskLogsTable.js +++ b/web/src/components/table/TaskLogsTable.js @@ -96,20 +96,8 @@ const renderTimestamp = (timestampInSeconds) => { }; function renderDuration(submit_time, finishTime) { - // 确保startTime和finishTime都是有效的时间戳 if (!submit_time || !finishTime) return 'N/A'; - - // 将时间戳转换为Date对象 - const start = new Date(submit_time); - const finish = new Date(finishTime); - - // 计算时间差(毫秒) - const durationMs = finish - start; - - // 将时间差转换为秒,并保留一位小数 - const durationSec = (durationMs / 1000).toFixed(1); - - // 设置颜色:大于60秒则为红色,小于等于60秒则为绿色 + const durationSec = finishTime - submit_time; const color = durationSec > 60 ? 'red' : 'green'; // 返回带有样式的颜色标签 diff --git a/web/src/helpers/data.js b/web/src/helpers/data.js index bc1d28aa..afc29384 100644 --- a/web/src/helpers/data.js +++ b/web/src/helpers/data.js @@ -9,6 +9,7 @@ export function setStatusData(data) { localStorage.setItem('enable_task', data.enable_task); localStorage.setItem('enable_data_export', data.enable_data_export); localStorage.setItem('chats', JSON.stringify(data.chats)); + localStorage.setItem('pay_methods', JSON.stringify(data.pay_methods)); localStorage.setItem( 'data_export_default_time', data.data_export_default_time, diff --git a/web/src/pages/Channel/EditChannel.js b/web/src/pages/Channel/EditChannel.js index b1cbef49..ca38e6b9 100644 --- a/web/src/pages/Channel/EditChannel.js +++ b/web/src/pages/Channel/EditChannel.js @@ -298,18 +298,27 @@ const EditChannel = (props) => { } }; - useEffect(() => { - let localModelOptions = [...originModelOptions]; - inputs.models.forEach((model) => { - if (!localModelOptions.find((option) => option.label === model)) { - localModelOptions.push({ - label: model, - value: model, - }); - } - }); - setModelOptions(localModelOptions); - }, [originModelOptions, inputs.models]); +useEffect(() => { + // 使用 Map 来避免重复,以 value 为键 + const modelMap = new Map(); + + // 先添加原始模型选项 + originModelOptions.forEach(option => { + modelMap.set(option.value, option); + }); + + // 再添加当前选中的模型(如果不存在) + inputs.models.forEach(model => { + if (!modelMap.has(model)) { + modelMap.set(model, { + label: model, + value: model, + }); + } + }); + + setModelOptions(Array.from(modelMap.values())); +}, [originModelOptions, inputs.models]); useEffect(() => { fetchModels().then(); diff --git a/web/src/pages/TopUp/index.js b/web/src/pages/TopUp/index.js index cb32bca2..e327178d 100644 --- a/web/src/pages/TopUp/index.js +++ b/web/src/pages/TopUp/index.js @@ -7,7 +7,7 @@ import { renderQuota, renderQuotaWithAmount, copy, - getQuotaPerUnit + getQuotaPerUnit, } from '../../helpers'; import { Avatar, @@ -34,7 +34,7 @@ import { Copy, Users, User, - Coins + Coins, } from 'lucide-react'; const { Text, Title } = Typography; @@ -49,9 +49,15 @@ const TopUp = () => { const [topUpCode, setTopUpCode] = useState(''); const [amount, setAmount] = useState(0.0); const [minTopUp, setMinTopUp] = useState(statusState?.status?.min_topup || 1); - const [topUpCount, setTopUpCount] = useState(statusState?.status?.min_topup || 1); - const [topUpLink, setTopUpLink] = useState(statusState?.status?.top_up_link || ''); - const [enableOnlineTopUp, setEnableOnlineTopUp] = useState(statusState?.status?.enable_online_topup || false); + const [topUpCount, setTopUpCount] = useState( + statusState?.status?.min_topup || 1, + ); + const [topUpLink, setTopUpLink] = useState( + statusState?.status?.top_up_link || '', + ); + const [enableOnlineTopUp, setEnableOnlineTopUp] = useState( + statusState?.status?.enable_online_topup || false, + ); const [priceRatio, setPriceRatio] = useState(statusState?.status?.price || 1); const [userQuota, setUserQuota] = useState(0); const [isSubmitting, setIsSubmitting] = useState(false); @@ -61,6 +67,7 @@ const TopUp = () => { const [amountLoading, setAmountLoading] = useState(false); const [paymentLoading, setPaymentLoading] = useState(false); const [confirmLoading, setConfirmLoading] = useState(false); + const [payMethods, setPayMethods] = useState([]); // 邀请相关状态 const [affLink, setAffLink] = useState(''); @@ -76,7 +83,7 @@ const TopUp = () => { { value: 100 }, { value: 300 }, { value: 500 }, - { value: 1000 } + { value: 1000 }, ]); const [selectedPreset, setSelectedPreset] = useState(null); @@ -126,7 +133,7 @@ const TopUp = () => { if (userState.user) { const updatedUser = { ...userState.user, - quota: userState.user.quota + data + quota: userState.user.quota + data, }; userDispatch({ type: 'login', payload: updatedUser }); } @@ -283,6 +290,34 @@ const TopUp = () => { } getAffLink().then(); setTransferAmount(getQuotaPerUnit()); + + let payMethods = localStorage.getItem('pay_methods'); + try { + payMethods = JSON.parse(payMethods); + if (payMethods && payMethods.length > 0) { + // 检查name和type是否为空 + payMethods = payMethods.filter((method) => { + return method.name && method.type; + }); + // 如果没有color,则设置默认颜色 + payMethods = payMethods.map((method) => { + if (!method.color) { + if (method.type === 'zfb') { + method.color = 'rgba(var(--semi-blue-5), 1)'; + } else if (method.type === 'wx') { + method.color = 'rgba(var(--semi-green-5), 1)'; + } else { + method.color = 'rgba(var(--semi-primary-5), 1)'; + } + } + return method; + }); + setPayMethods(payMethods); + } + } catch (e) { + console.log(e); + showError(t('支付方式配置错误, 请联系管理员')); + } }, []); useEffect(() => { @@ -347,12 +382,12 @@ const TopUp = () => { }; return ( -
+
{/* 划转模态框 */} - +
+ {t('划转邀请额度')}
} @@ -360,22 +395,22 @@ const TopUp = () => { onOk={transfer} onCancel={handleTransferCancel} maskClosable={false} - size="small" + size='small' centered > -
+
- + {t('可用邀请额度')}
- + {t('划转额度')} ({t('最低') + renderQuota(getQuotaPerUnit())}) { max={userState?.user?.aff_quota || 0} value={transferAmount} onChange={(value) => setTransferAmount(value)} - size="large" - className="w-full" + size='large' + className='w-full' />
@@ -393,8 +428,8 @@ const TopUp = () => { {/* 充值确认模态框 */} - +
+ {t('充值确认')}
} @@ -402,57 +437,80 @@ const TopUp = () => { onOk={onlineTopUp} onCancel={handleCancel} maskClosable={false} - size="small" + size='small' centered confirmLoading={confirmLoading} > -
-
+
+
{t('充值数量')}: {renderQuotaWithAmount(topUpCount)}
-
+
{t('实付金额')}: {amountLoading ? ( ) : ( - {renderAmount()} + + {renderAmount()} + )}
-
+
{t('支付方式')}: - {payWay === 'zfb' ? ( -
- - {t('支付宝')} -
- ) : ( -
- - {t('微信')} -
- )} + {(() => { + const payMethod = payMethods.find( + (method) => method.type === payWay, + ); + if (payMethod) { + return ( +
+ {payMethod.type === 'zfb' ? ( + + ) : payMethod.type === 'wx' ? ( + + ) : ( + + )} + {payMethod.name} +
+ ); + } else { + // 默认充值方式 + return payWay === 'zfb' ? ( +
+ + {t('支付宝')} +
+ ) : ( +
+ + {t('微信')} +
+ ); + } + })()}
-
+
{/* 左侧充值区域 */} -
+
{/* 在线充值卡片 */} -
-
+
+
+
@@ -460,21 +518,23 @@ const TopUp = () => { {t('在线充值')} - + {t('快速方便的充值方式')}
-
+
{userDataLoading ? ( ) : ( - -
- - {getUsername()} ({getUserRole()}) - {getUsername()} + +
+ + + {getUsername()} ({getUserRole()}) + + {getUsername()}
)} @@ -483,29 +543,33 @@ const TopUp = () => {
} > -
+
{/* 账户余额信息 */} -
- - +
+ + {t('当前余额')} {userDataLoading ? ( - + ) : ( -
+
{renderQuota(userState?.user?.quota || userQuota)}
)} - - + + {t('历史消耗')} {userDataLoading ? ( - + ) : ( -
+
{renderQuota(userState?.user?.used_quota || 0)}
)} @@ -516,47 +580,59 @@ const TopUp = () => { <> {/* 预设充值额度卡片网格 */}
- {t('选择充值额度')} -
+ + {t('选择充值额度')} + +
{presetAmounts.map((preset, index) => ( selectPresetAmount(preset)} - className={`cursor-pointer !rounded-2xl transition-all hover:shadow-md ${selectedPreset === preset.value - ? 'border-blue-500' - : 'border-gray-200 hover:border-gray-300' - }`} + className={`cursor-pointer !rounded-2xl transition-all hover:shadow-md ${ + selectedPreset === preset.value + ? 'border-blue-500' + : 'border-gray-200 hover:border-gray-300' + }`} bodyStyle={{ textAlign: 'center' }} > -
- +
+ {formatLargeNumber(preset.value)}
-
- {t('实付')} ¥{(preset.value * priceRatio).toFixed(2)} +
+ {t('实付')} ¥ + {(preset.value * priceRatio).toFixed(2)}
))}
{/* 桌面端显示的自定义金额和支付按钮 */} -
+
- {t('或输入自定义金额')} + + {t('或输入自定义金额')} +
-
+
{t('充值数量')} {amountLoading ? ( - + ) : ( - {t('实付金额:') + renderAmount()} + + {t('实付金额:') + renderAmount()} + )}
{ getAmount(1); } }} - size="large" - className="w-full" - formatter={(value) => value ? `${value}` : ''} - parser={(value) => value ? parseInt(value.replace(/[^\d]/g, '')) : 0} + size='large' + className='w-full' + formatter={(value) => (value ? `${value}` : '')} + parser={(value) => + value ? parseInt(value.replace(/[^\d]/g, '')) : 0 + } />
-
- + {t('微信')} + */} + {payMethods.map((payMethod) => ( + + ))}
@@ -613,39 +716,41 @@ const TopUp = () => { {!enableOnlineTopUp && ( )} - {t('兑换码充值')} + {t('兑换码充值')} - -
- + +
+ {t('使用兑换码快速充值')}
-
+
setRedemptionCode(value)} - size="large" + size='large' />
-
+
{topUpLink && ( )}
{/* 右侧邀请信息卡片 */} -
+
-
-
+
+
+
@@ -689,7 +794,7 @@ const TopUp = () => { {t('邀请奖励')} - + {t('邀请好友获得额外奖励')}
@@ -698,53 +803,56 @@ const TopUp = () => {
} > -
-
- -
- {t('待使用收益')} +
+
+ +
+ {t('待使用收益')}
-
+
{renderQuota(userState?.user?.aff_quota || 0)}
-
- - {t('总收益')} -
+
+ + {t('总收益')} +
{renderQuota(userState?.user?.aff_history_quota || 0)}
- - {t('邀请人数')} -
- + + {t('邀请人数')} +
+ {userState?.user?.aff_count || 0}
-
+
{t('邀请链接')} } > @@ -753,24 +861,24 @@ const TopUp = () => { } /> -
- -
-
-
- +
+ +
+
+
+ {t('邀请好友注册,好友充值后您可获得相应奖励')}
-
-
- +
+
+ {t('通过划转功能将奖励额度转入到您的账户余额中')}
-
-
- +
+
+ {t('邀请的好友越多,获得的奖励越多')}
@@ -785,20 +893,27 @@ const TopUp = () => { {/* 移动端底部固定的自定义金额和支付区域 */} {enableOnlineTopUp && ( -
-
+
+
-
+
{t('充值数量')} {amountLoading ? ( ) : ( - {t('实付金额:') + renderAmount()} + + {t('实付金额:') + renderAmount()} + )}
{ getAmount(1); } }} - className="w-full" - formatter={(value) => value ? `${value}` : ''} - parser={(value) => value ? parseInt(value.replace(/[^\d]/g, '')) : 0} + className='w-full' + formatter={(value) => (value ? `${value}` : '')} + parser={(value) => + value ? parseInt(value.replace(/[^\d]/g, '')) : 0 + } />
-
- + {t('微信')} + */} + {payMethods.map((payMethod) => ( + + ))}