2023-08-14 22:16:32 +08:00
import React , { useEffect , useState } from 'react' ;
import { Divider , Form , Grid , Header } from 'semantic-ui-react' ;
2023-09-17 20:59:12 +08:00
import { API , showError , showSuccess , timestamp2string , verifyJSON } from '../helpers' ;
2023-06-19 09:53:56 +08:00
const OperationSetting = ( ) => {
2023-09-17 20:59:12 +08:00
let now = new Date ( ) ; let [ inputs , setInputs ] = useState ( {
2023-08-14 22:16:32 +08:00
QuotaForNewUser : 0 ,
QuotaForInviter : 0 ,
QuotaForInvitee : 0 ,
QuotaRemindThreshold : 0 ,
PreConsumedQuota : 0 ,
ModelRatio : '' ,
GroupRatio : '' ,
TopUpLink : '' ,
ChatLink : '' ,
QuotaPerUnit : 0 ,
AutomaticDisableChannelEnabled : '' ,
ChannelDisableThreshold : 0 ,
LogConsumeEnabled : '' ,
DisplayInCurrencyEnabled : '' ,
DisplayTokenStatEnabled : '' ,
2023-09-17 20:59:12 +08:00
RetryTimes : 0
2023-08-14 22:16:32 +08:00
} ) ;
const [ originInputs , setOriginInputs ] = useState ( { } ) ;
2023-09-17 20:59:12 +08:00
let [ loading , setLoading ] = useState ( false ) ; let [ historyTimestamp , setHistoryTimestamp ] = useState ( timestamp2string ( now . getTime ( ) / 1000 - 30 * 24 * 3600 ) ) ; // a month ago
2023-06-19 09:53:56 +08:00
const getOptions = async ( ) => {
const res = await API . get ( '/api/option/' ) ;
const { success , message , data } = res . data ;
if ( success ) {
let newInputs = { } ;
data . forEach ( ( item ) => {
if ( item . key === 'ModelRatio' || 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 ) ;
}
} ;
2023-08-14 22:16:32 +08:00
useEffect ( ( ) => {
getOptions ( ) . then ( ) ;
} , [ ] ) ;
2023-06-19 09:53:56 +08:00
2023-08-14 22:16:32 +08:00
const updateOption = async ( key , value ) => {
setLoading ( true ) ;
if ( key . endsWith ( 'Enabled' ) ) {
value = inputs [ key ] === 'true' ? 'false' : 'true' ;
2023-06-19 09:53:56 +08:00
}
2023-08-14 22:16:32 +08:00
const res = await API . put ( '/api/option/' , {
key ,
value
} ) ;
const { success , message } = res . data ;
if ( success ) {
setInputs ( ( inputs ) => ( { ... inputs , [ key ] : value } ) ) ;
} else {
showError ( message ) ;
2023-06-19 09:53:56 +08:00
}
2023-08-14 22:16:32 +08:00
setLoading ( false ) ;
} ;
const handleInputChange = async ( e , { name , value } ) => {
if ( name . endsWith ( 'Enabled' ) ) {
await updateOption ( name , value ) ;
} else {
setInputs ( ( inputs ) => ( { ... inputs , [ name ] : value } ) ) ;
2023-06-20 20:09:17 +08:00
}
2023-08-14 22:16:32 +08:00
} ;
2023-06-19 09:53:56 +08:00
const submitConfig = async ( group ) => {
switch ( group ) {
case 'monitor' :
if ( originInputs [ 'ChannelDisableThreshold' ] !== inputs . ChannelDisableThreshold ) {
await updateOption ( 'ChannelDisableThreshold' , inputs . ChannelDisableThreshold ) ;
2023-07-15 19:06:51 +08:00
}
2023-06-19 09:53:56 +08:00
if ( originInputs [ 'QuotaRemindThreshold' ] !== inputs . QuotaRemindThreshold ) {
await updateOption ( 'QuotaRemindThreshold' , inputs . QuotaRemindThreshold ) ;
}
break ;
case 'ratio' :
if ( originInputs [ 'ModelRatio' ] !== inputs . ModelRatio ) {
if ( ! verifyJSON ( inputs . ModelRatio ) ) {
showError ( '模型倍率不是合法的 JSON 字符串' ) ;
return ;
}
await updateOption ( 'ModelRatio' , inputs . ModelRatio ) ;
}
if ( originInputs [ 'GroupRatio' ] !== inputs . GroupRatio ) {
if ( ! verifyJSON ( inputs . GroupRatio ) ) {
showError ( '分组倍率不是合法的 JSON 字符串' ) ;
return ;
}
await updateOption ( 'GroupRatio' , inputs . GroupRatio ) ;
}
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 ( originInputs [ 'TopUpLink' ] !== inputs . TopUpLink ) {
await updateOption ( 'TopUpLink' , inputs . TopUpLink ) ;
}
if ( originInputs [ 'ChatLink' ] !== inputs . ChatLink ) {
await updateOption ( 'ChatLink' , inputs . ChatLink ) ;
}
2023-06-20 20:09:17 +08:00
if ( originInputs [ 'QuotaPerUnit' ] !== inputs . QuotaPerUnit ) {
await updateOption ( 'QuotaPerUnit' , inputs . QuotaPerUnit ) ;
}
2023-07-15 19:06:51 +08:00
if ( originInputs [ 'RetryTimes' ] !== inputs . RetryTimes ) {
await updateOption ( 'RetryTimes' , inputs . RetryTimes ) ;
}
2023-06-19 09:53:56 +08:00
break ;
}
} ;
2023-09-17 20:59:12 +08:00
const deleteHistoryLogs = async ( ) => {
2023-09-17 17:09:56 +08:00
console . log ( inputs ) ;
const res = await API . delete ( ` /api/log/?target_timestamp= ${ Date . parse ( historyTimestamp ) / 1000 } ` ) ;
const { success , message , data } = res . data ;
if ( success ) {
showSuccess ( ` ${ data } 条日志已清理! ` ) ;
return ;
}
showError ( '日志清理失败:' + message ) ;
2023-09-17 20:59:12 +08:00
} ; return (
2023-08-14 22:16:32 +08:00
< Grid columns = { 1 } >
< Grid . Column >
< Form loading = { loading } >
< Header as = 'h3' >
通用设置
< / H e a d e r >
< Form . Group widths = { 4 } >
< Form . Input
label = '充值链接'
name = 'TopUpLink'
onChange = { handleInputChange }
autoComplete = 'new-password'
value = { inputs . TopUpLink }
type = 'link'
placeholder = '例如发卡网站的购买链接'
/ >
< Form . Input
label = '聊天页面链接'
name = 'ChatLink'
onChange = { handleInputChange }
autoComplete = 'new-password'
value = { inputs . ChatLink }
type = 'link'
placeholder = '例如 ChatGPT Next Web 的部署地址'
/ >
< Form . Input
label = '单位美元额度'
name = 'QuotaPerUnit'
onChange = { handleInputChange }
autoComplete = 'new-password'
value = { inputs . QuotaPerUnit }
type = 'number'
step = '0.01'
placeholder = '一单位货币能兑换的额度'
/ >
< Form . Input
label = '失败重试次数'
name = 'RetryTimes'
type = { 'number' }
step = '1'
min = '0'
onChange = { handleInputChange }
autoComplete = 'new-password'
value = { inputs . RetryTimes }
placeholder = '失败重试次数'
/ >
< / F o r m . G r o u p >
< Form . Group inline >
2023-09-17 17:09:56 +08:00
2023-08-14 22:16:32 +08:00
< Form . Checkbox
checked = { inputs . DisplayInCurrencyEnabled === 'true' }
label = '以货币形式显示额度'
name = 'DisplayInCurrencyEnabled'
onChange = { handleInputChange }
/ >
< Form . Checkbox
checked = { inputs . DisplayTokenStatEnabled === 'true' }
label = 'Billing 相关 API 显示令牌额度而非用户额度'
name = 'DisplayTokenStatEnabled'
onChange = { handleInputChange }
/ >
< / F o r m . G r o u p >
< Form . Button onClick = { ( ) => {
submitConfig ( 'general' ) . then ( ) ;
2023-09-17 20:59:12 +08:00
} } > 保存通用设置 < /Form.Button><Divider / >
2023-09-17 17:09:56 +08:00
< Header as = 'h3' >
日志设置
< / H e a d e r >
< Form . Group inline >
< Form . Checkbox
checked = { inputs . LogConsumeEnabled === 'true' }
label = '启用额度消费日志记录'
name = 'LogConsumeEnabled'
onChange = { handleInputChange }
/ >
< / F o r m . G r o u p >
< Form . Group widths = { 4 } >
< Form . Input label = '目标时间' value = { historyTimestamp } type = 'datetime-local'
name = 'history_timestamp'
onChange = { ( e , { name , value } ) => {
setHistoryTimestamp ( value ) ;
} } / >
< / F o r m . G r o u p >
< Form . Button onClick = { ( ) => {
deleteHistoryLogs ( ) . then ( ) ;
} } > 清理历史日志 < / F o r m . B u t t o n >
2023-08-14 22:16:32 +08:00
< Divider / >
< Header as = 'h3' >
监控设置
< / H e a d e r >
< Form . Group widths = { 3 } >
< Form . Input
label = '最长响应时间'
name = 'ChannelDisableThreshold'
onChange = { handleInputChange }
autoComplete = 'new-password'
value = { inputs . ChannelDisableThreshold }
type = 'number'
min = '0'
placeholder = '单位秒,当运行通道全部测试时,超过此时间将自动禁用通道'
/ >
< Form . Input
label = '额度提醒阈值'
name = 'QuotaRemindThreshold'
onChange = { handleInputChange }
autoComplete = 'new-password'
value = { inputs . QuotaRemindThreshold }
type = 'number'
min = '0'
placeholder = '低于此额度时将发送邮件提醒用户'
/ >
< / F o r m . G r o u p >
< Form . Group inline >
< Form . Checkbox
checked = { inputs . AutomaticDisableChannelEnabled === 'true' }
label = '失败时自动禁用通道'
name = 'AutomaticDisableChannelEnabled'
onChange = { handleInputChange }
/ >
< / F o r m . G r o u p >
< Form . Button onClick = { ( ) => {
submitConfig ( 'monitor' ) . then ( ) ;
} } > 保存监控设置 < / F o r m . B u t t o n >
< Divider / >
< Header as = 'h3' >
额度设置
< / H e a d e r >
< Form . Group widths = { 4 } >
< Form . Input
label = '新用户初始额度'
name = 'QuotaForNewUser'
onChange = { handleInputChange }
autoComplete = 'new-password'
value = { inputs . QuotaForNewUser }
type = 'number'
min = '0'
placeholder = '例如: 100'
/ >
< Form . Input
label = '请求预扣费额度'
name = 'PreConsumedQuota'
onChange = { handleInputChange }
autoComplete = 'new-password'
value = { inputs . PreConsumedQuota }
type = 'number'
min = '0'
placeholder = '请求结束后多退少补'
/ >
< Form . Input
label = '邀请新用户奖励额度'
name = 'QuotaForInviter'
onChange = { handleInputChange }
autoComplete = 'new-password'
value = { inputs . QuotaForInviter }
type = 'number'
min = '0'
placeholder = '例如: 2000'
/ >
< Form . Input
label = '新用户使用邀请码奖励额度'
name = 'QuotaForInvitee'
onChange = { handleInputChange }
autoComplete = 'new-password'
value = { inputs . QuotaForInvitee }
type = 'number'
min = '0'
placeholder = '例如: 1000'
/ >
< / F o r m . G r o u p >
< Form . Button onClick = { ( ) => {
submitConfig ( 'quota' ) . then ( ) ;
} } > 保存额度设置 < / F o r m . B u t t o n >
< Divider / >
< Header as = 'h3' >
倍率设置
< / H e a d e r >
< Form . Group widths = 'equal' >
< Form . TextArea
label = '模型倍率'
name = 'ModelRatio'
onChange = { handleInputChange }
style = { { minHeight : 250 , fontFamily : 'JetBrains Mono, Consolas' } }
autoComplete = 'new-password'
value = { inputs . ModelRatio }
placeholder = '为一个 JSON 文本,键为模型名称,值为倍率'
/ >
< / F o r m . G r o u p >
< Form . Group widths = 'equal' >
< Form . TextArea
label = '分组倍率'
name = 'GroupRatio'
onChange = { handleInputChange }
style = { { minHeight : 250 , fontFamily : 'JetBrains Mono, Consolas' } }
autoComplete = 'new-password'
value = { inputs . GroupRatio }
placeholder = '为一个 JSON 文本,键为分组名称,值为倍率'
/ >
< / F o r m . G r o u p >
< Form . Button onClick = { ( ) => {
submitConfig ( 'ratio' ) . then ( ) ;
} } > 保存倍率设置 < / F o r m . B u t t o n >
< / F o r m >
< / G r i d . C o l u m n >
< / G r i d >
)
;
2023-06-19 09:53:56 +08:00
} ;
export default OperationSetting ;