Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
c8028aa8
authored
Sep 17, 2024
by
CalciumIon
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'main' into g-main
# Conflicts: # web/src/App.js
parents
f4dcb4e2
e0e30bad
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
455 additions
and
324 deletions
+455
-324
README.md
+9
-10
common/utils.go
+5
-0
controller/model.go
+40
-13
controller/token.go
+2
-0
middleware/auth.go
+1
-0
middleware/distributor.go
+8
-0
model/token.go
+24
-1
web/src/App.js
+189
-192
web/src/components/Footer.js
+12
-14
web/src/components/HeaderBar.js
+53
-12
web/src/components/SiderBar.js
+69
-55
web/src/index.css
+6
-5
web/src/index.js
+20
-20
web/src/pages/Token/EditToken.js
+17
-2
No files found.
README.md
View file @
c8028aa8
<div
align=
"center"
>
# New API
<a
href=
"https://trendshift.io/repositories/8227"
target=
"_blank"
><img
src=
"https://trendshift.io/api/badge/repositories/8227"
alt=
"Calcium-Ion%2Fnew-api | Trendshift"
style=
"width: 250px; height: 55px;"
width=
"250"
height=
"55"
/></a>
</div>
> [!NOTE]
> 本项目为开源项目,在[One API](https://github.com/songquanpeng/one-api)的基础上进行二次开发
...
...
@@ -115,24 +120,18 @@ docker run --name new-api -d --restart always -p 3000:3000 -e SQL_DSN="root:1234
## Suno接口设置文档
[
对接文档
](
Suno.md
)
## 交流群
<img
src=
"https://github.com/Calcium-Ion/new-api/assets/61247483/de536a8a-0161-47a7-a0a2-66ef6de81266"
width=
"300"
>
## 界面截图







夜间模式



## 交流群
<img
src=
"https://github.com/Calcium-Ion/new-api/assets/61247483/de536a8a-0161-47a7-a0a2-66ef6de81266"
width=
"200"
>
## 相关项目
-
[
One API
](
https://github.com/songquanpeng/one-api
)
:原版项目
-
[
Midjourney-Proxy
](
https://github.com/novicezk/midjourney-proxy
)
:Midjourney接口支持
...
...
common/utils.go
View file @
c8028aa8
...
...
@@ -128,6 +128,11 @@ func IntMax(a int, b int) int {
}
}
func
IsIP
(
s
string
)
bool
{
ip
:=
net
.
ParseIP
(
s
)
return
ip
!=
nil
}
func
GetUUID
()
string
{
code
:=
uuid
.
New
()
.
String
()
code
=
strings
.
Replace
(
code
,
"-"
,
""
,
-
1
)
...
...
controller/model.go
View file @
c8028aa8
...
...
@@ -146,22 +146,49 @@ func ListModels(c *gin.Context) {
})
return
}
models
:=
model
.
GetGroupModels
(
user
.
Group
)
userOpenAiModels
:=
make
([]
dto
.
OpenAIModels
,
0
)
permission
:=
getPermission
()
for
_
,
s
:=
range
models
{
if
_
,
ok
:=
openAIModelsMap
[
s
];
ok
{
userOpenAiModels
=
append
(
userOpenAiModels
,
openAIModelsMap
[
s
])
modelLimitEnable
:=
c
.
GetBool
(
"token_model_limit_enabled"
)
if
modelLimitEnable
{
s
,
ok
:=
c
.
Get
(
"token_model_limit"
)
var
tokenModelLimit
map
[
string
]
bool
if
ok
{
tokenModelLimit
=
s
.
(
map
[
string
]
bool
)
}
else
{
userOpenAiModels
=
append
(
userOpenAiModels
,
dto
.
OpenAIModels
{
Id
:
s
,
Object
:
"model"
,
Created
:
1626777600
,
OwnedBy
:
"custom"
,
Permission
:
permission
,
Root
:
s
,
Parent
:
nil
,
})
tokenModelLimit
=
map
[
string
]
bool
{}
}
for
allowModel
,
_
:=
range
tokenModelLimit
{
if
_
,
ok
:=
openAIModelsMap
[
allowModel
];
ok
{
userOpenAiModels
=
append
(
userOpenAiModels
,
openAIModelsMap
[
allowModel
])
}
else
{
userOpenAiModels
=
append
(
userOpenAiModels
,
dto
.
OpenAIModels
{
Id
:
allowModel
,
Object
:
"model"
,
Created
:
1626777600
,
OwnedBy
:
"custom"
,
Permission
:
permission
,
Root
:
allowModel
,
Parent
:
nil
,
})
}
}
}
else
{
models
:=
model
.
GetGroupModels
(
user
.
Group
)
for
_
,
s
:=
range
models
{
if
_
,
ok
:=
openAIModelsMap
[
s
];
ok
{
userOpenAiModels
=
append
(
userOpenAiModels
,
openAIModelsMap
[
s
])
}
else
{
userOpenAiModels
=
append
(
userOpenAiModels
,
dto
.
OpenAIModels
{
Id
:
s
,
Object
:
"model"
,
Created
:
1626777600
,
OwnedBy
:
"custom"
,
Permission
:
permission
,
Root
:
s
,
Parent
:
nil
,
})
}
}
}
c
.
JSON
(
200
,
gin
.
H
{
...
...
controller/token.go
View file @
c8028aa8
...
...
@@ -134,6 +134,7 @@ func AddToken(c *gin.Context) {
UnlimitedQuota
:
token
.
UnlimitedQuota
,
ModelLimitsEnabled
:
token
.
ModelLimitsEnabled
,
ModelLimits
:
token
.
ModelLimits
,
AllowIps
:
token
.
AllowIps
,
}
err
=
cleanToken
.
Insert
()
if
err
!=
nil
{
...
...
@@ -221,6 +222,7 @@ func UpdateToken(c *gin.Context) {
cleanToken
.
UnlimitedQuota
=
token
.
UnlimitedQuota
cleanToken
.
ModelLimitsEnabled
=
token
.
ModelLimitsEnabled
cleanToken
.
ModelLimits
=
token
.
ModelLimits
cleanToken
.
AllowIps
=
token
.
AllowIps
}
err
=
cleanToken
.
Update
()
if
err
!=
nil
{
...
...
middleware/auth.go
View file @
c8028aa8
...
...
@@ -175,6 +175,7 @@ func TokenAuth() func(c *gin.Context) {
}
else
{
c
.
Set
(
"token_model_limit_enabled"
,
false
)
}
c
.
Set
(
"allow_ips"
,
token
.
GetIpLimitsMap
())
if
len
(
parts
)
>
1
{
if
model
.
IsAdmin
(
token
.
UserId
)
{
c
.
Set
(
"specific_channel_id"
,
parts
[
1
])
...
...
middleware/distributor.go
View file @
c8028aa8
...
...
@@ -22,6 +22,14 @@ type ModelRequest struct {
func
Distribute
()
func
(
c
*
gin
.
Context
)
{
return
func
(
c
*
gin
.
Context
)
{
allowIpsMap
:=
c
.
GetStringMap
(
"allow_ips"
)
if
len
(
allowIpsMap
)
!=
0
{
clientIp
:=
c
.
ClientIP
()
if
_
,
ok
:=
allowIpsMap
[
clientIp
];
!
ok
{
abortWithOpenAiMessage
(
c
,
http
.
StatusForbidden
,
"您的 IP 不在令牌允许访问的列表中"
)
return
}
}
userId
:=
c
.
GetInt
(
"id"
)
var
channel
*
model
.
Channel
channelId
,
ok
:=
c
.
Get
(
"specific_channel_id"
)
...
...
model/token.go
View file @
c8028aa8
...
...
@@ -23,10 +23,33 @@ type Token struct {
UnlimitedQuota
bool
`json:"unlimited_quota" gorm:"default:false"`
ModelLimitsEnabled
bool
`json:"model_limits_enabled" gorm:"default:false"`
ModelLimits
string
`json:"model_limits" gorm:"type:varchar(1024);default:''"`
AllowIps
*
string
`json:"allow_ips" gorm:"default:''"`
UsedQuota
int
`json:"used_quota" gorm:"default:0"`
// used quota
DeletedAt
gorm
.
DeletedAt
`gorm:"index"`
}
func
(
token
*
Token
)
GetIpLimitsMap
()
map
[
string
]
any
{
// delete empty spaces
//split with \n
ipLimitsMap
:=
make
(
map
[
string
]
any
)
if
token
.
AllowIps
==
nil
{
return
ipLimitsMap
}
cleanIps
:=
strings
.
ReplaceAll
(
*
token
.
AllowIps
,
" "
,
""
)
if
cleanIps
==
""
{
return
ipLimitsMap
}
ips
:=
strings
.
Split
(
cleanIps
,
"
\n
"
)
for
_
,
ip
:=
range
ips
{
ip
=
strings
.
TrimSpace
(
ip
)
ip
=
strings
.
ReplaceAll
(
ip
,
","
,
""
)
if
common
.
IsIP
(
ip
)
{
ipLimitsMap
[
ip
]
=
true
}
}
return
ipLimitsMap
}
func
GetAllUserTokens
(
userId
int
,
startIdx
int
,
num
int
)
([]
*
Token
,
error
)
{
var
tokens
[]
*
Token
var
err
error
...
...
@@ -130,7 +153,7 @@ func (token *Token) Insert() error {
// Update Make sure your token's fields is completed, because this will update non-zero values
func
(
token
*
Token
)
Update
()
error
{
var
err
error
err
=
DB
.
Model
(
token
)
.
Select
(
"name"
,
"status"
,
"expired_time"
,
"remain_quota"
,
"unlimited_quota"
,
"model_limits_enabled"
,
"model_limits"
)
.
Updates
(
token
)
.
Error
err
=
DB
.
Model
(
token
)
.
Select
(
"name"
,
"status"
,
"expired_time"
,
"remain_quota"
,
"unlimited_quota"
,
"model_limits_enabled"
,
"model_limits"
,
"allow_ips"
)
.
Updates
(
token
)
.
Error
return
err
}
...
...
web/src/App.js
View file @
c8028aa8
This diff is collapsed.
Click to expand it.
web/src/components/Footer.js
View file @
c8028aa8
...
...
@@ -3,7 +3,7 @@ import React, { useEffect, useState } from 'react';
import
{
getFooterHTML
,
getSystemName
}
from
'../helpers'
;
import
{
Layout
,
Tooltip
}
from
'@douyinfe/semi-ui'
;
const
Footer
=
()
=>
{
const
Footer
Bar
=
()
=>
{
const
systemName
=
getSystemName
();
const
[
footer
,
setFooter
]
=
useState
(
getFooterHTML
());
let
remainCheckTimes
=
5
;
...
...
@@ -56,19 +56,17 @@ const Footer = () => {
},
[]);
return
(
<
Layout
>
<
Layout
.
Content
style
=
{{
textAlign
:
'center'
}}
>
{
footer
?
(
<
div
className
=
'custom-footer'
dangerouslySetInnerHTML
=
{{
__html
:
footer
}}
><
/div
>
)
:
(
defaultFooter
)}
<
/Layout.Content
>
<
/Layout
>
<
div
style
=
{{
textAlign
:
'center'
}}
>
{
footer
?
(
<
div
className
=
'custom-footer'
dangerouslySetInnerHTML
=
{{
__html
:
footer
}}
><
/div
>
)
:
(
defaultFooter
)}
<
/div
>
);
};
export
default
Footer
;
export
default
Footer
Bar
;
web/src/components/HeaderBar.js
View file @
c8028aa8
...
...
@@ -3,14 +3,23 @@ import { Link, useNavigate } from 'react-router-dom';
import
{
UserContext
}
from
'../context/User'
;
import
{
useSetTheme
,
useTheme
}
from
'../context/Theme'
;
import
{
API
,
getLogo
,
getSystemName
,
showSuccess
}
from
'../helpers'
;
import
{
API
,
getLogo
,
getSystemName
,
isMobile
,
showSuccess
}
from
'../helpers'
;
import
'../index.css'
;
import
fireworks
from
'react-fireworks'
;
import
{
IconHelpCircle
,
IconKey
,
IconUser
}
from
'@douyinfe/semi-icons'
;
import
{
IconHelpCircle
,
IconHome
,
IconHomeStroked
,
IconKey
,
IconNoteMoneyStroked
,
IconPriceTag
,
IconUser
}
from
'@douyinfe/semi-icons'
;
import
{
Avatar
,
Dropdown
,
Layout
,
Nav
,
Switch
}
from
'@douyinfe/semi-ui'
;
import
{
stringToColor
}
from
'../helpers/render'
;
import
Text
from
'@douyinfe/semi-ui/lib/es/typography/text'
;
// HeaderBar Buttons
let
headerButtons
=
[
...
...
@@ -22,6 +31,21 @@ let headerButtons = [
},
];
let
buttons
=
[
{
text
:
'首页'
,
itemKey
:
'home'
,
to
:
'/'
,
icon
:
<
IconHomeStroked
/>
,
},
// {
// text: '模型价格',
// itemKey: 'pricing',
// to: '/pricing',
// icon: <IconNoteMoneyStroked />,
// },
];
if
(
localStorage
.
getItem
(
'chat_link'
))
{
headerButtons
.
splice
(
1
,
0
,
{
name
:
'聊天'
,
...
...
@@ -90,6 +114,7 @@ const HeaderBar = () => {
about
:
'/about'
,
login
:
'/login'
,
register
:
'/register'
,
home
:
'/'
,
};
return
(
<
Link
...
...
@@ -103,6 +128,18 @@ const HeaderBar = () => {
selectedKeys
=
{[]}
// items={headerButtons}
onSelect
=
{(
key
)
=>
{}}
header
=
{
isMobile
()?{
logo
:
(
<
img
src
=
{
logo
}
alt
=
'logo'
style
=
{{
marginRight
:
'0.75em'
}}
/
>
),
}:{
logo
:
(
<
img
src
=
{
logo
}
alt
=
'logo'
/>
),
text
:
systemName
,
}}
items
=
{
buttons
}
footer
=
{
<>
{
isNewYear
&&
(
...
...
@@ -121,15 +158,19 @@ const HeaderBar = () => {
<
/Dropdown
>
)}
<
Nav
.
Item
itemKey
=
{
'about'
}
icon
=
{
<
IconHelpCircle
/>
}
/
>
<
Switch
checkedText
=
'🌞'
size
=
{
'large'
}
checked
=
{
theme
===
'dark'
}
uncheckedText
=
'🌙'
onChange
=
{(
checked
)
=>
{
setTheme
(
checked
);
}}
/
>
<>
{
!
isMobile
()
&&
(
<
Switch
checkedText
=
'🌞'
size
=
{
'large'
}
checked
=
{
theme
===
'dark'
}
uncheckedText
=
'🌙'
onChange
=
{(
checked
)
=>
{
setTheme
(
checked
);
}}
/
>
)}
<
/
>
{
userState
.
user
?
(
<>
<
Dropdown
...
...
@@ -155,7 +196,7 @@ const HeaderBar = () => {
<
Nav
.
Item
itemKey
=
{
'login'
}
text
=
{
'登录'
}
icon
=
{
<
IconKey
/>
}
//
icon={<IconKey />}
/
>
<
Nav
.
Item
itemKey
=
{
'register'
}
...
...
web/src/components/SiderBar.js
View file @
c8028aa8
...
...
@@ -17,7 +17,7 @@ import {
IconCalendarClock
,
IconChecklistStroked
,
IconComment
,
IconCreditCard
,
IconGift
,
IconGift
,
IconHelpCircle
,
IconHistogram
,
IconHome
,
IconImage
,
...
...
@@ -25,10 +25,12 @@ import {
IconLayers
,
IconPriceTag
,
IconSetting
,
IconUser
,
IconUser
}
from
'@douyinfe/semi-icons'
;
import
{
Layout
,
Nav
}
from
'@douyinfe/semi-ui'
;
import
{
Avatar
,
Dropdown
,
Layout
,
Nav
,
Switch
}
from
'@douyinfe/semi-ui'
;
import
{
setStatusData
}
from
'../helpers/data.js'
;
import
{
stringToColor
}
from
'../helpers/render.js'
;
import
{
useSetTheme
,
useTheme
}
from
'../context/Theme/index.js'
;
// HeaderBar Buttons
...
...
@@ -43,6 +45,8 @@ const SiderBar = () => {
const
systemName
=
getSystemName
();
const
logo
=
getLogo
();
const
[
isCollapsed
,
setIsCollapsed
]
=
useState
(
defaultIsCollapsed
);
const
theme
=
useTheme
();
const
setTheme
=
useSetTheme
();
const
routerMap
=
{
home
:
'/'
,
...
...
@@ -63,11 +67,17 @@ const SiderBar = () => {
const
headerButtons
=
useMemo
(
()
=>
[
// {
// text: '首页',
// itemKey: 'home',
// to: '/',
// icon: <IconHome />,
// },
{
text
:
'
首页
'
,
itemKey
:
'
home
'
,
to
:
'/'
,
icon
:
<
Icon
Home
/>
,
text
:
'
模型价格
'
,
itemKey
:
'
pricing
'
,
to
:
'/
pricing
'
,
icon
:
<
Icon
PriceTag
/>
,
},
{
text
:
'渠道'
,
...
...
@@ -105,12 +115,6 @@ const SiderBar = () => {
icon
:
<
IconCreditCard
/>
,
},
{
text
:
'模型价格'
,
itemKey
:
'pricing'
,
to
:
'/pricing'
,
icon
:
<
IconPriceTag
/>
,
},
{
text
:
'用户管理'
,
itemKey
:
'user'
,
to
:
'/user'
,
...
...
@@ -205,48 +209,58 @@ const SiderBar = () => {
return
(
<>
<
Layout
>
<
div
style
=
{{
height
:
'100%'
}}
>
<
Nav
// bodyStyle={{ maxWidth: 200 }}
style
=
{{
maxWidth
:
200
}}
defaultIsCollapsed
=
{
isMobile
()
||
localStorage
.
getItem
(
'default_collapse_sidebar'
)
===
'true'
}
isCollapsed
=
{
isCollapsed
}
onCollapseChange
=
{(
collapsed
)
=>
{
setIsCollapsed
(
collapsed
);
}}
selectedKeys
=
{
selectedKeys
}
renderWrapper
=
{({
itemElement
,
isSubNav
,
isInSubNav
,
props
})
=>
{
return
(
<
Link
style
=
{{
textDecoration
:
'none'
}}
to
=
{
routerMap
[
props
.
itemKey
]}
>
{
itemElement
}
<
/Link
>
);
}}
items
=
{
headerButtons
}
onSelect
=
{(
key
)
=>
{
setSelectedKeys
([
key
.
itemKey
]);
}}
header
=
{{
logo
:
(
<
img
src
=
{
logo
}
alt
=
'logo'
style
=
{{
marginRight
:
'0.75em'
}}
/
>
),
text
:
systemName
,
}}
// footer={{
// text: '© 2021 NekoAPI',
// }}
>
<
Nav
.
Footer
collapseButton
=
{
true
}
><
/Nav.Footer
>
<
/Nav
>
<
/div
>
<
/Layout
>
<
Nav
style
=
{{
maxWidth
:
220
,
height
:
'100%'
}}
defaultIsCollapsed
=
{
isMobile
()
||
localStorage
.
getItem
(
'default_collapse_sidebar'
)
===
'true'
}
isCollapsed
=
{
isCollapsed
}
onCollapseChange
=
{(
collapsed
)
=>
{
setIsCollapsed
(
collapsed
);
}}
selectedKeys
=
{
selectedKeys
}
renderWrapper
=
{({
itemElement
,
isSubNav
,
isInSubNav
,
props
})
=>
{
return
(
<
Link
style
=
{{
textDecoration
:
'none'
}}
to
=
{
routerMap
[
props
.
itemKey
]}
>
{
itemElement
}
<
/Link
>
);
}}
items
=
{
headerButtons
}
onSelect
=
{(
key
)
=>
{
setSelectedKeys
([
key
.
itemKey
]);
}}
// header={{
// logo: (
// <img src={logo} alt='logo' style={{ marginRight: '0.75em' }} />
// ),
// text: systemName,
// }}
// footer={{
// text: '© 2021 NekoAPI',
// }}
footer
=
{
<>
{
isMobile
()
&&
(
<
Switch
checkedText
=
'🌞'
size
=
{
'small'
}
checked
=
{
theme
===
'dark'
}
uncheckedText
=
'🌙'
onChange
=
{(
checked
)
=>
{
setTheme
(
checked
);
}}
/
>
)}
<
/
>
}
>
<
Nav
.
Footer
collapseButton
=
{
true
}
><
/Nav.Footer
>
<
/Nav
>
<
/
>
);
};
...
...
web/src/index.css
View file @
c8028aa8
...
...
@@ -9,11 +9,12 @@ body {
scrollbar-width
:
none
;
color
:
var
(
--semi-color-text-0
)
!important
;
background-color
:
var
(
--semi-color-bg-0
)
!important
;
height
:
100
%
;
height
:
100
vh
;
}
#root
{
height
:
100%
;
height
:
100vh
;
flex-direction
:
column
;
}
@media
only
screen
and
(
max-width
:
767px
)
{
...
...
@@ -50,9 +51,9 @@ body {
}
}
.semi-layout
{
height
:
100%
;
}
/*.semi-layout {*/
/* height: 100%;*/
/*}*/
.tableShow
{
display
:
revert
;
...
...
web/src/index.js
View file @
c8028aa8
...
...
@@ -3,7 +3,6 @@ import ReactDOM from 'react-dom/client';
import
{
BrowserRouter
}
from
'react-router-dom'
;
import
App
from
'./App'
;
import
HeaderBar
from
'./components/HeaderBar'
;
import
Footer
from
'./components/Footer'
;
import
'semantic-ui-offline/semantic.min.css'
;
import
'./index.css'
;
import
{
UserProvider
}
from
'./context/User'
;
...
...
@@ -13,35 +12,36 @@ import { StatusProvider } from './context/Status';
import
{
Layout
}
from
'@douyinfe/semi-ui'
;
import
SiderBar
from
'./components/SiderBar'
;
import
{
ThemeProvider
}
from
'./context/Theme'
;
import
FooterBar
from
'./components/Footer'
;
// initialization
const
root
=
ReactDOM
.
createRoot
(
document
.
getElementById
(
'root'
));
const
{
Sider
,
Content
,
Header
}
=
Layout
;
const
{
Sider
,
Content
,
Header
,
Footer
}
=
Layout
;
root
.
render
(
<
React
.
StrictMode
>
<
StatusProvider
>
<
UserProvider
>
<
BrowserRouter
>
<
ThemeProvider
>
<
Layout
>
<
Si
der
>
<
Si
derBar
/>
<
/
Si
der
>
<
Layout
>
<
Hea
der
>
<
Hea
derBar
/>
<
/
Hea
der
>
<
Content
style
=
{{
padding
:
'24px'
,
}}
>
<
App
/
>
<
/Content
>
<
Layout
.
Foote
r
>
<
Footer
><
/
Footer
>
<
/Layout
.Footer
>
<
Layout
style
=
{{
height
:
'100vh'
,
display
:
'flex'
,
flexDirection
:
'column'
}}
>
<
Hea
der
>
<
Hea
derBar
/>
<
/
Hea
der
>
<
Layout
style
=
{{
flex
:
1
,
overflow
:
'hidden'
}}
>
<
Si
der
>
<
Si
derBar
/>
<
/
Si
der
>
<
Layout
>
<
Content
style
=
{{
overflowY
:
'auto'
,
padding
:
'24px'
}}
>
<
App
/
>
<
/Content
>
<
Layout
.
Footer
>
<
FooterBar
><
/FooterBa
r
>
<
/Layout.
Footer
>
<
/Layout
>
<
/Layout
>
<
ToastContainer
/>
<
/Layout
>
...
...
web/src/pages/Token/EditToken.js
View file @
c8028aa8
...
...
@@ -18,8 +18,8 @@ import {
Select
,
SideSheet
,
Space
,
Spin
,
Typography
,
Spin
,
TextArea
,
Typography
}
from
'@douyinfe/semi-ui'
;
import
Title
from
'@douyinfe/semi-ui/lib/es/typography/title'
;
import
{
Divider
}
from
'semantic-ui-react'
;
...
...
@@ -34,6 +34,7 @@ const EditToken = (props) => {
unlimited_quota
:
false
,
model_limits_enabled
:
false
,
model_limits
:
[],
allow_ips
:
''
,
};
const
[
inputs
,
setInputs
]
=
useState
(
originInputs
);
const
{
...
...
@@ -43,6 +44,7 @@ const EditToken = (props) => {
unlimited_quota
,
model_limits_enabled
,
model_limits
,
allow_ips
}
=
inputs
;
// const [visible, setVisible] = useState(false);
const
[
models
,
setModels
]
=
useState
({});
...
...
@@ -374,6 +376,19 @@ const EditToken = (props) => {
<
/Button
>
<
/div
>
<
Divider
/>
<
div
style
=
{{
marginTop
:
10
}}
>
<
Typography
.
Text
>
IP
白名单(请勿过度信任此功能)
<
/Typography.Text
>
<
/div
>
<
TextArea
label
=
'IP白名单'
name
=
'allow_ips'
placeholder
=
{
'允许的IP,一行一个'
}
onChange
=
{(
value
)
=>
{
handleInputChange
(
'allow_ips'
,
value
);
}}
value
=
{
inputs
.
allow_ips
}
style
=
{{
fontFamily
:
'JetBrains Mono, Consolas'
}}
/
>
<
div
style
=
{{
marginTop
:
10
,
display
:
'flex'
}}
>
<
Space
>
<
Checkbox
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment