Compare commits

...

21 Commits

Author SHA1 Message Date
CalciumIon
ed2ec69545 feat: 暂时禁用透传功能 2024-11-19 15:06:54 +08:00
CalciumIon
a167dd9a23 feat: 暂时禁用透传功能 2024-11-19 15:04:19 +08:00
CalciumIon
334a2424e9 fix: oauth aff 2024-11-18 18:53:55 +08:00
CalciumIon
7db703374c fix: oauth aff 2024-11-18 18:52:14 +08:00
Calcium-Ion
6a42ccf00e Merge pull request #569 from utopeadia/main
Modify the default gemini API to v1beta
2024-11-17 16:27:38 +08:00
Calcium-Ion
7aa7114bb9 Merge branch 'main' into main 2024-11-17 16:27:29 +08:00
Calcium-Ion
c3e6b2408e Merge pull request #570 from leezhuuuuu/main
增加对于gemini-exp-1114模型的支持,映射到v1beta
2024-11-17 16:26:36 +08:00
leezhuuuuu
4601932902 feat: add support for gemini-exp-1114 model / 添加 gemini-exp-1114 模型支持
# feat: add support for gemini-exp-1114 model / 添加 gemini-exp-1114 模型支持

## Changes / 更改内容
- Add gemini-exp-1114 to ModelList in constant.go
- Add gemini-exp-1114 to GeminiModelMap with v1beta API version
- 在 constant.go 的 ModelList 中添加 gemini-exp-1114 模型
- 在 GeminiModelMap 中添加 gemini-exp-1114 的 v1beta API 版本映射

## Testing / 测试情况
- [x] Tested gemini-exp-1114 model API calls / 已测试 gemini-exp-1114 模型的 API 调用
- [x] Verified existing models still work / 验证现有模型仍然正常工作
- [x] Confirmed v1beta API version works correctly / 确认 v1beta API 版本正常工作

## Related Issues / 相关问题
- Fix 404 error when calling gemini-exp-1114 model / 修复调用 gemini-exp-1114 模型时的 404 错误

## Implementation Details / 实现细节
- Use configuration-based approach instead of code modification / 使用基于配置的方式而不是修改代码
- Maintain clean separation of concerns / 保持关注点分离
- Keep backward compatibility / 保持向后兼容性

## Notes / 注意事项
- This PR follows the principle of minimal invasion / 本 PR 遵循最小侵入原则
- Configuration changes only / 仅包含配置更改
2024-11-16 21:52:37 +08:00
leezhuuuuu
5d96f7b2cc 增加对于gemini-exp-1114模型的支持,映射到v1beta
feat(gemini): add support for gemini-exp-1114 model

- Add gemini-exp-1114 to ModelList in constant.go
- Update GetRequestURL in adaptor.go to use v1beta API version for gemini-exp-1114
- Keep backward compatibility for other models

This change enables the use of the experimental gemini-exp-1114 model by correctly routing its requests to the v1beta API endpoint while maintaining existing functionality for other models.
2024-11-16 21:29:35 +08:00
HowieWood
8eb32e9b3f Modify the default gemini API to v1beta 2024-11-16 12:21:50 +00:00
CalciumIon
320e6ec5a4 fix: aws claude 2024-11-14 15:12:34 +08:00
Calcium-Ion
8baeece386 Merge pull request #564 from Licoy/main
优化页面组件大小规格一致
2024-11-12 22:39:34 +08:00
licoy
08023f6d96 feat: 增加GLOBAL_API_RATE_LIMIT_ENABLEGLOBAL_WEB_RATE_LIMIT_ENABLE环境变量,支持是否开启访问速率控制 2024-11-12 20:02:33 +08:00
licoy
fad29a8cc2 feat: 增加GLOBAL_API_RATE_LIMIT_DURATIONGLOBAL_WEB_RATE_LIMIT_DURATION环境变量,支持控制访问速率时间设置 2024-11-12 20:01:43 +08:00
licoy
67d09d68c6 feat: 优化数据管理操作栏均为顶部 2024-11-12 17:00:06 +08:00
licoy
cdc02f660b feat: 优化switch组件的大小规格与整体表单一致 2024-11-12 16:32:40 +08:00
licoy
674abe5ae2 feat: 统一运营设置页面的保存按钮大小规格 2024-11-12 16:30:51 +08:00
Calcium-Ion
0b0bcbab80 Merge pull request #563 from Licoy/main
封装OAuth2授权回调页面、修复独立日志数据库查询令牌日志时错误问题
2024-11-12 16:27:46 +08:00
licoy
450bea8f2c 修复独立日志数据库查询令牌日志时错误问题 2024-11-12 16:22:13 +08:00
licoy
bf75df8f04 优化设置页面的模块间距与部分数据获取提示 2024-11-12 16:17:55 +08:00
licoy
c6dae4b879 封装OAuth2授权回调页面 2024-11-12 16:11:38 +08:00
35 changed files with 3596 additions and 3845 deletions

View File

@@ -53,7 +53,7 @@
# Gemini 安全设置
# GEMINI_SAFETY_SETTING=BLOCK_NONE
# Gemini版本设置
# GEMINI_MODEL_MAP=gemini-1.5-pro-latest:v1beta,gemini-1.5-pro-001:v1beta
# GEMINI_MODEL_MAP=gemini-1.0-pro:v1
# Cohere 安全设置
# COHERE_SAFETY_SETTING=NONE
# 是否统计图片token

View File

@@ -74,7 +74,7 @@
- `GET_MEDIA_TOKEN`是统计图片token默认为 `true`关闭后将不再在本地计算图片token可能会导致和上游计费不同此项覆盖 `GET_MEDIA_TOKEN_NOT_STREAM` 选项作用。
- `GET_MEDIA_TOKEN_NOT_STREAM`:是否在非流(`stream=false`情况下统计图片token默认为 `true`
- `UPDATE_TASK`是否更新异步任务Midjourney、Suno默认为 `true`,关闭后将不会更新任务进度。
- `GEMINI_MODEL_MAP`Gemini模型指定版本(v1/v1beta),使用“模型:版本”指定,","分隔,例如:-e GEMINI_MODEL_MAP="gemini-1.5-pro-latest:v1beta,gemini-1.5-pro-001:v1beta",为空则使用默认配置
- `GEMINI_MODEL_MAP`Gemini模型指定版本(v1/v1beta),使用“模型:版本”指定,","分隔,例如:-e GEMINI_MODEL_MAP="gemini-1.5-pro-latest:v1beta,gemini-1.5-pro-001:v1beta",为空则使用默认配置(v1beta)
- `COHERE_SAFETY_SETTING`Cohere模型[安全设置](https://docs.cohere.com/docs/safety-modes#overview),可选值为 `NONE`, `CONTEXTUAL``STRICT`,默认为 `NONE`
## 部署
### 部署要求

View File

@@ -144,11 +144,13 @@ var (
// All duration's unit is seconds
// Shouldn't larger then RateLimitKeyExpirationDuration
var (
GlobalApiRateLimitNum = GetEnvOrDefault("GLOBAL_API_RATE_LIMIT", 180)
GlobalApiRateLimitDuration int64 = 3 * 60
GlobalApiRateLimitEnable = GetEnvOrDefaultBool("GLOBAL_API_RATE_LIMIT_ENABLE", true)
GlobalApiRateLimitNum = GetEnvOrDefault("GLOBAL_API_RATE_LIMIT", 180)
GlobalApiRateLimitDuration = int64(GetEnvOrDefault("GLOBAL_API_RATE_LIMIT_DURATION", 180))
GlobalWebRateLimitNum = GetEnvOrDefault("GLOBAL_WEB_RATE_LIMIT", 60)
GlobalWebRateLimitDuration int64 = 3 * 60
GlobalWebRateLimitEnable = GetEnvOrDefaultBool("GLOBAL_WEB_RATE_LIMIT_ENABLE", true)
GlobalWebRateLimitNum = GetEnvOrDefault("GLOBAL_WEB_RATE_LIMIT", 60)
GlobalWebRateLimitDuration = int64(GetEnvOrDefault("GLOBAL_WEB_RATE_LIMIT_DURATION", 180))
UploadRateLimitNum = 10
UploadRateLimitDuration int64 = 60

View File

@@ -20,16 +20,7 @@ var GetMediaTokenNotStream = common.GetEnvOrDefaultBool("GET_MEDIA_TOKEN_NOT_STR
var UpdateTask = common.GetEnvOrDefaultBool("UPDATE_TASK", true)
var GeminiModelMap = map[string]string{
"gemini-1.5-pro-latest": "v1beta",
"gemini-1.5-pro-001": "v1beta",
"gemini-1.5-pro": "v1beta",
"gemini-1.5-pro-exp-0801": "v1beta",
"gemini-1.5-pro-exp-0827": "v1beta",
"gemini-1.5-flash-latest": "v1beta",
"gemini-1.5-flash-exp-0827": "v1beta",
"gemini-1.5-flash-001": "v1beta",
"gemini-1.5-flash": "v1beta",
"gemini-ultra": "v1beta",
"gemini-1.0-pro": "v1",
}
func InitEnv() {

View File

@@ -142,8 +142,13 @@ func GitHubOAuth(c *gin.Context) {
user.Email = githubUser.Email
user.Role = common.RoleCommonUser
user.Status = common.UserStatusEnabled
affCode := session.Get("aff")
inviterId := 0
if affCode != nil {
inviterId, _ = model.GetUserIdByAffCode(affCode.(string))
}
if err := user.Insert(0); err != nil {
if err := user.Insert(inviterId); err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
@@ -227,6 +232,10 @@ func GitHubBind(c *gin.Context) {
func GenerateOAuthCode(c *gin.Context) {
session := sessions.Default(c)
state := common.GetRandomString(12)
affCode := c.Query("aff")
if affCode != "" {
session.Set("aff", affCode)
}
session.Set("oauth_state", state)
err := session.Save()
if err != nil {

View File

@@ -237,7 +237,13 @@ func LinuxdoOAuth(c *gin.Context) {
user.Role = common.RoleCommonUser
user.Status = common.UserStatusEnabled
if err := user.Insert(0); err != nil {
affCode := session.Get("aff")
inviterId := 0
if affCode != nil {
inviterId, _ = model.GetUserIdByAffCode(affCode.(string))
}
if err := user.Insert(inviterId); err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),

View File

@@ -13,6 +13,10 @@ var timeFormat = "2006-01-02T15:04:05.000Z"
var inMemoryRateLimiter common.InMemoryRateLimiter
var defNext = func(c *gin.Context) {
c.Next()
}
func redisRateLimiter(c *gin.Context, maxRequestNum int, duration int64, mark string) {
ctx := context.Background()
rdb := common.RDB
@@ -83,11 +87,17 @@ func rateLimitFactory(maxRequestNum int, duration int64, mark string) func(c *gi
}
func GlobalWebRateLimit() func(c *gin.Context) {
return rateLimitFactory(common.GlobalWebRateLimitNum, common.GlobalWebRateLimitDuration, "GW")
if common.GlobalWebRateLimitEnable {
return rateLimitFactory(common.GlobalWebRateLimitNum, common.GlobalWebRateLimitDuration, "GW")
}
return defNext
}
func GlobalAPIRateLimit() func(c *gin.Context) {
return rateLimitFactory(common.GlobalApiRateLimitNum, common.GlobalApiRateLimitDuration, "GA")
if common.GlobalApiRateLimitEnable {
return rateLimitFactory(common.GlobalApiRateLimitNum, common.GlobalApiRateLimitDuration, "GA")
}
return defNext
}
func CriticalRateLimit() func(c *gin.Context) {

View File

@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"one-api/common"
"os"
"strings"
"time"
@@ -39,7 +40,15 @@ const (
)
func GetLogByKey(key string) (logs []*Log, err error) {
err = LOG_DB.Joins("left join tokens on tokens.id = logs.token_id").Where("tokens.key = ?", strings.TrimPrefix(key, "sk-")).Find(&logs).Error
if os.Getenv("LOG_SQL_DSN") != "" {
var tk Token
if err = DB.Model(&Token{}).Where("`key`=?", strings.TrimPrefix(key, "sk-")).First(&tk).Error; err != nil {
return nil, err
}
err = LOG_DB.Model(&Log{}).Where("token_id=?", tk.Id).Find(&logs).Error
} else {
err = LOG_DB.Joins("left join tokens on tokens.id = logs.token_id").Where("tokens.key = ?", strings.TrimPrefix(key, "sk-")).Find(&logs).Error
}
return logs, err
}

View File

@@ -7,9 +7,9 @@ import (
type AwsClaudeRequest struct {
// AnthropicVersion should be "bedrock-2023-05-31"
AnthropicVersion string `json:"anthropic_version"`
System string `json:"system"`
System string `json:"system,omitempty"`
Messages []claude.ClaudeMessage `json:"messages"`
MaxTokens int `json:"max_tokens,omitempty"`
MaxTokens uint `json:"max_tokens,omitempty"`
Temperature float64 `json:"temperature,omitempty"`
TopP float64 `json:"top_p,omitempty"`
TopK int `json:"top_k,omitempty"`
@@ -17,3 +17,18 @@ type AwsClaudeRequest struct {
Tools []claude.Tool `json:"tools,omitempty"`
ToolChoice any `json:"tool_choice,omitempty"`
}
func copyRequest(req *claude.ClaudeRequest) *AwsClaudeRequest {
return &AwsClaudeRequest{
AnthropicVersion: "bedrock-2023-05-31",
System: req.System,
Messages: req.Messages,
MaxTokens: req.MaxTokens,
Temperature: req.Temperature,
TopP: req.TopP,
TopK: req.TopK,
StopSequences: req.StopSequences,
Tools: req.Tools,
ToolChoice: req.ToolChoice,
}
}

View File

@@ -5,7 +5,6 @@ import (
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
"io"
"net/http"
@@ -78,13 +77,7 @@ func awsHandler(c *gin.Context, info *relaycommon.RelayInfo, requestMode int) (*
return wrapErr(errors.New("request not found")), nil
}
claudeReq := claudeReq_.(*claude.ClaudeRequest)
awsClaudeReq := &AwsClaudeRequest{
AnthropicVersion: "bedrock-2023-05-31",
}
if err = copier.Copy(awsClaudeReq, claudeReq); err != nil {
return wrapErr(errors.Wrap(err, "copy request")), nil
}
awsClaudeReq := copyRequest(claudeReq)
awsReq.Body, err = json.Marshal(awsClaudeReq)
if err != nil {
return wrapErr(errors.Wrap(err, "marshal request")), nil
@@ -136,12 +129,7 @@ func awsStreamHandler(c *gin.Context, resp *http.Response, info *relaycommon.Rel
}
claudeReq := claudeReq_.(*claude.ClaudeRequest)
awsClaudeReq := &AwsClaudeRequest{
AnthropicVersion: "bedrock-2023-05-31",
}
if err = copier.Copy(awsClaudeReq, claudeReq); err != nil {
return wrapErr(errors.Wrap(err, "copy request")), nil
}
awsClaudeReq := copyRequest(claudeReq)
awsReq.Body, err = json.Marshal(awsClaudeReq)
if err != nil {
return wrapErr(errors.Wrap(err, "marshal request")), nil

View File

@@ -30,13 +30,13 @@ func (a *Adaptor) Init(info *relaycommon.RelayInfo) {
}
func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
// 从映射中获取模型名称对应的版本,如果找不到就使用 info.ApiVersion 或默认的版本 "v1"
// 从映射中获取模型名称对应的版本,如果找不到就使用 info.ApiVersion 或默认的版本 "v1beta"
version, beta := constant.GeminiModelMap[info.UpstreamModelName]
if !beta {
if info.ApiVersion != "" {
version = info.ApiVersion
} else {
version = "v1"
version = "v1beta"
}
}

View File

@@ -7,6 +7,7 @@ const (
var ModelList = []string{
"gemini-1.0-pro-latest", "gemini-1.0-pro-001", "gemini-1.5-pro-latest", "gemini-1.5-flash-latest", "gemini-ultra",
"gemini-1.0-pro-vision-latest", "gemini-1.0-pro-vision-001", "gemini-1.5-pro-exp-0827", "gemini-1.5-flash-exp-0827",
"gemini-exp-1114",
}
var ChannelName = "google gemini"

View File

@@ -6,6 +6,7 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/bytedance/sonic"
"io"
"math"
"net/http"
@@ -76,7 +77,7 @@ func TextHelper(c *gin.Context) (openaiErr *dto.OpenAIErrorWithStatusCode) {
}
// map model name
isModelMapped := false
//isModelMapped := false
modelMapping := c.GetString("model_mapping")
//isModelMapped := false
if modelMapping != "" && modelMapping != "{}" {
@@ -86,7 +87,7 @@ func TextHelper(c *gin.Context) (openaiErr *dto.OpenAIErrorWithStatusCode) {
return service.OpenAIErrorWrapperLocal(err, "unmarshal_model_mapping_failed", http.StatusInternalServerError)
}
if modelMap[textRequest.Model] != "" {
isModelMapped = true
//isModelMapped = true
textRequest.Model = modelMap[textRequest.Model]
// set upstream model name
//isModelMapped = true
@@ -165,23 +166,25 @@ func TextHelper(c *gin.Context) (openaiErr *dto.OpenAIErrorWithStatusCode) {
adaptor.Init(relayInfo)
var requestBody io.Reader
if relayInfo.ChannelType == common.ChannelTypeOpenAI && !isModelMapped {
body, err := common.GetRequestBody(c)
if err != nil {
return service.OpenAIErrorWrapperLocal(err, "get_request_body_failed", http.StatusInternalServerError)
}
requestBody = bytes.NewBuffer(body)
} else {
convertedRequest, err := adaptor.ConvertRequest(c, relayInfo, textRequest)
if err != nil {
return service.OpenAIErrorWrapperLocal(err, "convert_request_failed", http.StatusInternalServerError)
}
jsonData, err := json.Marshal(convertedRequest)
if err != nil {
return service.OpenAIErrorWrapperLocal(err, "json_marshal_failed", http.StatusInternalServerError)
}
requestBody = bytes.NewBuffer(jsonData)
//if relayInfo.ChannelType == common.ChannelTypeOpenAI && !isModelMapped {
// body, err := common.GetRequestBody(c)
// if err != nil {
// return service.OpenAIErrorWrapperLocal(err, "get_request_body_failed", http.StatusInternalServerError)
// }
// requestBody = bytes.NewBuffer(body)
//} else {
//
//}
convertedRequest, err := adaptor.ConvertRequest(c, relayInfo, textRequest)
if err != nil {
return service.OpenAIErrorWrapperLocal(err, "convert_request_failed", http.StatusInternalServerError)
}
jsonData, err := sonic.Marshal(convertedRequest)
if err != nil {
return service.OpenAIErrorWrapperLocal(err, "json_marshal_failed", http.StatusInternalServerError)
}
requestBody = bytes.NewBuffer(jsonData)
statusCodeMappingStr := c.GetString("status_code_mapping")
var httpResp *http.Response

4789
web/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,6 @@ import Setting from './pages/Setting';
import EditUser from './pages/User/EditUser';
import { getLogo, getSystemName } from './helpers';
import PasswordResetForm from './components/PasswordResetForm';
import GitHubOAuth from './components/GitHubOAuth';
import PasswordResetConfirm from './components/PasswordResetConfirm';
import { UserContext } from './context/User';
import Channel from './pages/Channel';
@@ -26,7 +25,7 @@ import Midjourney from './pages/Midjourney';
import Pricing from './pages/Pricing/index.js';
import Task from "./pages/Task/index.js";
import Playground from './components/Playground.js';
import LinuxDoOAuth from './components/LinuxDoOAuth.js';
import OAuth2Callback from "./components/OAuth2Callback.js";
const Home = lazy(() => import('./pages/Home'));
const Detail = lazy(() => import('./pages/Detail'));
@@ -178,7 +177,7 @@ function App() {
path='/oauth/github'
element={
<Suspense fallback={<Loading></Loading>}>
<GitHubOAuth />
<OAuth2Callback type='github'></OAuth2Callback>
</Suspense>
}
/>
@@ -186,7 +185,7 @@ function App() {
path='/oauth/linuxdo'
element={
<Suspense fallback={<Loading></Loading>}>
<LinuxDoOAuth />
<OAuth2Callback type='linuxdo'></OAuth2Callback>
</Suspense>
}
/>

View File

@@ -17,7 +17,7 @@ import {
renderQuota,
} from '../helpers/render';
import {
Button,
Button, Divider,
Dropdown,
Form,
InputNumber,
@@ -707,226 +707,217 @@ const ChannelsTable = () => {
};
return (
<>
<EditChannel
refresh={refresh}
visible={showEdit}
handleClose={closeEdit}
editingChannel={editingChannel}
/>
<Form
onSubmit={() => {
searchChannels(searchKeyword, searchGroup, searchModel);
}}
labelPosition='left'
>
<div style={{ display: 'flex' }}>
<Space>
<Form.Input
field='search_keyword'
label='搜索渠道关键词'
placeholder='ID名称和密钥 ...'
value={searchKeyword}
loading={searching}
onChange={(v) => {
setSearchKeyword(v.trim());
}}
/>
<Form.Input
field='search_model'
label='模型'
placeholder='模型关键字'
value={searchModel}
loading={searching}
onChange={(v) => {
setSearchModel(v.trim());
}}
/>
<Form.Select
field='group'
label='分组'
optionList={[{ label: '选择分组', value: null}, ...groupOptions]}
initValue={null}
onChange={(v) => {
setSearchGroup(v);
searchChannels(searchKeyword, v, searchModel);
}}
/>
<>
<EditChannel
refresh={refresh}
visible={showEdit}
handleClose={closeEdit}
editingChannel={editingChannel}
/>
<Form
onSubmit={() => {
searchChannels(searchKeyword, searchGroup, searchModel);
}}
labelPosition='left'
>
<div style={{display: 'flex'}}>
<Space>
<Form.Input
field='search_keyword'
label='搜索渠道关键词'
placeholder='ID名称和密钥 ...'
value={searchKeyword}
loading={searching}
onChange={(v) => {
setSearchKeyword(v.trim());
}}
/>
<Form.Input
field='search_model'
label='模型'
placeholder='模型关键字'
value={searchModel}
loading={searching}
onChange={(v) => {
setSearchModel(v.trim());
}}
/>
<Form.Select
field='group'
label='分组'
optionList={[{label: '选择分组', value: null}, ...groupOptions]}
initValue={null}
onChange={(v) => {
setSearchGroup(v);
searchChannels(searchKeyword, v, searchModel);
}}
/>
<Button
label='查询'
type='primary'
htmlType='submit'
className='btn-margin-right'
style={{marginRight: 8}}
>
查询
</Button>
</Space>
</div>
</Form>
<Divider style={{marginBottom:15}}/>
<div
style={{
display: isMobile() ? '' : 'flex',
marginTop: isMobile() ? 0 : -45,
zIndex: 999,
pointerEvents: 'none',
}}
>
<Space
style={{pointerEvents: 'auto', marginTop: isMobile() ? 0 : 45}}
>
<Typography.Text strong>使用ID排序</Typography.Text>
<Switch
checked={idSort}
label='使用ID排序'
uncheckedText='关'
aria-label='是否用ID排序'
onChange={(v) => {
localStorage.setItem('id-sort', v + '');
setIdSort(v);
loadChannels(0, pageSize, v)
.then()
.catch((reason) => {
showError(reason);
});
}}
></Switch>
<Button
label='查询'
type='primary'
htmlType='submit'
className='btn-margin-right'
style={{ marginRight: 8 }}
theme='light'
type='primary'
style={{marginRight: 8}}
onClick={() => {
setEditingChannel({
id: undefined,
});
setShowEdit(true);
}}
>
查询
添加渠道
</Button>
<Popconfirm
title='确定?'
okType={'warning'}
onConfirm={testAllChannels}
position={isMobile() ? 'top' : 'top'}
>
<Button theme='light' type='warning' style={{marginRight: 8}}>
测试所有通道
</Button>
</Popconfirm>
<Popconfirm
title='确定?'
okType={'secondary'}
onConfirm={updateAllChannelsBalance}
>
<Button theme='light' type='secondary' style={{marginRight: 8}}>
更新所有已启用通道余额
</Button>
</Popconfirm>
<Popconfirm
title='确定是否要删除禁用通道?'
content='此修改将不可逆'
okType={'danger'}
onConfirm={deleteAllDisabledChannels}
>
<Button theme='light' type='danger' style={{marginRight: 8}}>
删除禁用通道
</Button>
</Popconfirm>
<Button
theme='light'
type='primary'
style={{marginRight: 8}}
onClick={refresh}
>
刷新
</Button>
</Space>
</div>
</Form>
<div style={{ marginTop: 10, display: 'flex' }}>
<Space>
<div style={{marginTop: 20}}>
<Space>
<Typography.Text strong>使用ID排序</Typography.Text>
<Typography.Text strong>开启批量删除</Typography.Text>
<Switch
checked={idSort}
label='使用ID排序'
uncheckedText='关'
aria-label='是否用ID排序'
onChange={(v) => {
localStorage.setItem('id-sort', v + '');
setIdSort(v);
loadChannels(0, pageSize, v)
.then()
.catch((reason) => {
showError(reason);
});
}}
label='开启批量删除'
uncheckedText='关'
aria-label='是否开启批量删除'
onChange={(v) => {
setEnableBatchDelete(v);
}}
></Switch>
</Space>
</Space>
</div>
<Table
className={'channel-table'}
style={{ marginTop: 15 }}
columns={columns}
dataSource={pageData}
pagination={{
currentPage: activePage,
pageSize: pageSize,
total: channelCount,
pageSizeOpts: [10, 20, 50, 100],
showSizeChanger: true,
formatPageText: (page) => '',
onPageSizeChange: (size) => {
handlePageSizeChange(size).then();
},
onPageChange: handlePageChange,
}}
loading={loading}
onRow={handleRow}
rowSelection={
enableBatchDelete
? {
onChange: (selectedRowKeys, selectedRows) => {
// console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
setSelectedChannels(selectedRows);
},
}
: null
}
/>
<div
style={{
display: isMobile() ? '' : 'flex',
marginTop: isMobile() ? 0 : -45,
zIndex: 999,
position: 'relative',
pointerEvents: 'none',
}}
>
<Space
style={{ pointerEvents: 'auto', marginTop: isMobile() ? 0 : 45 }}
>
<Button
theme='light'
type='primary'
style={{ marginRight: 8 }}
onClick={() => {
setEditingChannel({
id: undefined,
});
setShowEdit(true);
}}
>
添加渠道
</Button>
<Popconfirm
title='确定?'
okType={'warning'}
onConfirm={testAllChannels}
position={isMobile() ? 'top' : 'top'}
>
<Button theme='light' type='warning' style={{ marginRight: 8 }}>
测试所有通道
</Button>
</Popconfirm>
<Popconfirm
title='确定?'
okType={'secondary'}
onConfirm={updateAllChannelsBalance}
>
<Button theme='light' type='secondary' style={{ marginRight: 8 }}>
更新所有已启用通道余额
</Button>
</Popconfirm>
<Popconfirm
title='确定是否要删除禁用通道?'
content='此修改将不可逆'
okType={'danger'}
onConfirm={deleteAllDisabledChannels}
>
<Button theme='light' type='danger' style={{ marginRight: 8 }}>
删除禁用通道
</Button>
</Popconfirm>
<Button
theme='light'
type='primary'
style={{ marginRight: 8 }}
onClick={refresh}
>
刷新
</Button>
</Space>
{/*<div style={{width: '100%', pointerEvents: 'none', position: 'absolute'}}>*/}
{/*</div>*/}
</div>
<div style={{ marginTop: 20 }}>
<Space>
<Typography.Text strong>开启批量删除</Typography.Text>
<Switch
label='开启批量删除'
uncheckedText='关'
aria-label='是否开启批量删除'
onChange={(v) => {
setEnableBatchDelete(v);
}}
></Switch>
<Popconfirm
title='确定是否要删除所选通道?'
content='此修改将不可逆'
okType={'danger'}
onConfirm={batchDeleteChannels}
disabled={!enableBatchDelete}
position={'top'}
>
<Button
disabled={!enableBatchDelete}
theme='light'
type='danger'
style={{ marginRight: 8 }}
<Popconfirm
title='确定是否要删除所选通道?'
content='此修改将不可逆'
okType={'danger'}
onConfirm={batchDeleteChannels}
disabled={!enableBatchDelete}
position={'top'}
>
删除所选通道
</Button>
</Popconfirm>
<Popconfirm
title='确定是否要修复数据库一致性?'
content='进行该操作时,可能导致渠道访问错误,请仅在数据库出现问题时使用'
okType={'warning'}
onConfirm={fixChannelsAbilities}
position={'top'}
>
<Button theme='light' type='secondary' style={{ marginRight: 8 }}>
修复数据库一致性
</Button>
</Popconfirm>
</Space>
</div>
</>
<Button
disabled={!enableBatchDelete}
theme='light'
type='danger'
style={{marginRight: 8}}
>
删除所选通道
</Button>
</Popconfirm>
<Popconfirm
title='确定是否要修复数据库一致性?'
content='进行该操作时,可能导致渠道访问错误,请仅在数据库出现问题时使用'
okType={'warning'}
onConfirm={fixChannelsAbilities}
position={'top'}
>
<Button theme='light' type='secondary' style={{marginRight: 8}}>
修复数据库一致性
</Button>
</Popconfirm>
</Space>
</div>
<Table
className={'channel-table'}
style={{marginTop: 15}}
columns={columns}
dataSource={pageData}
pagination={{
currentPage: activePage,
pageSize: pageSize,
total: channelCount,
pageSizeOpts: [10, 20, 50, 100],
showSizeChanger: true,
formatPageText: (page) => '',
onPageSizeChange: (size) => {
handlePageSizeChange(size).then();
},
onPageChange: handlePageChange,
}}
loading={loading}
onRow={handleRow}
rowSelection={
enableBatchDelete
? {
onChange: (selectedRowKeys, selectedRows) => {
// console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
setSelectedChannels(selectedRows);
},
}
: null
}
/>
</>
);
};

View File

@@ -1,61 +0,0 @@
import React, { useContext, useEffect, useState } from 'react';
import { Dimmer, Loader, Segment } from 'semantic-ui-react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { API, showError, showSuccess, updateAPI } from '../helpers';
import { UserContext } from '../context/User';
import { setUserData } from '../helpers/data.js';
const GitHubOAuth = () => {
const [searchParams, setSearchParams] = useSearchParams();
const [userState, userDispatch] = useContext(UserContext);
const [prompt, setPrompt] = useState('处理中...');
const [processing, setProcessing] = useState(true);
let navigate = useNavigate();
const sendCode = async (code, state, count) => {
const res = await API.get(`/api/oauth/github?code=${code}&state=${state}`);
const { success, message, data } = res.data;
if (success) {
if (message === 'bind') {
showSuccess('绑定成功!');
navigate('/setting');
} else {
userDispatch({ type: 'login', payload: data });
localStorage.setItem('user', JSON.stringify(data));
setUserData(data);
updateAPI()
showSuccess('登录成功!');
navigate('/token');
}
} else {
showError(message);
if (count === 0) {
setPrompt(`操作失败,重定向至登录界面中...`);
navigate('/setting'); // in case this is failed to bind GitHub
return;
}
count++;
setPrompt(`出现错误,第 ${count} 次重试中...`);
await new Promise((resolve) => setTimeout(resolve, count * 2000));
await sendCode(code, state, count);
}
};
useEffect(() => {
let code = searchParams.get('code');
let state = searchParams.get('state');
sendCode(code, state, 0).then();
}, []);
return (
<Segment style={{ minHeight: '300px' }}>
<Dimmer active inverted>
<Loader size='large'>{prompt}</Loader>
</Dimmer>
</Segment>
);
};
export default GitHubOAuth;

View File

@@ -1,61 +0,0 @@
import React, { useContext, useEffect, useState } from 'react';
import { Dimmer, Loader, Segment } from 'semantic-ui-react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { API, showError, showSuccess, updateAPI } from '../helpers';
import { UserContext } from '../context/User';
import { setUserData } from '../helpers/data.js';
const LinuxDoOAuth = () => {
const [searchParams, setSearchParams] = useSearchParams();
const [userState, userDispatch] = useContext(UserContext);
const [prompt, setPrompt] = useState('处理中...');
const [processing, setProcessing] = useState(true);
let navigate = useNavigate();
const sendCode = async (code, state, count) => {
const res = await API.get(`/api/oauth/linuxdo?code=${code}&state=${state}`);
const { success, message, data } = res.data;
if (success) {
if (message === 'bind') {
showSuccess('绑定成功!');
navigate('/setting');
} else {
userDispatch({ type: 'login', payload: data });
localStorage.setItem('user', JSON.stringify(data));
setUserData(data);
updateAPI()
showSuccess('登录成功!');
navigate('/token');
}
} else {
showError(message);
if (count === 0) {
setPrompt(`操作失败,重定向至登录界面中...`);
navigate('/setting'); // in case this is failed to bind GitHub
return;
}
count++;
setPrompt(`出现错误,第 ${count} 次重试中...`);
await new Promise((resolve) => setTimeout(resolve, count * 2000));
await sendCode(code, state, count);
}
};
useEffect(() => {
let code = searchParams.get('code');
let state = searchParams.get('state');
sendCode(code, state, 0).then();
}, []);
return (
<Segment style={{ minHeight: '300px' }}>
<Dimmer active inverted>
<Loader size='large'>{prompt}</Loader>
</Dimmer>
</Segment>
);
};
export default LinuxDoOAuth;

View File

@@ -44,8 +44,15 @@ const LoginForm = () => {
const [turnstileToken, setTurnstileToken] = useState('');
let navigate = useNavigate();
const [status, setStatus] = useState({});
const [showWeChatLoginModal, setShowWeChatLoginModal] = useState(false);
const logo = getLogo();
let affCode = new URLSearchParams(window.location.search).get('aff');
if (affCode) {
localStorage.setItem('aff', affCode);
}
useEffect(() => {
if (searchParams.get('expired')) {
showError('未登录或登录已过期,请重新登录!');
@@ -61,7 +68,6 @@ const LoginForm = () => {
}
}, []);
const [showWeChatLoginModal, setShowWeChatLoginModal] = useState(false);
const onWeChatLoginClicked = () => {
setShowWeChatLoginModal(true);

View File

@@ -767,6 +767,22 @@ const LogsTable = () => {
<Form.Section></Form.Section>
</>
</Form>
<div style={{marginTop:10}}>
<Select
defaultValue='0'
style={{ width: 120 }}
onChange={(value) => {
setLogType(parseInt(value));
loadLogs(0, pageSize, parseInt(value));
}}
>
<Select.Option value='0'>全部</Select.Option>
<Select.Option value='1'>充值</Select.Option>
<Select.Option value='2'>消费</Select.Option>
<Select.Option value='3'>管理</Select.Option>
<Select.Option value='4'>系统</Select.Option>
</Select>
</div>
<Table
style={{ marginTop: 5 }}
columns={columns}
@@ -786,20 +802,6 @@ const LogsTable = () => {
onPageChange: handlePageChange,
}}
/>
<Select
defaultValue='0'
style={{ width: 120 }}
onChange={(value) => {
setLogType(parseInt(value));
loadLogs(0, pageSize, parseInt(value));
}}
>
<Select.Option value='0'>全部</Select.Option>
<Select.Option value='1'>充值</Select.Option>
<Select.Option value='2'>消费</Select.Option>
<Select.Option value='3'>管理</Select.Option>
<Select.Option value='4'>系统</Select.Option>
</Select>
</Layout>
</>
);

View File

@@ -0,0 +1,61 @@
import React, { useContext, useEffect, useState } from 'react';
import { Dimmer, Loader, Segment } from 'semantic-ui-react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { API, showError, showSuccess, updateAPI } from '../helpers';
import { UserContext } from '../context/User';
import { setUserData } from '../helpers/data.js';
const OAuth2Callback = (props) => {
const [searchParams, setSearchParams] = useSearchParams();
const [userState, userDispatch] = useContext(UserContext);
const [prompt, setPrompt] = useState('处理中...');
const [processing, setProcessing] = useState(true);
let navigate = useNavigate();
const sendCode = async (code, state, count) => {
const res = await API.get(`/api/oauth/${props.type}?code=${code}&state=${state}`);
const { success, message, data } = res.data;
if (success) {
if (message === 'bind') {
showSuccess('绑定成功!');
navigate('/setting');
} else {
userDispatch({ type: 'login', payload: data });
localStorage.setItem('user', JSON.stringify(data));
setUserData(data);
updateAPI()
showSuccess('登录成功!');
navigate('/token');
}
} else {
showError(message);
if (count === 0) {
setPrompt(`操作失败,重定向至登录界面中...`);
navigate('/setting'); // in case this is failed to bind GitHub
return;
}
count++;
setPrompt(`出现错误,第 ${count} 次重试中...`);
await new Promise((resolve) => setTimeout(resolve, count * 2000));
await sendCode(code, state, count);
}
};
useEffect(() => {
let code = searchParams.get('code');
let state = searchParams.get('state');
sendCode(code, state, 0).then();
}, []);
return (
<Segment style={{ minHeight: '300px' }}>
<Dimmer active inverted>
<Loader size='large'>{prompt}</Loader>
</Dimmer>
</Segment>
);
};
export default OAuth2Callback;

View File

@@ -90,7 +90,7 @@ const OperationSetting = () => {
try {
setLoading(true);
await getOptions();
showSuccess('刷新成功');
// showSuccess('刷新成功');
} catch (error) {
showError('刷新失败');
} finally {

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,7 @@ import {
import { ITEMS_PER_PAGE } from '../constants';
import { renderQuota } from '../helpers/render';
import {
Button,
Button, Divider,
Form,
Modal,
Popconfirm,
@@ -391,6 +391,39 @@ const RedemptionsTable = () => {
onChange={handleKeywordChange}
/>
</Form>
<Divider style={{margin:'5px 0 15px 0'}}/>
<div>
<Button
theme='light'
type='primary'
style={{ marginRight: 8 }}
onClick={() => {
setEditingRedemption({
id: undefined,
});
setShowEdit(true);
}}
>
添加兑换码
</Button>
<Button
label='复制所选兑换码'
type='warning'
onClick={async () => {
if (selectedKeys.length === 0) {
showError('请至少选择一个兑换码!');
return;
}
let keys = '';
for (let i = 0; i < selectedKeys.length; i++) {
keys += selectedKeys[i].name + ' ' + selectedKeys[i].key + '\n';
}
await copyText(keys);
}}
>
复制所选兑换码到剪贴板
</Button>
</div>
<Table
style={{ marginTop: 20 }}
@@ -414,36 +447,6 @@ const RedemptionsTable = () => {
rowSelection={rowSelection}
onRow={handleRow}
></Table>
<Button
theme='light'
type='primary'
style={{ marginRight: 8 }}
onClick={() => {
setEditingRedemption({
id: undefined,
});
setShowEdit(true);
}}
>
添加兑换码
</Button>
<Button
label='复制所选兑换码'
type='warning'
onClick={async () => {
if (selectedKeys.length === 0) {
showError('请至少选择一个兑换码!');
return;
}
let keys = '';
for (let i = 0; i < selectedKeys.length; i++) {
keys += selectedKeys[i].name + ' ' + selectedKeys[i].key + '\n';
}
await copyText(keys);
}}
>
复制所选兑换码到剪贴板
</Button>
</>
);
};

View File

@@ -1,10 +1,16 @@
import React, { useEffect, useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { API, getLogo, showError, showInfo, showSuccess } from '../helpers';
import { API, getLogo, showError, showInfo, showSuccess, updateAPI } from '../helpers';
import Turnstile from 'react-turnstile';
import { Button, Card, Form, Layout } from '@douyinfe/semi-ui';
import { Button, Card, Divider, Form, Icon, Layout, Modal } from '@douyinfe/semi-ui';
import Title from '@douyinfe/semi-ui/lib/es/typography/title';
import Text from '@douyinfe/semi-ui/lib/es/typography/text';
import { IconGithubLogo } from '@douyinfe/semi-icons';
import { onGitHubOAuthClicked, onLinuxDOOAuthClicked } from './utils.js';
import LinuxDoIcon from './LinuxDoIcon.js';
import WeChatIcon from './WeChatIcon.js';
import TelegramLoginButton from 'react-telegram-login/src';
import { setUserData } from '../helpers/data.js';
const RegisterForm = () => {
const [inputs, setInputs] = useState({
@@ -20,7 +26,11 @@ const RegisterForm = () => {
const [turnstileSiteKey, setTurnstileSiteKey] = useState('');
const [turnstileToken, setTurnstileToken] = useState('');
const [loading, setLoading] = useState(false);
const [showWeChatLoginModal, setShowWeChatLoginModal] = useState(false);
const [status, setStatus] = useState({});
let navigate = useNavigate();
const logo = getLogo();
let affCode = new URLSearchParams(window.location.search).get('aff');
if (affCode) {
localStorage.setItem('aff', affCode);
@@ -30,6 +40,7 @@ const RegisterForm = () => {
let status = localStorage.getItem('status');
if (status) {
status = JSON.parse(status);
setStatus(status);
setShowEmailVerification(status.email_verification);
if (status.turnstile_check) {
setTurnstileEnabled(true);
@@ -38,7 +49,32 @@ const RegisterForm = () => {
}
});
let navigate = useNavigate();
const onWeChatLoginClicked = () => {
setShowWeChatLoginModal(true);
};
const onSubmitWeChatVerificationCode = async () => {
if (turnstileEnabled && turnstileToken === '') {
showInfo('请稍后几秒重试Turnstile 正在检查用户环境!');
return;
}
const res = await API.get(
`/api/oauth/wechat?code=${inputs.wechat_verification_code}`,
);
const { success, message, data } = res.data;
if (success) {
userDispatch({ type: 'login', payload: data });
localStorage.setItem('user', JSON.stringify(data));
setUserData(data);
updateAPI();
navigate('/');
showSuccess('登录成功!');
setShowWeChatLoginModal(false);
} else {
showError(message);
}
};
function handleChange(name, value) {
setInputs((inputs) => ({ ...inputs, [name]: value }));
@@ -189,14 +225,127 @@ const RegisterForm = () => {
</Link>
</Text>
</div>
{status.github_oauth ||
status.wechat_login ||
status.telegram_oauth ||
status.linuxdo_oauth ? (
<>
<Divider margin='12px' align='center'>
第三方登录
</Divider>
<div
style={{
display: 'flex',
justifyContent: 'center',
marginTop: 20,
}}
>
{status.github_oauth ? (
<Button
type='primary'
icon={<IconGithubLogo />}
onClick={() =>
onGitHubOAuthClicked(status.github_client_id)
}
/>
) : (
<></>
)}
{status.linuxdo_oauth ? (
<Button
icon={<LinuxDoIcon />}
onClick={() =>
onLinuxDOOAuthClicked(status.linuxdo_client_id)
}
/>
) : (
<></>
)}
{status.wechat_login ? (
<Button
type='primary'
style={{ color: 'rgba(var(--semi-green-5), 1)' }}
icon={<Icon svg={<WeChatIcon />} />}
onClick={onWeChatLoginClicked}
/>
) : (
<></>
)}
</div>
{status.telegram_oauth ? (
<>
<div
style={{
display: 'flex',
justifyContent: 'center',
marginTop: 5,
}}
>
<TelegramLoginButton
dataOnauth={onTelegramLoginClicked}
botName={status.telegram_bot_name}
/>
</div>
</>
) : (
<></>
)}
</>
) : (
<></>
)}
</Card>
{turnstileEnabled ? (
<Turnstile
sitekey={turnstileSiteKey}
onVerify={(token) => {
setTurnstileToken(token);
<Modal
title='微信扫码登录'
visible={showWeChatLoginModal}
maskClosable={true}
onOk={onSubmitWeChatVerificationCode}
onCancel={() => setShowWeChatLoginModal(false)}
okText={'登录'}
size={'small'}
centered={true}
>
<div
style={{
display: 'flex',
alignItem: 'center',
flexDirection: 'column',
}}
/>
>
<img src={status.wechat_qrcode} />
</div>
<div style={{ textAlign: 'center' }}>
<p>
微信扫码关注公众号输入验证码获取验证码三分钟内有效
</p>
</div>
<Form size='large'>
<Form.Input
field={'wechat_verification_code'}
placeholder='验证码'
label={'验证码'}
value={inputs.wechat_verification_code}
onChange={(value) =>
handleChange('wechat_verification_code', value)
}
/>
</Form>
</Modal>
{turnstileEnabled ? (
<div
style={{
display: 'flex',
justifyContent: 'center',
marginTop: 20,
}}
>
<Turnstile
sitekey={turnstileSiteKey}
onVerify={(token) => {
setTurnstileToken(token);
}}
/>
</div>
) : (
<></>
)}

View File

@@ -10,7 +10,7 @@ import {
import { ITEMS_PER_PAGE } from '../constants';
import {renderGroup, renderQuota} from '../helpers/render';
import {
Button,
Button, Divider,
Dropdown,
Form,
Modal,
@@ -596,6 +596,40 @@ const TokensTable = () => {
查询
</Button>
</Form>
<Divider style={{margin:'15px 0'}}/>
<div>
<Button
theme='light'
type='primary'
style={{ marginRight: 8 }}
onClick={() => {
setEditingToken({
id: undefined,
});
setShowEdit(true);
}}
>
添加令牌
</Button>
<Button
label='复制所选令牌'
type='warning'
onClick={async () => {
if (selectedKeys.length === 0) {
showError('请至少选择一个令牌!');
return;
}
let keys = '';
for (let i = 0; i < selectedKeys.length; i++) {
keys +=
selectedKeys[i].name + ' sk-' + selectedKeys[i].key + '\n';
}
await copyText(keys);
}}
>
复制所选令牌到剪贴板
</Button>
</div>
<Table
style={{ marginTop: 20 }}
@@ -619,37 +653,6 @@ const TokensTable = () => {
rowSelection={rowSelection}
onRow={handleRow}
></Table>
<Button
theme='light'
type='primary'
style={{ marginRight: 8 }}
onClick={() => {
setEditingToken({
id: undefined,
});
setShowEdit(true);
}}
>
添加令牌
</Button>
<Button
label='复制所选令牌'
type='warning'
onClick={async () => {
if (selectedKeys.length === 0) {
showError('请至少选择一个令牌!');
return;
}
let keys = '';
for (let i = 0; i < selectedKeys.length; i++) {
keys +=
selectedKeys[i].name + ' sk-' + selectedKeys[i].key + '\n';
}
await copyText(keys);
}}
>
复制所选令牌到剪贴板
</Button>
</>
);
};

View File

@@ -476,10 +476,18 @@ const UsersTable = () => {
type='primary'
htmlType='submit'
className='btn-margin-right'
style={{ marginRight: 8 }}
>
查询
</Button>
<Button
theme='light'
type='primary'
onClick={() => {
setShowAddUser(true);
}}
>
添加用户
</Button>
</Space>
</div>
</Form>
@@ -496,16 +504,6 @@ const UsersTable = () => {
}}
loading={loading}
/>
<Button
theme='light'
type='primary'
style={{ marginRight: 8 }}
onClick={() => {
setShowAddUser(true);
}}
>
添加用户
</Button>
</>
);
};

View File

@@ -1,7 +1,12 @@
import { API, showError } from '../helpers';
export async function getOAuthState() {
const res = await API.get('/api/oauth/state');
let path = '/api/oauth/state';
let affCode = localStorage.getItem('aff');
if (affCode && affCode.length > 0) {
path += `?aff=${affCode}`;
}
const res = await API.get(path);
const { success, message, data } = res.data;
if (success) {
return data;

View File

@@ -144,7 +144,7 @@ export default function SettingsCreditLimit(props) {
</Row>
<Row>
<Button size='large' onClick={onSubmit}>
<Button size='default' onClick={onSubmit}>
保存额度设置
</Button>
</Row>

View File

@@ -87,7 +87,7 @@ export default function DataDashboard(props) {
<Form.Switch
field={'DataExportEnabled'}
label={'启用数据看板(实验性)'}
size='large'
size='default'
checkedText=''
uncheckedText=''
onChange={(value) => {
@@ -135,7 +135,7 @@ export default function DataDashboard(props) {
</Col>
</Row>
<Row>
<Button size='large' onClick={onSubmit}>
<Button size='default' onClick={onSubmit}>
保存数据看板设置
</Button>
</Row>

View File

@@ -81,7 +81,7 @@ export default function SettingsDrawing(props) {
<Form.Switch
field={'DrawingEnabled'}
label={'启用绘图功能'}
size='large'
size='default'
checkedText=''
uncheckedText=''
onChange={(value) => {
@@ -96,7 +96,7 @@ export default function SettingsDrawing(props) {
<Form.Switch
field={'MjNotifyEnabled'}
label={'允许回调(会泄露服务器 IP 地址)'}
size='large'
size='default'
checkedText=''
uncheckedText=''
onChange={(value) =>
@@ -111,7 +111,7 @@ export default function SettingsDrawing(props) {
<Form.Switch
field={'MjAccountFilterEnabled'}
label={'允许 AccountFilter 参数'}
size='large'
size='default'
checkedText=''
uncheckedText=''
onChange={(value) =>
@@ -126,7 +126,7 @@ export default function SettingsDrawing(props) {
<Form.Switch
field={'MjForwardUrlEnabled'}
label={'开启之后将上游地址替换为服务器地址'}
size='large'
size='default'
checkedText=''
uncheckedText=''
onChange={(value) =>
@@ -146,7 +146,7 @@ export default function SettingsDrawing(props) {
<Tag>--relax</Tag> <Tag>--turbo</Tag>
</>
}
size='large'
size='default'
checkedText=''
uncheckedText=''
onChange={(value) =>
@@ -165,7 +165,7 @@ export default function SettingsDrawing(props) {
检测必须等待绘图成功才能进行放大等操作
</>
}
size='large'
size='default'
checkedText=''
uncheckedText=''
onChange={(value) =>
@@ -178,7 +178,7 @@ export default function SettingsDrawing(props) {
</Col>
</Row>
<Row>
<Button size='large' onClick={onSubmit}>
<Button size='default' onClick={onSubmit}>
保存绘图设置
</Button>
</Row>

View File

@@ -141,7 +141,7 @@ export default function GeneralSettings(props) {
<Form.Switch
field={'DisplayInCurrencyEnabled'}
label={'以货币形式显示额度'}
size='large'
size='default'
checkedText=''
uncheckedText=''
onChange={(value) => {
@@ -156,7 +156,7 @@ export default function GeneralSettings(props) {
<Form.Switch
field={'DisplayTokenStatEnabled'}
label={'Billing 相关 API 显示令牌额度而非用户额度'}
size='large'
size='default'
checkedText=''
uncheckedText=''
onChange={(value) =>
@@ -171,7 +171,7 @@ export default function GeneralSettings(props) {
<Form.Switch
field={'DefaultCollapseSidebar'}
label={'默认折叠侧边栏'}
size='large'
size='default'
checkedText=''
uncheckedText=''
onChange={(value) =>
@@ -184,7 +184,7 @@ export default function GeneralSettings(props) {
</Col>
</Row>
<Row>
<Button size='large' onClick={onSubmit}>
<Button size='default' onClick={onSubmit}>
保存通用设置
</Button>
</Row>

View File

@@ -102,7 +102,7 @@ export default function SettingsLog(props) {
<Form.Switch
field={'LogConsumeEnabled'}
label={'启用额度消费日志记录'}
size='large'
size='default'
checkedText=''
uncheckedText=''
onChange={(value) => {
@@ -135,7 +135,7 @@ export default function SettingsLog(props) {
</Row>
<Row>
<Button size='large' onClick={onSubmit}>
<Button size='default' onClick={onSubmit}>
保存日志设置
</Button>
</Row>

View File

@@ -114,7 +114,7 @@ export default function SettingsMonitoring(props) {
<Form.Switch
field={'AutomaticDisableChannelEnabled'}
label={'失败时自动禁用通道'}
size='large'
size='default'
checkedText=''
uncheckedText=''
onChange={(value) => {
@@ -129,7 +129,7 @@ export default function SettingsMonitoring(props) {
<Form.Switch
field={'AutomaticEnableChannelEnabled'}
label={'成功时自动启用通道'}
size='large'
size='default'
checkedText=''
uncheckedText=''
onChange={(value) =>
@@ -142,7 +142,7 @@ export default function SettingsMonitoring(props) {
</Col>
</Row>
<Row>
<Button size='large' onClick={onSubmit}>
<Button size='default' onClick={onSubmit}>
保存监控设置
</Button>
</Row>

View File

@@ -77,7 +77,7 @@ export default function SettingsSensitiveWords(props) {
<Form.Switch
field={'CheckSensitiveEnabled'}
label={'启用屏蔽词过滤功能'}
size='large'
size='default'
checkedText=''
uncheckedText=''
onChange={(value) => {
@@ -92,7 +92,7 @@ export default function SettingsSensitiveWords(props) {
<Form.Switch
field={'CheckSensitiveOnPromptEnabled'}
label={'启用 Prompt 检查'}
size='large'
size='default'
checkedText=''
uncheckedText=''
onChange={(value) =>
@@ -123,7 +123,7 @@ export default function SettingsSensitiveWords(props) {
</Col>
</Row>
<Row>
<Button size='large' onClick={onSubmit}>
<Button size='default' onClick={onSubmit}>
保存屏蔽词过滤设置
</Button>
</Row>