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
>
-
+
@@ -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
+ }
/>
-
- */}
+ {payMethods.map((payMethod) => (
+ preTopUp(payMethod.type)}
+ size='large'
+ disabled={!enableOnlineTopUp}
+ loading={paymentLoading && payWay === payMethod.type}
+ icon={
+ payMethod.type === 'zfb' ? (
+
+ ) : payMethod.type === 'wx' ? (
+
+ ) : (
+
+ )
+ }
+ style={{
+ height: '44px',
+ color: payMethod.color,
+ }}
+ >
+ {payMethod.name}
+
+ ))}
>
@@ -613,39 +716,41 @@ const TopUp = () => {
{!enableOnlineTopUp && (
)}
- {t('兑换码充值')}
+ {t('兑换码充值')}
-
-
-
+
+
+
{t('使用兑换码快速充值')}
-
+
setRedemptionCode(value)}
- size="large"
+ size='large'
/>
-
+
{topUpLink && (
}
style={{ height: '40px' }}
>
@@ -653,12 +758,12 @@ const TopUp = () => {
)}
{isSubmitting ? t('兑换中...') : t('兑换')}
@@ -670,18 +775,18 @@ const TopUp = () => {
{/* 右侧邀请信息卡片 */}
-
+
-
-
+
+
+
@@ -689,7 +794,7 @@ const TopUp = () => {
{t('邀请奖励')}
-
+
{t('邀请好友获得额外奖励')}
@@ -698,53 +803,56 @@ const TopUp = () => {
}
>
-
-
-
-
-
{t('待使用收益')}
+
+
+
+
+ {t('待使用收益')}
setOpenTransfer(true)}
>
{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
+ }
/>
-
-
+ {/* preTopUp('zfb')}
disabled={!enableOnlineTopUp}
loading={paymentLoading && payWay === 'zfb'}
icon={}
>
- {t('支付宝')}
+ {t('支付宝')}
preTopUp('wx')}
disabled={!enableOnlineTopUp}
loading={paymentLoading && payWay === 'wx'}
icon={}
>
- {t('微信')}
-
+ {t('微信')}
+ */}
+ {payMethods.map((payMethod) => (
+ preTopUp(payMethod.type)}
+ disabled={!enableOnlineTopUp}
+ loading={paymentLoading && payWay === payMethod.type}
+ icon={
+ payMethod.type === 'zfb' ? (
+
+ ) : payMethod.type === 'wx' ? (
+
+ ) : (
+
+ )
+ }
+ style={{
+ color: payMethod.color,
+ }}
+ >
+ {payMethod.name}
+
+ ))}