mirror of
https://github.com/songquanpeng/one-api.git
synced 2025-10-30 13:23:42 +08:00
Compare commits
10 Commits
v0.2.0
...
v0.2.2-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
241ade2fae | ||
|
|
80065de8a3 | ||
|
|
16f53b5afb | ||
|
|
3071300c0c | ||
|
|
8b056bf408 | ||
|
|
e5640857b1 | ||
|
|
331177d97e | ||
|
|
4fed003f1a | ||
|
|
a1ea1bf696 | ||
|
|
7c66fc6c21 |
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
custom: ['https://iamazing.cn/page/reward']
|
||||||
@@ -49,7 +49,7 @@ _✨ All in one 的 OpenAI 接口,整合各种 API 访问方式,开箱即用
|
|||||||
+ [x] [OpenAI-SB](https://openai-sb.com)
|
+ [x] [OpenAI-SB](https://openai-sb.com)
|
||||||
+ [x] [OpenAI Max](https://openaimax.com)
|
+ [x] [OpenAI Max](https://openaimax.com)
|
||||||
+ [x] [OhMyGPT](https://www.ohmygpt.com)
|
+ [x] [OhMyGPT](https://www.ohmygpt.com)
|
||||||
+ [x] 自定义渠道
|
+ [x] 自定义渠道:例如使用自行搭建的 OpenAI 代理
|
||||||
2. 支持通过负载均衡的方式访问多个渠道。
|
2. 支持通过负载均衡的方式访问多个渠道。
|
||||||
3. 支持单个访问渠道设置多个 API Key,利用起来你的多个 API Key。
|
3. 支持单个访问渠道设置多个 API Key,利用起来你的多个 API Key。
|
||||||
4. 支持 HTTP SSE,可以通过流式传输实现打字机效果。
|
4. 支持 HTTP SSE,可以通过流式传输实现打字机效果。
|
||||||
|
|||||||
@@ -94,10 +94,6 @@ func relayHelper(c *gin.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = c.Request.Body.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
req.Header.Set("Authorization", c.Request.Header.Get("Authorization"))
|
req.Header.Set("Authorization", c.Request.Header.Get("Authorization"))
|
||||||
req.Header.Set("Content-Type", c.Request.Header.Get("Content-Type"))
|
req.Header.Set("Content-Type", c.Request.Header.Get("Content-Type"))
|
||||||
req.Header.Set("Accept", c.Request.Header.Get("Accept"))
|
req.Header.Set("Accept", c.Request.Header.Get("Accept"))
|
||||||
@@ -111,7 +107,10 @@ func relayHelper(c *gin.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
err = c.Request.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
var textResponse TextResponse
|
var textResponse TextResponse
|
||||||
isStream := resp.Header.Get("Content-Type") == "text/event-stream"
|
isStream := resp.Header.Get("Content-Type") == "text/event-stream"
|
||||||
var streamResponseText string
|
var streamResponseText string
|
||||||
@@ -138,7 +137,7 @@ func relayHelper(c *gin.Context) error {
|
|||||||
ratio = common.RatioGPT3dot5
|
ratio = common.RatioGPT3dot5
|
||||||
}
|
}
|
||||||
quota = int(float64(quota) * ratio)
|
quota = int(float64(quota) * ratio)
|
||||||
err := model.ConsumeTokenQuota(tokenId, quota)
|
err := model.DecreaseTokenQuota(tokenId, quota)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.SysError("Error consuming token remain quota: " + err.Error())
|
common.SysError("Error consuming token remain quota: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,30 @@ func GetToken(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetTokenStatus(c *gin.Context) {
|
||||||
|
tokenId := c.GetInt("token_id")
|
||||||
|
userId := c.GetInt("id")
|
||||||
|
token, err := model.GetTokenByIds(tokenId, userId)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
expiredAt := token.ExpiredTime
|
||||||
|
if expiredAt == -1 {
|
||||||
|
expiredAt = 0
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"object": "credit_summary",
|
||||||
|
"total_granted": token.RemainQuota,
|
||||||
|
"total_used": 0, // not supported currently
|
||||||
|
"total_available": token.RemainQuota,
|
||||||
|
"expires_at": expiredAt * 1000,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func AddToken(c *gin.Context) {
|
func AddToken(c *gin.Context) {
|
||||||
isAdmin := c.GetInt("role") >= common.RoleAdminUser
|
isAdmin := c.GetInt("role") >= common.RoleAdminUser
|
||||||
token := model.Token{}
|
token := model.Token{}
|
||||||
|
|||||||
22
docker-compose.yml
Normal file
22
docker-compose.yml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
version: '3.4'
|
||||||
|
|
||||||
|
services:
|
||||||
|
one-api:
|
||||||
|
image: ghcr.io/songquanpeng/one-api:latest
|
||||||
|
container_name: one-api
|
||||||
|
restart: always
|
||||||
|
command: --log-dir /app/logs
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
volumes:
|
||||||
|
- /home/ubuntu/data/one-api:/data
|
||||||
|
- /home/ubuntu/data/one-api/logs:/app/logs
|
||||||
|
# environment:
|
||||||
|
# REDIS_CONN_STRING: redis://default:redispw@localhost:49153
|
||||||
|
# SESSION_SECRET: random_string
|
||||||
|
# SQL_DSN: root:123456@tcp(localhost:3306)/one-api
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "curl -s http://localhost:3000/api/status | grep -o '\"success\":\\s*true' | awk '{print $2}' | grep 'true'"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
@@ -55,7 +55,7 @@ func Redeem(key string, tokenId int) (quota int, err error) {
|
|||||||
if redemption.Status != common.RedemptionCodeStatusEnabled {
|
if redemption.Status != common.RedemptionCodeStatusEnabled {
|
||||||
return 0, errors.New("该兑换码已被使用")
|
return 0, errors.New("该兑换码已被使用")
|
||||||
}
|
}
|
||||||
err = TopUpTokenQuota(tokenId, redemption.Quota)
|
err = IncreaseTokenQuota(tokenId, redemption.Quota)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,15 +116,26 @@ func DeleteTokenById(id int, userId int) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
quota := token.RemainQuota
|
||||||
|
if quota != 0 {
|
||||||
|
if quota > 0 {
|
||||||
|
err = IncreaseUserQuota(userId, quota)
|
||||||
|
} else {
|
||||||
|
err = DecreaseUserQuota(userId, -quota)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return token.Delete()
|
return token.Delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConsumeTokenQuota(id int, quota int) (err error) {
|
func IncreaseTokenQuota(id int, quota int) (err error) {
|
||||||
err = DB.Model(&Token{}).Where("id = ?", id).Update("remain_quota", gorm.Expr("remain_quota - ?", quota)).Error
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func TopUpTokenQuota(id int, quota int) (err error) {
|
|
||||||
err = DB.Model(&Token{}).Where("id = ?", id).Update("remain_quota", gorm.Expr("remain_quota + ?", quota)).Error
|
err = DB.Model(&Token{}).Where("id = ?", id).Update("remain_quota", gorm.Expr("remain_quota + ?", quota)).Error
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DecreaseTokenQuota(id int, quota int) (err error) {
|
||||||
|
err = DB.Model(&Token{}).Where("id = ?", id).Update("remain_quota", gorm.Expr("remain_quota - ?", quota)).Error
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|||||||
@@ -225,6 +225,11 @@ func GetUserQuota(id int) (quota int, err error) {
|
|||||||
return quota, err
|
return quota, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IncreaseUserQuota(id int, quota int) (err error) {
|
||||||
|
err = DB.Model(&User{}).Where("id = ?", id).Update("quota", gorm.Expr("quota + ?", quota)).Error
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func DecreaseUserQuota(id int, quota int) (err error) {
|
func DecreaseUserQuota(id int, quota int) (err error) {
|
||||||
err = DB.Model(&User{}).Where("id = ?", id).Update("quota", gorm.Expr("quota - ?", quota)).Error
|
err = DB.Model(&User{}).Where("id = ?", id).Update("quota", gorm.Expr("quota - ?", quota)).Error
|
||||||
return err
|
return err
|
||||||
|
|||||||
13
one-api.service
Normal file
13
one-api.service
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=One API Service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=yourusername # 守护进程用户名
|
||||||
|
WorkingDirectory=/path/to/One-API # One API运行路径
|
||||||
|
ExecStart=/path/to/One-API/one-api --port 3000 --log-dir /path/to/One-API/logs # 端口
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
18
router/dashboard.go
Normal file
18
router/dashboard.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-contrib/gzip"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"one-api/controller"
|
||||||
|
"one-api/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetDashboardRouter(router *gin.Engine) {
|
||||||
|
apiRouter := router.Group("/dashboard")
|
||||||
|
apiRouter.Use(gzip.Gzip(gzip.DefaultCompression))
|
||||||
|
apiRouter.Use(middleware.GlobalAPIRateLimit())
|
||||||
|
apiRouter.Use(middleware.TokenAuth())
|
||||||
|
{
|
||||||
|
apiRouter.GET("/billing/credit_grants", controller.GetTokenStatus)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
func SetRouter(router *gin.Engine, buildFS embed.FS, indexPage []byte) {
|
func SetRouter(router *gin.Engine, buildFS embed.FS, indexPage []byte) {
|
||||||
SetApiRouter(router)
|
SetApiRouter(router)
|
||||||
|
SetDashboardRouter(router)
|
||||||
SetRelayRouter(router)
|
SetRelayRouter(router)
|
||||||
setWebRouter(router, buildFS, indexPage)
|
setWebRouter(router, buildFS, indexPage)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,9 +35,4 @@ func SetRelayRouter(router *gin.Engine) {
|
|||||||
relayV1Router.DELETE("/models/:model", controller.RelayNotImplemented)
|
relayV1Router.DELETE("/models/:model", controller.RelayNotImplemented)
|
||||||
relayV1Router.POST("/moderations", controller.RelayNotImplemented)
|
relayV1Router.POST("/moderations", controller.RelayNotImplemented)
|
||||||
}
|
}
|
||||||
relayDashboardRouter := router.Group("/dashboard") // TODO: return system's own token info
|
|
||||||
relayDashboardRouter.Use(middleware.TokenAuth(), middleware.Distribute())
|
|
||||||
{
|
|
||||||
relayDashboardRouter.Any("/*path", controller.Relay)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Button, Form, Label, Pagination, Table } from 'semantic-ui-react';
|
import { Button, Form, Label, Pagination, Popup, Table } from 'semantic-ui-react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { API, showError, showSuccess } from '../helpers';
|
import { API, showError, showSuccess } from '../helpers';
|
||||||
|
|
||||||
@@ -237,15 +237,25 @@ const UsersTable = () => {
|
|||||||
>
|
>
|
||||||
降级
|
降级
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Popup
|
||||||
size={'small'}
|
trigger={
|
||||||
negative
|
<Button size='small' negative>
|
||||||
onClick={() => {
|
删除
|
||||||
manageUser(user.username, 'delete', idx);
|
</Button>
|
||||||
}}
|
}
|
||||||
|
on='click'
|
||||||
|
flowing
|
||||||
|
hoverable
|
||||||
>
|
>
|
||||||
删除
|
<Button
|
||||||
</Button>
|
negative
|
||||||
|
onClick={() => {
|
||||||
|
manageUser(user.username, 'delete', idx);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
删除用户 {user.username}
|
||||||
|
</Button>
|
||||||
|
</Popup>
|
||||||
<Button
|
<Button
|
||||||
size={'small'}
|
size={'small'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ const AddChannel = () => {
|
|||||||
<Form.Input
|
<Form.Input
|
||||||
label='Base URL'
|
label='Base URL'
|
||||||
name='base_url'
|
name='base_url'
|
||||||
placeholder={'请输入自定义渠道的 Base URL'}
|
placeholder={'请输入自定义渠道的 Base URL,例如:https://openai.justsong.cn'}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
value={inputs.base_url}
|
value={inputs.base_url}
|
||||||
autoComplete='off'
|
autoComplete='off'
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ const EditChannel = () => {
|
|||||||
<Form.Input
|
<Form.Input
|
||||||
label='Base URL'
|
label='Base URL'
|
||||||
name='base_url'
|
name='base_url'
|
||||||
placeholder={'请输入新的自定义渠道的 Base URL'}
|
placeholder={'请输入新的自定义渠道的 Base URL,例如:https://openai.justsong.cn'}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
value={inputs.base_url}
|
value={inputs.base_url}
|
||||||
autoComplete='off'
|
autoComplete='off'
|
||||||
|
|||||||
Reference in New Issue
Block a user