import { useState, useEffect, useContext } from 'react'; import SubCard from 'ui-component/cards/SubCard'; import { Stack, FormControl, InputLabel, OutlinedInput, Checkbox, Button, FormControlLabel, TextField, Alert } from '@mui/material'; import { showSuccess, showError, verifyJSON } from 'utils/common'; import { API } from 'utils/api'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker'; import ChatLinksDataGrid from './ChatLinksDataGrid'; import dayjs from 'dayjs'; import { LoadStatusContext } from 'contexts/StatusContext'; require('dayjs/locale/zh-cn'); const OperationSetting = () => { let now = new Date(); let [inputs, setInputs] = useState({ QuotaForNewUser: 0, QuotaForInviter: 0, QuotaForInvitee: 0, QuotaRemindThreshold: 0, PreConsumedQuota: 0, GroupRatio: '', TopUpLink: '', ChatLink: '', ChatLinks: '', QuotaPerUnit: 0, AutomaticDisableChannelEnabled: '', AutomaticEnableChannelEnabled: '', ChannelDisableThreshold: 0, LogConsumeEnabled: '', DisplayInCurrencyEnabled: '', DisplayTokenStatEnabled: '', ApproximateTokenEnabled: '', RetryTimes: 0, RetryCooldownSeconds: 0, MjNotifyEnabled: '', ChatCacheEnabled: '', ChatCacheExpireMinute: 5, ChatImageRequestProxy: '', PaymentUSDRate: 0, PaymentMinAmount: 1 }); const [originInputs, setOriginInputs] = useState({}); let [loading, setLoading] = useState(false); let [historyTimestamp, setHistoryTimestamp] = useState(now.getTime() / 1000 - 30 * 24 * 3600); // a month ago new Date().getTime() / 1000 + 3600 const loadStatus = useContext(LoadStatusContext); const getOptions = async () => { try { const res = await API.get('/api/option/'); const { success, message, data } = res.data; if (success) { let newInputs = {}; data.forEach((item) => { if (item.key === 'GroupRatio') { item.value = JSON.stringify(JSON.parse(item.value), null, 2); } newInputs[item.key] = item.value; }); setInputs(newInputs); setOriginInputs(newInputs); } else { showError(message); } } catch (error) { return; } }; useEffect(() => { getOptions().then(); }, []); const updateOption = async (key, value) => { setLoading(true); if (key.endsWith('Enabled')) { value = inputs[key] === 'true' ? 'false' : 'true'; } try { const res = await API.put('/api/option/', { key, value }); const { success, message } = res.data; if (success) { setInputs((inputs) => ({ ...inputs, [key]: value })); getOptions(); await loadStatus(); } else { showError(message); } } catch (error) { return; } setLoading(false); }; const handleInputChange = async (event) => { let { name, value } = event.target; if (name.endsWith('Enabled')) { await updateOption(name, value); showSuccess('设置成功!'); } else { setInputs((inputs) => ({ ...inputs, [name]: value })); } }; const submitConfig = async (group) => { switch (group) { case 'monitor': if (originInputs['ChannelDisableThreshold'] !== inputs.ChannelDisableThreshold) { await updateOption('ChannelDisableThreshold', inputs.ChannelDisableThreshold); } if (originInputs['QuotaRemindThreshold'] !== inputs.QuotaRemindThreshold) { await updateOption('QuotaRemindThreshold', inputs.QuotaRemindThreshold); } break; case 'ratio': if (originInputs['GroupRatio'] !== inputs.GroupRatio) { if (!verifyJSON(inputs.GroupRatio)) { showError('分组倍率不是合法的 JSON 字符串'); return; } await updateOption('GroupRatio', inputs.GroupRatio); } break; case 'chatlinks': if (originInputs['ChatLinks'] !== inputs.ChatLinks) { if (!verifyJSON(inputs.ChatLinks)) { showError('links不是合法的 JSON 字符串'); return; } await updateOption('ChatLinks', inputs.ChatLinks); } break; case 'quota': if (originInputs['QuotaForNewUser'] !== inputs.QuotaForNewUser) { await updateOption('QuotaForNewUser', inputs.QuotaForNewUser); } if (originInputs['QuotaForInvitee'] !== inputs.QuotaForInvitee) { await updateOption('QuotaForInvitee', inputs.QuotaForInvitee); } if (originInputs['QuotaForInviter'] !== inputs.QuotaForInviter) { await updateOption('QuotaForInviter', inputs.QuotaForInviter); } if (originInputs['PreConsumedQuota'] !== inputs.PreConsumedQuota) { await updateOption('PreConsumedQuota', inputs.PreConsumedQuota); } break; case 'general': if (inputs.QuotaPerUnit < 0 || inputs.RetryTimes < 0 || inputs.RetryCooldownSeconds < 0) { showError('单位额度、重试次数、冷却时间不能为负数'); return; } if (originInputs['TopUpLink'] !== inputs.TopUpLink) { await updateOption('TopUpLink', inputs.TopUpLink); } if (originInputs['ChatLink'] !== inputs.ChatLink) { await updateOption('ChatLink', inputs.ChatLink); } if (originInputs['QuotaPerUnit'] !== inputs.QuotaPerUnit) { await updateOption('QuotaPerUnit', inputs.QuotaPerUnit); } if (originInputs['RetryTimes'] !== inputs.RetryTimes) { await updateOption('RetryTimes', inputs.RetryTimes); } if (originInputs['RetryCooldownSeconds'] !== inputs.RetryCooldownSeconds) { await updateOption('RetryCooldownSeconds', inputs.RetryCooldownSeconds); } break; case 'other': if (originInputs['ChatCacheExpireMinute'] !== inputs.ChatCacheExpireMinute) { await updateOption('ChatCacheExpireMinute', inputs.ChatCacheExpireMinute); } if (originInputs['ChatImageRequestProxy'] !== inputs.ChatImageRequestProxy) { await updateOption('ChatImageRequestProxy', inputs.ChatImageRequestProxy); } break; case 'payment': if (originInputs['PaymentUSDRate'] !== inputs.PaymentUSDRate) { await updateOption('PaymentUSDRate', inputs.PaymentUSDRate); } if (originInputs['PaymentMinAmount'] !== inputs.PaymentMinAmount) { await updateOption('PaymentMinAmount', inputs.PaymentMinAmount); } break; } showSuccess('保存成功!'); }; const deleteHistoryLogs = async () => { try { const res = await API.delete(`/api/log/?target_timestamp=${Math.floor(historyTimestamp)}`); const { success, message, data } = res.data; if (success) { showSuccess(`${data} 条日志已清理!`); return; } showError('日志清理失败:' + message); } catch (error) { return; } }; return ( 充值链接 聊天链接 单位额度 重试次数 重试间隔(秒) } /> } /> } /> } /> } /> 缓存时间(分钟) 当用户使用vision模型并提供了图片链接时,我们的服务器需要下载这些图片并计算 tokens。为了在下载图片时保护服务器的 IP 地址不被泄露,可以在下方配置一个代理。这个代理配置使用的是 HTTP 或 SOCKS5 代理。如果你是个人用户,这个配置可以不用理会。代理格式为 http://127.0.0.1:1080 或 socks5://127.0.0.1:1080 图片检测代理 } /> { setHistoryTimestamp(newValue === null ? null : newValue.unix()); }} slotProps={{ actionBar: { actions: ['today', 'clear', 'accept'] } }} /> 最长响应时间 额度提醒阈值 } /> } /> 新用户初始额度 请求预扣费额度 邀请新用户奖励额度 新用户使用邀请码奖励额度 支付设置:
1. 美元汇率:用于计算充值金额的美元金额
2. 最低充值金额(美元):最低充值金额,单位为美元,填写整数
3. 页面都以美元为单位计算,实际用户支付的货币,按照支付网关设置的货币进行转换
例如: A 网关设置货币为 CNY,用户支付 100 美元,那么实际支付金额为 100 * 美元汇率
B 网关设置货币为 USD,用户支付 100 美元,那么实际支付金额为 100 美元
美元汇率 最低充值金额(美元)
配置聊天链接,该配置在令牌中的聊天生效以及首页的Playground中的聊天生效.
链接中可以使{'{key}'}替换用户的令牌,{'{server}'}替换服务器地址。例如: {'https://chat.oneapi.pro/#/?settings={"key":"sk-{key}","url":"{server}"}'}
如果未配置,会默认配置以下4个链接:
ChatGPT Next : {'https://chat.oneapi.pro/#/?settings={"key":"{key}","url":"{server}"}'}
chatgpt-web-midjourney-proxy : {'https://vercel.ddaiai.com/#/?settings={"key":"{key}","url":"{server}"}'}
AMA 问天 : {'ama://set-api-key?server={server}&key={key}'}
opencat : {'opencat://team/join?domain={server}&token={key}'}
排序规则:值越大越靠前,值相同则按照配置顺序
); }; export default OperationSetting;