mirror of
https://github.com/songquanpeng/one-api.git
synced 2025-12-11 02:35:57 +08:00
Compare commits
7 Commits
v0.4.4
...
v0.4.5-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6855d0dc39 | ||
|
|
a43b1e2add | ||
|
|
46c43396d8 | ||
|
|
6dcffca065 | ||
|
|
d754620ef7 | ||
|
|
21111126a2 | ||
|
|
d91e7dcfdc |
36
README.md
36
README.md
@@ -55,10 +55,11 @@ _✨ All in one 的 OpenAI 接口,整合各种 API 访问方式,开箱即用
|
|||||||
+ [x] [API2D](https://api2d.com/r/197971)
|
+ [x] [API2D](https://api2d.com/r/197971)
|
||||||
+ [x] [OhMyGPT](https://aigptx.top?aff=uFpUl2Kf)
|
+ [x] [OhMyGPT](https://aigptx.top?aff=uFpUl2Kf)
|
||||||
+ [x] [AI Proxy](https://aiproxy.io/?i=OneAPI) (邀请码:`OneAPI`)
|
+ [x] [AI Proxy](https://aiproxy.io/?i=OneAPI) (邀请码:`OneAPI`)
|
||||||
|
+ [x] [OpenAI-SB](https://openai-sb.com)
|
||||||
|
+ [x] [API2GPT](http://console.api2gpt.com/m/00002S)
|
||||||
|
+ [x] [CloseAI](https://console.openai-asia.com/r/2412)
|
||||||
+ [x] [AI.LS](https://ai.ls)
|
+ [x] [AI.LS](https://ai.ls)
|
||||||
+ [x] [OpenAI Max](https://openaimax.com)
|
+ [x] [OpenAI Max](https://openaimax.com)
|
||||||
+ [x] [OpenAI-SB](https://openai-sb.com)
|
|
||||||
+ [x] [CloseAI](https://console.openai-asia.com/r/2412)
|
|
||||||
+ [x] 自定义渠道:例如各种未收录的第三方代理服务
|
+ [x] 自定义渠道:例如各种未收录的第三方代理服务
|
||||||
2. 支持通过**负载均衡**的方式访问多个渠道。
|
2. 支持通过**负载均衡**的方式访问多个渠道。
|
||||||
3. 支持 **stream 模式**,可以通过流式传输实现打字机效果。
|
3. 支持 **stream 模式**,可以通过流式传输实现打字机效果。
|
||||||
@@ -74,15 +75,18 @@ _✨ All in one 的 OpenAI 接口,整合各种 API 访问方式,开箱即用
|
|||||||
1. 支持自定义系统名称,logo 以及页脚。
|
1. 支持自定义系统名称,logo 以及页脚。
|
||||||
2. 支持自定义首页和关于页面,可以选择使用 HTML & Markdown 代码进行自定义,或者使用一个单独的网页通过 iframe 嵌入。
|
2. 支持自定义首页和关于页面,可以选择使用 HTML & Markdown 代码进行自定义,或者使用一个单独的网页通过 iframe 嵌入。
|
||||||
13. 支持通过系统访问令牌访问管理 API。
|
13. 支持通过系统访问令牌访问管理 API。
|
||||||
14. 支持用户管理,支持**多种用户登录注册方式**:
|
14. 支持 Cloudflare Turnstile 用户校验。
|
||||||
|
15. 支持用户管理,支持**多种用户登录注册方式**:
|
||||||
+ 邮箱登录注册以及通过邮箱进行密码重置。
|
+ 邮箱登录注册以及通过邮箱进行密码重置。
|
||||||
+ [GitHub 开放授权](https://github.com/settings/applications/new)。
|
+ [GitHub 开放授权](https://github.com/settings/applications/new)。
|
||||||
+ 微信公众号授权(需要额外部署 [WeChat Server](https://github.com/songquanpeng/wechat-server))。
|
+ 微信公众号授权(需要额外部署 [WeChat Server](https://github.com/songquanpeng/wechat-server))。
|
||||||
15. 未来其他大模型开放 API 后,将第一时间支持,并将其封装成同样的 API 访问方式。
|
16. 未来其他大模型开放 API 后,将第一时间支持,并将其封装成同样的 API 访问方式。
|
||||||
|
|
||||||
## 部署
|
## 部署
|
||||||
### 基于 Docker 进行部署
|
### 基于 Docker 进行部署
|
||||||
执行:`docker run -d --restart always -p 3000:3000 -v /home/ubuntu/data/one-api:/data justsong/one-api`
|
部署命令:`docker run --name one-api -d --restart always -p 3000:3000 -v /home/ubuntu/data/one-api:/data justsong/one-api`
|
||||||
|
|
||||||
|
更新命令:`docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower -cR`
|
||||||
|
|
||||||
`-p 3000:3000` 中的第一个 `3000` 是宿主机的端口,可以根据需要进行修改。
|
`-p 3000:3000` 中的第一个 `3000` 是宿主机的端口,可以根据需要进行修改。
|
||||||
|
|
||||||
@@ -151,6 +155,26 @@ sudo service nginx restart
|
|||||||
|
|
||||||
环境变量的具体使用方法详见[此处](#环境变量)。
|
环境变量的具体使用方法详见[此处](#环境变量)。
|
||||||
|
|
||||||
|
### 部署第三方服务配合 One API 使用
|
||||||
|
> 欢迎 PR 添加更多示例。
|
||||||
|
|
||||||
|
#### ChatGPT Next Web
|
||||||
|
项目主页:https://github.com/Yidadaa/ChatGPT-Next-Web
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --name chat-next-web -d -p 3001:3000 -e BASE_URL=https://openai.justsong.cn yidadaa/chatgpt-next-web
|
||||||
|
```
|
||||||
|
|
||||||
|
注意修改端口号和 `BASE_URL`。
|
||||||
|
|
||||||
|
#### ChatGPT Web
|
||||||
|
项目主页:https://github.com/Chanzhaoyu/chatgpt-web
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --name chatgpt-web -d -p 3002:3002 -e OPENAI_API_BASE_URL=https://openai.justsong.cn -e OPENAI_API_KEY=sk-xxx chenzhaoyu94/chatgpt-web
|
||||||
|
```
|
||||||
|
|
||||||
|
注意修改端口号、`OPENAI_API_BASE_URL` 和 `OPENAI_API_KEY`。
|
||||||
|
|
||||||
### 部署到第三方平台
|
### 部署到第三方平台
|
||||||
<details>
|
<details>
|
||||||
@@ -207,7 +231,7 @@ graph LR
|
|||||||
+ 例子:`REDIS_CONN_STRING=redis://default:redispw@localhost:49153`
|
+ 例子:`REDIS_CONN_STRING=redis://default:redispw@localhost:49153`
|
||||||
2. `SESSION_SECRET`:设置之后将使用固定的会话密钥,这样系统重新启动后已登录用户的 cookie 将依旧有效。
|
2. `SESSION_SECRET`:设置之后将使用固定的会话密钥,这样系统重新启动后已登录用户的 cookie 将依旧有效。
|
||||||
+ 例子:`SESSION_SECRET=random_string`
|
+ 例子:`SESSION_SECRET=random_string`
|
||||||
3. `SQL_DSN`:设置之后将使用指定数据库而非 SQLite。
|
3. `SQL_DSN`:设置之后将使用指定数据库而非 SQLite,请使用 MySQL 8.0 版本。
|
||||||
+ 例子:`SQL_DSN=root:123456@tcp(localhost:3306)/one-api`
|
+ 例子:`SQL_DSN=root:123456@tcp(localhost:3306)/one-api`
|
||||||
4. `FRONTEND_BASE_URL`:设置之后将使用指定的前端地址,而非后端地址。
|
4. `FRONTEND_BASE_URL`:设置之后将使用指定的前端地址,而非后端地址。
|
||||||
+ 例子:`FRONTEND_BASE_URL=https://openai.justsong.cn`
|
+ 例子:`FRONTEND_BASE_URL=https://openai.justsong.cn`
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/google/uuid"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
var StartTime = time.Now().Unix() // unit: second
|
var StartTime = time.Now().Unix() // unit: second
|
||||||
@@ -133,6 +134,7 @@ const (
|
|||||||
ChannelTypeAILS = 9
|
ChannelTypeAILS = 9
|
||||||
ChannelTypeAIProxy = 10
|
ChannelTypeAIProxy = 10
|
||||||
ChannelTypePaLM = 11
|
ChannelTypePaLM = 11
|
||||||
|
ChannelTypeAPI2GPT = 12
|
||||||
)
|
)
|
||||||
|
|
||||||
var ChannelBaseURLs = []string{
|
var ChannelBaseURLs = []string{
|
||||||
@@ -148,4 +150,5 @@ var ChannelBaseURLs = []string{
|
|||||||
"https://api.caipacity.com", // 9
|
"https://api.caipacity.com", // 9
|
||||||
"https://api.aiproxy.io", // 10
|
"https://api.aiproxy.io", // 10
|
||||||
"", // 11
|
"", // 11
|
||||||
|
"https://api.api2gpt.com", // 12
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,13 @@ type AIProxyUserOverviewResponse struct {
|
|||||||
} `json:"data"`
|
} `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type API2GPTUsageResponse struct {
|
||||||
|
Object string `json:"object"`
|
||||||
|
TotalGranted float64 `json:"total_granted"`
|
||||||
|
TotalUsed float64 `json:"total_used"`
|
||||||
|
TotalRemaining float64 `json:"total_remaining"`
|
||||||
|
}
|
||||||
|
|
||||||
// GetAuthHeader get auth header
|
// GetAuthHeader get auth header
|
||||||
func GetAuthHeader(token string) http.Header {
|
func GetAuthHeader(token string) http.Header {
|
||||||
h := http.Header{}
|
h := http.Header{}
|
||||||
@@ -127,6 +134,23 @@ func updateChannelAIProxyBalance(channel *model.Channel) (float64, error) {
|
|||||||
return response.Data.TotalPoints, nil
|
return response.Data.TotalPoints, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateChannelAPI2GPTBalance(channel *model.Channel) (float64, error) {
|
||||||
|
url := "https://api.api2gpt.com/dashboard/billing/credit_grants"
|
||||||
|
body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
response := API2GPTUsageResponse{}
|
||||||
|
err = json.Unmarshal(body, &response)
|
||||||
|
fmt.Print(response)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
channel.UpdateBalance(response.TotalRemaining)
|
||||||
|
return response.TotalRemaining, nil
|
||||||
|
}
|
||||||
|
|
||||||
func updateChannelBalance(channel *model.Channel) (float64, error) {
|
func updateChannelBalance(channel *model.Channel) (float64, error) {
|
||||||
baseURL := common.ChannelBaseURLs[channel.Type]
|
baseURL := common.ChannelBaseURLs[channel.Type]
|
||||||
switch channel.Type {
|
switch channel.Type {
|
||||||
@@ -142,6 +166,8 @@ func updateChannelBalance(channel *model.Channel) (float64, error) {
|
|||||||
return updateChannelOpenAISBBalance(channel)
|
return updateChannelOpenAISBBalance(channel)
|
||||||
case common.ChannelTypeAIProxy:
|
case common.ChannelTypeAIProxy:
|
||||||
return updateChannelAIProxyBalance(channel)
|
return updateChannelAIProxyBalance(channel)
|
||||||
|
case common.ChannelTypeAPI2GPT:
|
||||||
|
return updateChannelAPI2GPTBalance(channel)
|
||||||
default:
|
default:
|
||||||
return 0, errors.New("尚未实现")
|
return 0, errors.New("尚未实现")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -259,8 +259,9 @@ func relayHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
common.SysError("Error consuming token remain quota: " + err.Error())
|
common.SysError("Error consuming token remain quota: " + err.Error())
|
||||||
}
|
}
|
||||||
|
tokenName := c.GetString("token_name")
|
||||||
userId := c.GetInt("id")
|
userId := c.GetInt("id")
|
||||||
model.RecordLog(userId, model.LogTypeConsume, fmt.Sprintf("使用模型 %s 消耗 %d 点额度(模型倍率 %.2f,分组倍率 %.2f)", textRequest.Model, quota, modelRatio, groupRatio))
|
model.RecordLog(userId, model.LogTypeConsume, fmt.Sprintf("通过令牌「%s」使用模型 %s 消耗 %d 点额度(模型倍率 %.2f,分组倍率 %.2f)", tokenName, textRequest.Model, quota, modelRatio, groupRatio))
|
||||||
model.UpdateUserUsedQuotaAndRequestCount(userId, quota)
|
model.UpdateUserUsedQuotaAndRequestCount(userId, quota)
|
||||||
channelId := c.GetInt("channel_id")
|
channelId := c.GetInt("channel_id")
|
||||||
model.UpdateChannelUsedQuota(channelId, quota)
|
model.UpdateChannelUsedQuota(channelId, quota)
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ func TokenAuth() func(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
c.Set("id", token.UserId)
|
c.Set("id", token.UserId)
|
||||||
c.Set("token_id", token.Id)
|
c.Set("token_id", token.Id)
|
||||||
|
c.Set("token_name", token.Name)
|
||||||
requestURL := c.Request.URL.String()
|
requestURL := c.Request.URL.String()
|
||||||
consumeQuota := true
|
consumeQuota := true
|
||||||
if strings.HasPrefix(requestURL, "/v1/models") {
|
if strings.HasPrefix(requestURL, "/v1/models") {
|
||||||
|
|||||||
@@ -10,6 +10,6 @@ func CORS() gin.HandlerFunc {
|
|||||||
config.AllowAllOrigins = true
|
config.AllowAllOrigins = true
|
||||||
config.AllowCredentials = true
|
config.AllowCredentials = true
|
||||||
config.AllowMethods = []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}
|
config.AllowMethods = []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}
|
||||||
config.AllowHeaders = []string{"Origin", "Content-Length", "Content-Type", "Authorization", "Accept", "Connection"}
|
config.AllowHeaders = []string{"Origin", "Content-Length", "Content-Type", "Authorization", "Accept", "Connection", "x-requested-with"}
|
||||||
return cors.New(config)
|
return cors.New(config)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ function renderBalance(type, balance) {
|
|||||||
return <span>¥{(balance / 10000).toFixed(2)}</span>;
|
return <span>¥{(balance / 10000).toFixed(2)}</span>;
|
||||||
case 10: // AI Proxy
|
case 10: // AI Proxy
|
||||||
return <span>{renderNumber(balance)}</span>;
|
return <span>{renderNumber(balance)}</span>;
|
||||||
|
case 12: // API2GPT
|
||||||
|
return <span>¥{balance.toFixed(2)}</span>;
|
||||||
default:
|
default:
|
||||||
return <span>不支持</span>;
|
return <span>不支持</span>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,5 +8,6 @@ export const CHANNEL_OPTIONS = [
|
|||||||
{ key: 6, text: 'OpenAI Max', value: 6, color: 'violet' },
|
{ key: 6, text: 'OpenAI Max', value: 6, color: 'violet' },
|
||||||
{ key: 7, text: 'OhMyGPT', value: 7, color: 'purple' },
|
{ key: 7, text: 'OhMyGPT', value: 7, color: 'purple' },
|
||||||
{ key: 9, text: 'AI.LS', value: 9, color: 'yellow' },
|
{ key: 9, text: 'AI.LS', value: 9, color: 'yellow' },
|
||||||
{ key: 10, text: 'AI Proxy', value: 10, color: 'purple' }
|
{ key: 10, text: 'AI Proxy', value: 10, color: 'purple' },
|
||||||
|
{ key: 12, text: 'API2GPT', value: 12, color: 'blue' }
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user