mirror of
https://github.com/songquanpeng/one-api.git
synced 2025-11-07 17:23:42 +08:00
Compare commits
15 Commits
v0.4.8
...
v0.4.9-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
edc5156693 | ||
|
|
9ec6506c32 | ||
|
|
f387cc5ead | ||
|
|
569b68c43b | ||
|
|
f0c40a6cd0 | ||
|
|
0cea9e6a6f | ||
|
|
b1b3651e84 | ||
|
|
8f6bd51f58 | ||
|
|
bddbf57104 | ||
|
|
9a16b0f9e5 | ||
|
|
3530309a31 | ||
|
|
733ebc067b | ||
|
|
6a8567ac14 | ||
|
|
aabc546691 | ||
|
|
1c82b06f35 |
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -8,11 +8,13 @@ assignees: ''
|
||||
---
|
||||
|
||||
**例行检查**
|
||||
|
||||
[//]: # (方框内删除已有的空格,填 x 号)
|
||||
+ [ ] 我已确认目前没有类似 issue
|
||||
+ [ ] 我已确认我已升级到最新版本
|
||||
+ [ ] 我已完整查看过项目 README,尤其是常见问题部分
|
||||
+ [ ] 我理解并愿意跟进此 issue,协助测试和提供反馈
|
||||
+ [ ] 我理解并认可上述内容,并理解项目维护者精力有限,不遵循规则的 issue 可能会被无视或直接关闭
|
||||
+ [ ] 我理解并认可上述内容,并理解项目维护者精力有限,**不遵循规则的 issue 可能会被无视或直接关闭**
|
||||
|
||||
**问题描述**
|
||||
|
||||
|
||||
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -6,6 +6,3 @@ contact_links:
|
||||
- name: 赞赏支持
|
||||
url: https://iamazing.cn/page/reward
|
||||
about: 请作者喝杯咖啡,以激励作者持续开发
|
||||
- name: 付费部署或定制功能
|
||||
url: https://openai.justsong.cn/
|
||||
about: 加群后联系群主
|
||||
|
||||
5
.github/ISSUE_TEMPLATE/feature_request.md
vendored
5
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -8,10 +8,13 @@ assignees: ''
|
||||
---
|
||||
|
||||
**例行检查**
|
||||
|
||||
[//]: # (方框内删除已有的空格,填 x 号)
|
||||
+ [ ] 我已确认目前没有类似 issue
|
||||
+ [ ] 我已确认我已升级到最新版本
|
||||
+ [ ] 我已完整查看过项目 README,已确定现有版本无法满足需求
|
||||
+ [ ] 我理解并愿意跟进此 issue,协助测试和提供反馈
|
||||
+ [ ] 我理解并认可上述内容,并理解项目维护者精力有限,不遵循规则的 issue 可能会被无视或直接关闭
|
||||
+ [ ] 我理解并认可上述内容,并理解项目维护者精力有限,**不遵循规则的 issue 可能会被无视或直接关闭**
|
||||
|
||||
**功能描述**
|
||||
|
||||
|
||||
@@ -285,6 +285,10 @@ If the channel ID is not provided, load balancing will be used to distribute the
|
||||
## Note
|
||||
This project is an open-source project. Please use it in compliance with OpenAI's [Terms of Use](https://openai.com/policies/terms-of-use) and **applicable laws and regulations**. It must not be used for illegal purposes.
|
||||
|
||||
This project is open-sourced under the MIT license. One must somehow retain the copyright information of One API.
|
||||
This project is released under the MIT license. Based on this, attribution and a link to this project must be included at the bottom of the page.
|
||||
|
||||
The same applies to derivative projects based on this project.
|
||||
|
||||
If you do not wish to include attribution, prior authorization must be obtained.
|
||||
|
||||
According to the MIT license, users should bear the risk and responsibility of using this project, and the developer of this open-source project is not responsible for this.
|
||||
|
||||
18
README.md
18
README.md
@@ -77,16 +77,17 @@ _✨ All in one 的 OpenAI 接口,整合各种 API 访问方式,开箱即用
|
||||
11. 支持**用户邀请奖励**。
|
||||
12. 支持以美元为单位显示额度。
|
||||
13. 支持发布公告,设置充值链接,设置新用户初始额度。
|
||||
14. 支持丰富的**自定义**设置,
|
||||
14. 支持模型映射,重定向用户的请求模型。
|
||||
15. 支持丰富的**自定义**设置,
|
||||
1. 支持自定义系统名称,logo 以及页脚。
|
||||
2. 支持自定义首页和关于页面,可以选择使用 HTML & Markdown 代码进行自定义,或者使用一个单独的网页通过 iframe 嵌入。
|
||||
15. 支持通过系统访问令牌访问管理 API。
|
||||
16. 支持 Cloudflare Turnstile 用户校验。
|
||||
17. 支持用户管理,支持**多种用户登录注册方式**:
|
||||
16. 支持通过系统访问令牌访问管理 API。
|
||||
17. 支持 Cloudflare Turnstile 用户校验。
|
||||
18. 支持用户管理,支持**多种用户登录注册方式**:
|
||||
+ 邮箱登录注册以及通过邮箱进行密码重置。
|
||||
+ [GitHub 开放授权](https://github.com/settings/applications/new)。
|
||||
+ 微信公众号授权(需要额外部署 [WeChat Server](https://github.com/songquanpeng/wechat-server))。
|
||||
18. 未来其他大模型开放 API 后,将第一时间支持,并将其封装成同样的 API 访问方式。
|
||||
19. 未来其他大模型开放 API 后,将第一时间支持,并将其封装成同样的 API 访问方式。
|
||||
|
||||
## 部署
|
||||
### 基于 Docker 进行部署
|
||||
@@ -111,6 +112,7 @@ server{
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
proxy_set_header Accept-Encoding gzip;
|
||||
proxy_read_timeout 300s; # GPT-4 需要较长的超时时间,请自行调整
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -306,6 +308,8 @@ https://openai.justsong.cn
|
||||
5. ChatGPT Next Web 报错:`Failed to fetch`
|
||||
+ 部署的时候不要设置 `BASE_URL`。
|
||||
+ 检查你的接口地址和 API Key 有没有填对。
|
||||
6. 报错:`当前分组负载已饱和,请稍后再试`
|
||||
+ 上游通道 429 了。
|
||||
|
||||
## 相关项目
|
||||
[FastGPT](https://github.com/c121914yu/FastGPT): 三分钟搭建 AI 知识库
|
||||
@@ -313,6 +317,8 @@ https://openai.justsong.cn
|
||||
## 注意
|
||||
本项目为开源项目,请在遵循 OpenAI 的[使用条款](https://openai.com/policies/terms-of-use)以及**法律法规**的情况下使用,不得用于非法用途。
|
||||
|
||||
本项目使用 MIT 协议进行开源,请以某种方式保留 One API 的版权信息。
|
||||
本项目使用 MIT 协议进行开源,**在此基础上**,必须在页面底部保留署名以及指向本项目的链接。如果不想保留署名,必须首先获得授权。
|
||||
|
||||
同样适用于基于本项目的二开项目。
|
||||
|
||||
依据 MIT 协议,使用者需自行承担使用本项目的风险与责任,本开源项目开发者与此无关。
|
||||
@@ -148,6 +148,7 @@ const (
|
||||
ChannelTypeAIProxy = 10
|
||||
ChannelTypePaLM = 11
|
||||
ChannelTypeAPI2GPT = 12
|
||||
ChannelTypeAIGC2D = 13
|
||||
)
|
||||
|
||||
var ChannelBaseURLs = []string{
|
||||
@@ -164,4 +165,5 @@ var ChannelBaseURLs = []string{
|
||||
"https://api.aiproxy.io", // 10
|
||||
"", // 11
|
||||
"https://api.api2gpt.com", // 12
|
||||
"https://api.aigc2d.com", // 13
|
||||
}
|
||||
|
||||
@@ -61,6 +61,14 @@ type API2GPTUsageResponse struct {
|
||||
TotalRemaining float64 `json:"total_remaining"`
|
||||
}
|
||||
|
||||
type APGC2DGPTUsageResponse struct {
|
||||
//Grants interface{} `json:"grants"`
|
||||
Object string `json:"object"`
|
||||
TotalAvailable float64 `json:"total_available"`
|
||||
TotalGranted float64 `json:"total_granted"`
|
||||
TotalUsed float64 `json:"total_used"`
|
||||
}
|
||||
|
||||
// GetAuthHeader get auth header
|
||||
func GetAuthHeader(token string) http.Header {
|
||||
h := http.Header{}
|
||||
@@ -150,6 +158,21 @@ func updateChannelAPI2GPTBalance(channel *model.Channel) (float64, error) {
|
||||
return response.TotalRemaining, nil
|
||||
}
|
||||
|
||||
func updateChannelAIGC2DBalance(channel *model.Channel) (float64, error) {
|
||||
url := "https://api.aigc2d.com/dashboard/billing/credit_grants"
|
||||
body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
response := APGC2DGPTUsageResponse{}
|
||||
err = json.Unmarshal(body, &response)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
channel.UpdateBalance(response.TotalAvailable)
|
||||
return response.TotalAvailable, nil
|
||||
}
|
||||
|
||||
func updateChannelBalance(channel *model.Channel) (float64, error) {
|
||||
baseURL := common.ChannelBaseURLs[channel.Type]
|
||||
switch channel.Type {
|
||||
@@ -167,6 +190,8 @@ func updateChannelBalance(channel *model.Channel) (float64, error) {
|
||||
return updateChannelAIProxyBalance(channel)
|
||||
case common.ChannelTypeAPI2GPT:
|
||||
return updateChannelAPI2GPTBalance(channel)
|
||||
case common.ChannelTypeAIGC2D:
|
||||
return updateChannelAIGC2DBalance(channel)
|
||||
default:
|
||||
return 0, errors.New("尚未实现")
|
||||
}
|
||||
|
||||
@@ -180,10 +180,10 @@ func UpdateToken(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
if token.Status == common.TokenStatusEnabled {
|
||||
if cleanToken.Status == common.TokenStatusExpired && cleanToken.ExpiredTime <= common.GetTimestamp() {
|
||||
if cleanToken.Status == common.TokenStatusExpired && cleanToken.ExpiredTime <= common.GetTimestamp() && cleanToken.ExpiredTime != -1 {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "令牌已过期,无法启用,请先修改令牌过期时间",
|
||||
"message": "令牌已过期,无法启用,请先修改令牌过期时间,或者设置为永不过期",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"通过令牌「%s」使用模型 %s 消耗 %s(模型倍率 %.2f,分组倍率 %.2f)": "Using model %s with token %s consumes %s (model rate %.2f, group rate %.2f)",
|
||||
"当前分组负载已饱和,请稍后再试,或升级账户以提升服务质量。": "The current group load is saturated, please try again later, or upgrade your account to improve service quality.",
|
||||
"令牌名称长度必须在1-20之间": "The length of the token name must be between 1-20",
|
||||
"令牌已过期,无法启用,请先修改令牌过期时间": "The token has expired and cannot be enabled. Please modify the token expiration time first",
|
||||
"令牌已过期,无法启用,请先修改令牌过期时间,或者设置为永不过期": "The token has expired and cannot be enabled. Please modify the expiration time of the token, or set it to never expire.",
|
||||
"令牌可用额度已用尽,无法启用,请先修改令牌剩余额度,或者设置为无限额度": "The available quota of the token has been used up and cannot be enabled. Please modify the remaining quota of the token, or set it to unlimited quota",
|
||||
"管理员关闭了密码登录": "The administrator has turned off password login",
|
||||
"无法保存会话信息,请重试": "Unable to save session information, please try again",
|
||||
@@ -456,5 +456,7 @@
|
||||
"提示": "Prompt",
|
||||
"补全": "Completion",
|
||||
"消耗额度": "Used Quota",
|
||||
"可选值": "Optional Values"
|
||||
"可选值": "Optional Values",
|
||||
"渠道不存在:%d": "Channel does not exist: %d",
|
||||
"数据库一致性已被破坏,请联系管理员": "Database consistency has been broken, please contact the administrator"
|
||||
}
|
||||
|
||||
@@ -75,9 +75,14 @@ func Distribute() func(c *gin.Context) {
|
||||
}
|
||||
channel, err = model.CacheGetRandomSatisfiedChannel(userGroup, modelRequest.Model)
|
||||
if err != nil {
|
||||
message := "无可用渠道"
|
||||
if channel != nil {
|
||||
common.SysError(fmt.Sprintf("渠道不存在:%d", channel.Id))
|
||||
message = "数据库一致性已被破坏,请联系管理员"
|
||||
}
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{
|
||||
"error": gin.H{
|
||||
"message": "无可用渠道",
|
||||
"message": message,
|
||||
"type": "one_api_error",
|
||||
},
|
||||
})
|
||||
|
||||
@@ -24,6 +24,7 @@ func GetRandomSatisfiedChannel(group string, model string) (*Channel, error) {
|
||||
return nil, err
|
||||
}
|
||||
channel := Channel{}
|
||||
channel.Id = ability.ChannelId
|
||||
err = DB.First(&channel, "id = ?", ability.ChannelId).Error
|
||||
return &channel, err
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ func GetAllChannels(startIdx int, num int, selectAll bool) ([]*Channel, error) {
|
||||
}
|
||||
|
||||
func SearchChannels(keyword string) (channels []*Channel, err error) {
|
||||
err = DB.Omit("key").Where("id = ? or name LIKE ? or key = ?", keyword, keyword+"%", keyword).Find(&channels).Error
|
||||
err = DB.Omit("key").Where("id = ? or name LIKE ? or `key` = ?", keyword, keyword+"%", keyword).Find(&channels).Error
|
||||
return channels, err
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"one-api/common"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
@@ -14,6 +15,10 @@ func SetRouter(router *gin.Engine, buildFS embed.FS, indexPage []byte) {
|
||||
SetDashboardRouter(router)
|
||||
SetRelayRouter(router)
|
||||
frontendBaseUrl := os.Getenv("FRONTEND_BASE_URL")
|
||||
if common.IsMasterNode && frontendBaseUrl != "" {
|
||||
frontendBaseUrl = ""
|
||||
common.SysLog("FRONTEND_BASE_URL is ignored on master node")
|
||||
}
|
||||
if frontendBaseUrl == "" {
|
||||
SetWebRouter(router, buildFS, indexPage)
|
||||
} else {
|
||||
|
||||
@@ -38,6 +38,8 @@ function renderBalance(type, balance) {
|
||||
return <span>{renderNumber(balance)}</span>;
|
||||
case 12: // API2GPT
|
||||
return <span>¥{balance.toFixed(2)}</span>;
|
||||
case 13: // AIGC2D
|
||||
return <span>{renderNumber(balance)}</span>;
|
||||
default:
|
||||
return <span>不支持</span>;
|
||||
}
|
||||
@@ -58,8 +60,8 @@ const ChannelsTable = () => {
|
||||
if (startIdx === 0) {
|
||||
setChannels(data);
|
||||
} else {
|
||||
let newChannels = channels;
|
||||
newChannels.push(...data);
|
||||
let newChannels = [...channels];
|
||||
newChannels.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data);
|
||||
setChannels(newChannels);
|
||||
}
|
||||
} else {
|
||||
@@ -80,7 +82,7 @@ const ChannelsTable = () => {
|
||||
|
||||
const refresh = async () => {
|
||||
setLoading(true);
|
||||
await loadChannels(0);
|
||||
await loadChannels(activePage - 1);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -238,7 +240,7 @@ const ChannelsTable = () => {
|
||||
if (channels.length === 0) return;
|
||||
setLoading(true);
|
||||
let sortedChannels = [...channels];
|
||||
if (typeof sortedChannels[0][key] === 'string'){
|
||||
if (typeof sortedChannels[0][key] === 'string') {
|
||||
sortedChannels.sort((a, b) => {
|
||||
return ('' + a[key]).localeCompare(b[key]);
|
||||
});
|
||||
|
||||
@@ -108,7 +108,7 @@ const LogsTable = () => {
|
||||
setLogs(data);
|
||||
} else {
|
||||
let newLogs = [...logs];
|
||||
newLogs.push(...data);
|
||||
newLogs.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data);
|
||||
setLogs(newLogs);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -74,9 +74,6 @@ const OperationSetting = () => {
|
||||
const submitConfig = async (group) => {
|
||||
switch (group) {
|
||||
case 'monitor':
|
||||
if (originInputs['AutomaticDisableChannelEnabled'] !== inputs.AutomaticDisableChannelEnabled) {
|
||||
await updateOption('AutomaticDisableChannelEnabled', inputs.AutomaticDisableChannelEnabled);
|
||||
}
|
||||
if (originInputs['ChannelDisableThreshold'] !== inputs.ChannelDisableThreshold) {
|
||||
await updateOption('ChannelDisableThreshold', inputs.ChannelDisableThreshold);
|
||||
}
|
||||
|
||||
@@ -45,8 +45,8 @@ const TokensTable = () => {
|
||||
if (startIdx === 0) {
|
||||
setTokens(data);
|
||||
} else {
|
||||
let newTokens = tokens;
|
||||
newTokens.push(...data);
|
||||
let newTokens = [...tokens];
|
||||
newTokens.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data);
|
||||
setTokens(newTokens);
|
||||
}
|
||||
} else {
|
||||
@@ -67,7 +67,7 @@ const TokensTable = () => {
|
||||
|
||||
const refresh = async () => {
|
||||
setLoading(true);
|
||||
await loadTokens(0);
|
||||
await loadTokens(activePage - 1);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -9,5 +9,6 @@ export const CHANNEL_OPTIONS = [
|
||||
{ key: 7, text: 'OhMyGPT', value: 7, color: 'purple' },
|
||||
{ key: 9, text: 'AI.LS', value: 9, color: 'yellow' },
|
||||
{ key: 10, text: 'AI Proxy', value: 10, color: 'purple' },
|
||||
{ key: 12, text: 'API2GPT', value: 12, color: 'blue' }
|
||||
{ key: 12, text: 'API2GPT', value: 12, color: 'blue' },
|
||||
{ key: 13, text: 'AIGC2D', value: 13, color: 'purple' }
|
||||
];
|
||||
Reference in New Issue
Block a user