Merge remote-tracking branch 'origin/main'
This commit is contained in:
commit
943619db52
@ -285,6 +285,10 @@ If the channel ID is not provided, load balancing will be used to distribute the
|
|||||||
## Note
|
## 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 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.
|
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.
|
||||||
|
19
README.md
19
README.md
@ -93,6 +93,10 @@ _✨ All in one 的 OpenAI 接口,整合各种 API 访问方式,开箱即用
|
|||||||
### 基于 Docker 进行部署
|
### 基于 Docker 进行部署
|
||||||
部署命令:`docker run --name one-api -d --restart always -p 3000:3000 -e TZ=Asia/Shanghai -v /home/ubuntu/data/one-api:/data justsong/one-api`
|
部署命令:`docker run --name one-api -d --restart always -p 3000:3000 -e TZ=Asia/Shanghai -v /home/ubuntu/data/one-api:/data justsong/one-api`
|
||||||
|
|
||||||
|
如果上面的镜像无法拉取,可以尝试使用 GitHub 的 Docker 镜像,将上面的 `justsong/one-api` 替换为 `ghcr.io/songquanpeng/one-api` 即可。
|
||||||
|
|
||||||
|
如果你的并发量较大,推荐设置 `SQL_DSN`,详见下面[环境变量](#环境变量)一节。
|
||||||
|
|
||||||
更新命令:`docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower -cR`
|
更新命令:`docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower -cR`
|
||||||
|
|
||||||
`-p 3000:3000` 中的第一个 `3000` 是宿主机的端口,可以根据需要进行修改。
|
`-p 3000:3000` 中的第一个 `3000` 是宿主机的端口,可以根据需要进行修改。
|
||||||
@ -158,8 +162,8 @@ sudo service nginx restart
|
|||||||
### 多机部署
|
### 多机部署
|
||||||
1. 所有服务器 `SESSION_SECRET` 设置一样的值。
|
1. 所有服务器 `SESSION_SECRET` 设置一样的值。
|
||||||
2. 必须设置 `SQL_DSN`,使用 MySQL 数据库而非 SQLite,所有服务器连接同一个数据库。
|
2. 必须设置 `SQL_DSN`,使用 MySQL 数据库而非 SQLite,所有服务器连接同一个数据库。
|
||||||
3. 所有从服务器必须设置 `NODE_TYPE` 为 `slave`。
|
3. 所有从服务器必须设置 `NODE_TYPE` 为 `slave`,不设置则默认为主服务器。
|
||||||
4. 设置 `SYNC_FREQUENCY` 后服务器将定期从数据库同步配置。
|
4. 设置 `SYNC_FREQUENCY` 后服务器将定期从数据库同步配置,在使用远程数据库的情况下,推荐设置该项并启用 Redis,无论主从。
|
||||||
5. 从服务器可以选择设置 `FRONTEND_BASE_URL`,以重定向页面请求到主服务器。
|
5. 从服务器可以选择设置 `FRONTEND_BASE_URL`,以重定向页面请求到主服务器。
|
||||||
6. 从服务器上**分别**装好 Redis,设置好 `REDIS_CONN_STRING`,这样可以做到在缓存未过期的情况下数据库零访问,可以减少延迟。
|
6. 从服务器上**分别**装好 Redis,设置好 `REDIS_CONN_STRING`,这样可以做到在缓存未过期的情况下数据库零访问,可以减少延迟。
|
||||||
7. 如果主服务器访问数据库延迟也比较高,则也需要启用 Redis,并设置 `SYNC_FREQUENCY`,以定期从数据库同步配置。
|
7. 如果主服务器访问数据库延迟也比较高,则也需要启用 Redis,并设置 `SYNC_FREQUENCY`,以定期从数据库同步配置。
|
||||||
@ -231,6 +235,8 @@ docker run --name chatgpt-web -d -p 3002:3002 -e OPENAI_API_BASE_URL=https://ope
|
|||||||
|
|
||||||
等到系统启动后,使用 `root` 用户登录系统并做进一步的配置。
|
等到系统启动后,使用 `root` 用户登录系统并做进一步的配置。
|
||||||
|
|
||||||
|
**Note**:如果你不知道某个配置项的含义,可以临时删掉值以看到进一步的提示文字。
|
||||||
|
|
||||||
## 使用方法
|
## 使用方法
|
||||||
在`渠道`页面中添加你的 API Key,之后在`令牌`页面中新增访问令牌。
|
在`渠道`页面中添加你的 API Key,之后在`令牌`页面中新增访问令牌。
|
||||||
|
|
||||||
@ -261,7 +267,10 @@ graph LR
|
|||||||
+ 例子:`SESSION_SECRET=random_string`
|
+ 例子:`SESSION_SECRET=random_string`
|
||||||
3. `SQL_DSN`:设置之后将使用指定数据库而非 SQLite,请使用 MySQL 8.0 版本。
|
3. `SQL_DSN`:设置之后将使用指定数据库而非 SQLite,请使用 MySQL 8.0 版本。
|
||||||
+ 例子:`SQL_DSN=root:123456@tcp(localhost:3306)/oneapi`
|
+ 例子:`SQL_DSN=root:123456@tcp(localhost:3306)/oneapi`
|
||||||
4. `FRONTEND_BASE_URL`:设置之后将使用指定的前端地址,而非后端地址,仅限从服务器设置。
|
+ 注意需要提前建立数据库 `oneapi`,无需手动建表,程序将自动建表。
|
||||||
|
+ 如果使用本地数据库:部署命令可添加 `--network="host"` 以使得容器内的程序可以访问到宿主机上的 MySQL。
|
||||||
|
+ 如果使用云数据库:如果云服务器需要验证身份,需要在连接参数中添加 `?tls=skip-verify`。
|
||||||
|
4. `FRONTEND_BASE_URL`:设置之后将重定向页面请求到指定的地址,仅限从服务器设置。
|
||||||
+ 例子:`FRONTEND_BASE_URL=https://openai.justsong.cn`
|
+ 例子:`FRONTEND_BASE_URL=https://openai.justsong.cn`
|
||||||
5. `SYNC_FREQUENCY`:设置之后将定期与数据库同步配置,单位为秒,未设置则不进行同步。
|
5. `SYNC_FREQUENCY`:设置之后将定期与数据库同步配置,单位为秒,未设置则不进行同步。
|
||||||
+ 例子:`SYNC_FREQUENCY=60`
|
+ 例子:`SYNC_FREQUENCY=60`
|
||||||
@ -317,6 +326,8 @@ https://openai.justsong.cn
|
|||||||
## 注意
|
## 注意
|
||||||
本项目为开源项目,请在遵循 OpenAI 的[使用条款](https://openai.com/policies/terms-of-use)以及**法律法规**的情况下使用,不得用于非法用途。
|
本项目为开源项目,请在遵循 OpenAI 的[使用条款](https://openai.com/policies/terms-of-use)以及**法律法规**的情况下使用,不得用于非法用途。
|
||||||
|
|
||||||
本项目使用 MIT 协议进行开源,请以某种方式保留 One API 的版权信息。
|
本项目使用 MIT 协议进行开源,**在此基础上**,必须在页面底部保留署名以及指向本项目的链接。如果不想保留署名,必须首先获得授权。
|
||||||
|
|
||||||
|
同样适用于基于本项目的二开项目。
|
||||||
|
|
||||||
依据 MIT 协议,使用者需自行承担使用本项目的风险与责任,本开源项目开发者与此无关。
|
依据 MIT 协议,使用者需自行承担使用本项目的风险与责任,本开源项目开发者与此无关。
|
@ -180,10 +180,10 @@ func UpdateToken(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if token.Status == common.TokenStatusEnabled {
|
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{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"success": false,
|
"success": false,
|
||||||
"message": "令牌已过期,无法启用,请先修改令牌过期时间",
|
"message": "令牌已过期,无法启用,请先修改令牌过期时间,或者设置为永不过期",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
"通过令牌「%s」使用模型 %s 消耗 %s(模型倍率 %.2f,分组倍率 %.2f)": "Using model %s with token %s consumes %s (model rate %.2f, group rate %.2f)",
|
"通过令牌「%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.",
|
"当前分组负载已饱和,请稍后再试,或升级账户以提升服务质量。": "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",
|
"令牌名称长度必须在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 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",
|
"管理员关闭了密码登录": "The administrator has turned off password login",
|
||||||
"无法保存会话信息,请重试": "Unable to save session information, please try again",
|
"无法保存会话信息,请重试": "Unable to save session information, please try again",
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"one-api/common"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@ -14,6 +15,10 @@ func SetRouter(router *gin.Engine, buildFS embed.FS, indexPage []byte) {
|
|||||||
SetDashboardRouter(router)
|
SetDashboardRouter(router)
|
||||||
SetRelayRouter(router)
|
SetRelayRouter(router)
|
||||||
frontendBaseUrl := os.Getenv("FRONTEND_BASE_URL")
|
frontendBaseUrl := os.Getenv("FRONTEND_BASE_URL")
|
||||||
|
if common.IsMasterNode && frontendBaseUrl != "" {
|
||||||
|
frontendBaseUrl = ""
|
||||||
|
common.SysLog("FRONTEND_BASE_URL is ignored on master node")
|
||||||
|
}
|
||||||
if frontendBaseUrl == "" {
|
if frontendBaseUrl == "" {
|
||||||
SetWebRouter(router, buildFS, indexPage)
|
SetWebRouter(router, buildFS, indexPage)
|
||||||
} else {
|
} else {
|
||||||
|
@ -60,8 +60,8 @@ const ChannelsTable = () => {
|
|||||||
if (startIdx === 0) {
|
if (startIdx === 0) {
|
||||||
setChannels(data);
|
setChannels(data);
|
||||||
} else {
|
} else {
|
||||||
let newChannels = channels;
|
let newChannels = [...channels];
|
||||||
newChannels.push(...data);
|
newChannels.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data);
|
||||||
setChannels(newChannels);
|
setChannels(newChannels);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -82,7 +82,7 @@ const ChannelsTable = () => {
|
|||||||
|
|
||||||
const refresh = async () => {
|
const refresh = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
await loadChannels(0);
|
await loadChannels(activePage - 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -240,7 +240,7 @@ const ChannelsTable = () => {
|
|||||||
if (channels.length === 0) return;
|
if (channels.length === 0) return;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
let sortedChannels = [...channels];
|
let sortedChannels = [...channels];
|
||||||
if (typeof sortedChannels[0][key] === 'string'){
|
if (typeof sortedChannels[0][key] === 'string') {
|
||||||
sortedChannels.sort((a, b) => {
|
sortedChannels.sort((a, b) => {
|
||||||
return ('' + a[key]).localeCompare(b[key]);
|
return ('' + a[key]).localeCompare(b[key]);
|
||||||
});
|
});
|
||||||
|
@ -109,7 +109,7 @@ const LogsTable = () => {
|
|||||||
setLogs(data);
|
setLogs(data);
|
||||||
} else {
|
} else {
|
||||||
let newLogs = [...logs];
|
let newLogs = [...logs];
|
||||||
newLogs.push(...data);
|
newLogs.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data);
|
||||||
setLogs(newLogs);
|
setLogs(newLogs);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -45,8 +45,8 @@ const TokensTable = () => {
|
|||||||
if (startIdx === 0) {
|
if (startIdx === 0) {
|
||||||
setTokens(data);
|
setTokens(data);
|
||||||
} else {
|
} else {
|
||||||
let newTokens = tokens;
|
let newTokens = [...tokens];
|
||||||
newTokens.push(...data);
|
newTokens.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data);
|
||||||
setTokens(newTokens);
|
setTokens(newTokens);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -67,7 +67,7 @@ const TokensTable = () => {
|
|||||||
|
|
||||||
const refresh = async () => {
|
const refresh = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
await loadTokens(0);
|
await loadTokens(activePage - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -11,7 +11,7 @@ const EditToken = () => {
|
|||||||
const [loading, setLoading] = useState(isEdit);
|
const [loading, setLoading] = useState(isEdit);
|
||||||
const originInputs = {
|
const originInputs = {
|
||||||
name: '',
|
name: '',
|
||||||
remain_quota: 0,
|
remain_quota: isEdit ? 0 : 500000,
|
||||||
expired_time: -1,
|
expired_time: -1,
|
||||||
unlimited_quota: false
|
unlimited_quota: false
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user