diff --git a/README.md b/README.md
index 66eef0e1..07fa3c85 100644
--- a/README.md
+++ b/README.md
@@ -410,6 +410,12 @@ https://openai.justsong.cn
+ 检查是否启用了 HTTPS,浏览器会拦截 HTTPS 域名下的 HTTP 请求。
6. 报错:`当前分组负载已饱和,请稍后再试`
+ 上游通道 429 了。
+7. 升级之后我的数据会丢失吗?
+ + 如果使用 MySQL,不会。
+ + 如果使用 SQLite,需要按照我所给的部署命令挂载 volume 持久化 one-api.db 数据库文件,否则容器重启后数据会丢失。
+8. 升级之前数据库需要做变更吗?
+ + 一般情况下不需要,系统将在初始化的时候自动调整。
+ + 如果需要的话,我会在更新日志中说明,并给出脚本。
## 相关项目
* [FastGPT](https://github.com/labring/FastGPT): 基于 LLM 大语言模型的知识库问答系统
diff --git a/common/constants.go b/common/constants.go
index b84f27ad..6bdb31a4 100644
--- a/common/constants.go
+++ b/common/constants.go
@@ -165,9 +165,10 @@ const (
)
const (
- ChannelStatusUnknown = 0
- ChannelStatusEnabled = 1 // don't use 0, 0 is the default value!
- ChannelStatusDisabled = 2 // also don't use 0
+ ChannelStatusUnknown = 0
+ ChannelStatusEnabled = 1 // don't use 0, 0 is the default value!
+ ChannelStatusManuallyDisabled = 2 // also don't use 0
+ ChannelStatusAutoDisabled = 3
)
const (
diff --git a/controller/channel-test.go b/controller/channel-test.go
index caadd2f2..f9b31a1d 100644
--- a/controller/channel-test.go
+++ b/controller/channel-test.go
@@ -209,7 +209,7 @@ func disableChannel(channelId int, channelName string, reason string) {
if common.RootUserEmail == "" {
common.RootUserEmail = model.GetRootUserEmail()
}
- model.UpdateChannelStatusById(channelId, common.ChannelStatusDisabled)
+ model.UpdateChannelStatusById(channelId, common.ChannelStatusAutoDisabled)
subject := fmt.Sprintf("通道「%s」(#%d)已被禁用", channelName, channelId)
content := fmt.Sprintf("通道「%s」(#%d)已被禁用,原因:%s", channelName, channelId, reason)
err := common.SendEmail(subject, common.RootUserEmail, content)
diff --git a/controller/channel.go b/controller/channel.go
index d2403003..852fbf1b 100644
--- a/controller/channel.go
+++ b/controller/channel.go
@@ -128,6 +128,23 @@ func DeleteChannel(c *gin.Context) {
return
}
+func DeleteManuallyDisabledChannel(c *gin.Context) {
+ rows, err := model.DeleteChannelByStatus(common.ChannelStatusManuallyDisabled)
+ if err != nil {
+ c.JSON(http.StatusOK, gin.H{
+ "success": false,
+ "message": err.Error(),
+ })
+ return
+ }
+ c.JSON(http.StatusOK, gin.H{
+ "success": true,
+ "message": "",
+ "data": rows,
+ })
+ return
+}
+
func UpdateChannel(c *gin.Context) {
channel := model.Channel{}
err := c.ShouldBindJSON(&channel)
diff --git a/model/channel.go b/model/channel.go
index 76db6dd9..d20a5172 100644
--- a/model/channel.go
+++ b/model/channel.go
@@ -187,3 +187,8 @@ func updateChannelUsedQuota(id int, quota int) {
common.SysError("failed to update channel used quota: " + err.Error())
}
}
+
+func DeleteChannelByStatus(status int64) (int64, error) {
+ result := DB.Where("status = ?", status).Delete(&Channel{})
+ return result.RowsAffected, result.Error
+}
diff --git a/model/log.go b/model/log.go
index c189e01d..d26da9a2 100644
--- a/model/log.go
+++ b/model/log.go
@@ -8,14 +8,14 @@ import (
)
type Log struct {
- Id int `json:"id"`
+ Id int `json:"id;index:idx_created_at_id,priority:1"`
UserId int `json:"user_id" gorm:"index"`
- CreatedAt int64 `json:"created_at" gorm:"bigint;index"`
- Type int `json:"type" gorm:"index"`
+ CreatedAt int64 `json:"created_at" gorm:"bigint;index:idx_created_at_id,priority:2;index:idx_created_at_type"`
+ Type int `json:"type" gorm:"index:idx_created_at_type"`
Content string `json:"content"`
- Username string `json:"username" gorm:"index;default:''"`
+ Username string `json:"username" gorm:"index:index_username_model_name,priority:2;default:''"`
TokenName string `json:"token_name" gorm:"index;default:''"`
- ModelName string `json:"model_name" gorm:"index;default:''"`
+ ModelName string `json:"model_name" gorm:"index;index:index_username_model_name,priority:1;default:''"`
Quota int `json:"quota" gorm:"default:0"`
PromptTokens int `json:"prompt_tokens" gorm:"default:0"`
CompletionTokens int `json:"completion_tokens" gorm:"default:0"`
diff --git a/model/main.go b/model/main.go
index 852f14e8..08182634 100644
--- a/model/main.go
+++ b/model/main.go
@@ -82,6 +82,7 @@ func InitDB() (err error) {
if !common.IsMasterNode {
return nil
}
+ common.SysLog("database migration started")
err = db.AutoMigrate(&Channel{})
if err != nil {
return err
diff --git a/router/api-router.go b/router/api-router.go
index a2bcc8b6..f2a9fc4e 100644
--- a/router/api-router.go
+++ b/router/api-router.go
@@ -76,6 +76,7 @@ func SetApiRouter(router *gin.Engine) {
channelRoute.GET("/update_balance/:id", controller.UpdateChannelBalance)
channelRoute.POST("/", controller.AddChannel)
channelRoute.PUT("/", controller.UpdateChannel)
+ channelRoute.DELETE("/manually_disabled", controller.DeleteManuallyDisabledChannel)
channelRoute.DELETE("/:id", controller.DeleteChannel)
}
tokenRoute := apiRouter.Group("/token")
diff --git a/web/src/components/ChannelsTable.js b/web/src/components/ChannelsTable.js
index 519910f4..d5a29007 100644
--- a/web/src/components/ChannelsTable.js
+++ b/web/src/components/ChannelsTable.js
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react';
-import {Button, Form, Input, Label, Pagination, Popup, Table} from 'semantic-ui-react';
+import { Button, Form, Input, Label, Pagination, Popup, Table } from 'semantic-ui-react';
import { Link } from 'react-router-dom';
import { API, showError, showInfo, showNotice, showSuccess, timestamp2string } from '../helpers';
@@ -96,7 +96,7 @@ const ChannelsTable = () => {
});
}, []);
- const manageChannel = async (id, action, idx, priority) => {
+ const manageChannel = async (id, action, idx, value) => {
let data = { id };
let res;
switch (action) {
@@ -112,10 +112,20 @@ const ChannelsTable = () => {
res = await API.put('/api/channel/', data);
break;
case 'priority':
- if (priority === '') {
+ if (value === '') {
return;
}
- data.priority = parseInt(priority);
+ data.priority = parseInt(value);
+ res = await API.put('/api/channel/', data);
+ break;
+ case 'weight':
+ if (value === '') {
+ return;
+ }
+ data.weight = parseInt(value);
+ if (data.weight < 0) {
+ data.weight = 0;
+ }
res = await API.put('/api/channel/', data);
break;
}
@@ -142,9 +152,23 @@ const ChannelsTable = () => {
return ;
case 2:
return (
-
+
+ 已禁用
+ }
+ content='本渠道被手动禁用'
+ basic
+ />
+ );
+ case 3:
+ return (
+
+ 已禁用
+ }
+ content='本渠道被程序自动禁用'
+ basic
+ />
);
default:
return (
@@ -216,6 +240,17 @@ const ChannelsTable = () => {
}
};
+ const deleteAllManuallyDisabledChannels = async () => {
+ const res = await API.delete(`/api/channel/manually_disabled`);
+ const { success, message, data } = res.data;
+ if (success) {
+ showSuccess(`已删除所有手动禁用渠道,共计 ${data} 个`);
+ await refresh();
+ } else {
+ showError(message);
+ }
+ };
+
const updateChannelBalance = async (id, name, idx) => {
const res = await API.get(`/api/channel/update_balance/${id}/`);
const { success, message, balance } = res.data;
@@ -343,10 +378,10 @@ const ChannelsTable = () => {
余额
{
- sortChannel('priority');
- }}
+ style={{ cursor: 'pointer' }}
+ onClick={() => {
+ sortChannel('priority');
+ }}
>
优先级
@@ -390,18 +425,18 @@ const ChannelsTable = () => {
{
- manageChannel(
- channel.id,
- 'priority',
- idx,
- event.target.value,
- );
- }}>
-
- }
- content='渠道选择优先级,越高越优先'
- basic
+ trigger={ {
+ manageChannel(
+ channel.id,
+ 'priority',
+ idx,
+ event.target.value
+ );
+ }}>
+
+ }
+ content='渠道选择优先级,越高越优先'
+ basic
/>
@@ -481,6 +516,20 @@ const ChannelsTable = () => {
+
+ 删除所有手动禁用渠道
+
+ }
+ on='click'
+ flowing
+ hoverable
+ >
+
+