mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-04-05 20:50:12 +00:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4937a6d1ed | ||
|
|
de9a0d65ae | ||
|
|
ee99041910 | ||
|
|
c8a29251ac | ||
|
|
07b1c9a4db | ||
|
|
5d8de46e4c | ||
|
|
28885feea2 | ||
|
|
f693c13ce6 | ||
|
|
89cd0db28c | ||
|
|
ae57dd7b8b | ||
|
|
87d763e641 | ||
|
|
08f3562e53 | ||
|
|
88b0e6a768 | ||
|
|
a9f739a7e2 | ||
|
|
6d4edc1f5b | ||
|
|
2d1b2676f7 | ||
|
|
1035a8e0df | ||
|
|
ea433b2ed6 | ||
|
|
bb0c504709 |
14
README.md
14
README.md
@@ -71,7 +71,7 @@
|
||||
- `STREAMING_TIMEOUT`:设置流式一次回复的超时时间,默认为 60 秒。
|
||||
- `DIFY_DEBUG`:设置 Dify 渠道是否输出工作流和节点信息到客户端,默认为 `true`。
|
||||
- `FORCE_STREAM_OPTION`:是否覆盖客户端stream_options参数,请求上游返回流模式usage,默认为 `true`,建议开启,不影响客户端传入stream_options参数返回结果。
|
||||
- `GET_MEDIA_TOKEN`:是统计图片token,默认为 `true`,关闭后将不再在本地计算图片token,可能会导致和上游计费不同,此项覆盖 `GET_MEDIA_TOKEN_NOT_STREAM` 选项作用。
|
||||
- `GET_MEDIA_TOKEN`:是否统计图片token,默认为 `true`,关闭后将不再在本地计算图片token,可能会导致和上游计费不同,此项覆盖 `GET_MEDIA_TOKEN_NOT_STREAM` 选项作用。
|
||||
- `GET_MEDIA_TOKEN_NOT_STREAM`:是否在非流(`stream=false`)情况下统计图片token,默认为 `true`。
|
||||
- `UPDATE_TASK`:是否更新异步任务(Midjourney、Suno),默认为 `true`,关闭后将不会更新任务进度。
|
||||
- `GEMINI_MODEL_MAP`:Gemini模型指定版本(v1/v1beta),使用“模型:版本”指定,","分隔,例如:-e GEMINI_MODEL_MAP="gemini-1.5-pro-latest:v1beta,gemini-1.5-pro-001:v1beta",为空则使用默认配置(v1beta)
|
||||
@@ -88,6 +88,17 @@
|
||||
[图文教程](BT.md)
|
||||
|
||||
### 基于 Docker 进行部署
|
||||
### 使用 Docker Compose 部署(推荐)
|
||||
```shell
|
||||
# 下载项目
|
||||
git clone https://github.com/Calcium-Ion/new-api.git
|
||||
cd new-api
|
||||
# 按需编辑 docker-compose.yml
|
||||
# 启动
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### 直接使用 Docker 镜像
|
||||
```shell
|
||||
# 使用 SQLite 的部署命令:
|
||||
docker run --name new-api -d --restart always -p 3000:3000 -e TZ=Asia/Shanghai -v /home/ubuntu/data/new-api:/data calciumion/new-api:latest
|
||||
@@ -125,6 +136,7 @@ docker run --name new-api -d --restart always -p 3000:3000 -e SQL_DSN="root:1234
|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||
夜间模式
|
||||
|
||||
@@ -254,7 +254,7 @@ var ChannelBaseURLs = []string{
|
||||
"https://open.bigmodel.cn", // 16
|
||||
"https://dashscope.aliyuncs.com", // 17
|
||||
"", // 18
|
||||
"https://ai.360.cn", // 19
|
||||
"https://api.360.cn", // 19
|
||||
"https://openrouter.ai/api", // 20
|
||||
"https://api.aiproxy.io", // 21
|
||||
"https://fastgpt.run/api/openapi", // 22
|
||||
|
||||
@@ -150,6 +150,7 @@ var defaultModelRatio = map[string]float64{
|
||||
"360gpt-turbo": 0.0858, // ¥0.0012 / 1k tokens
|
||||
"360gpt-turbo-responsibility-8k": 0.8572, // ¥0.012 / 1k tokens
|
||||
"360gpt-pro": 0.8572, // ¥0.012 / 1k tokens
|
||||
"360gpt2-pro": 0.8572, // ¥0.012 / 1k tokens
|
||||
"embedding-bert-512-v1": 0.0715, // ¥0.001 / 1k tokens
|
||||
"embedding_s1_v1": 0.0715, // ¥0.001 / 1k tokens
|
||||
"semantic_similarity_s1_v1": 0.0715, // ¥0.001 / 1k tokens
|
||||
|
||||
@@ -3,12 +3,13 @@ package controller
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"one-api/common"
|
||||
"one-api/model"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type OpenAIModel struct {
|
||||
@@ -48,41 +49,36 @@ func GetAllChannels(c *gin.Context) {
|
||||
if pageSize < 0 {
|
||||
pageSize = common.ItemsPerPage
|
||||
}
|
||||
channelData := make([]*model.Channel, 0)
|
||||
idSort, _ := strconv.ParseBool(c.Query("id_sort"))
|
||||
channels, err := model.GetAllChannels(p*pageSize, pageSize, false, idSort)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
tags := make(map[string]bool)
|
||||
channelData := make([]*model.Channel, 0, len(channels))
|
||||
tagChannels := make([]*model.Channel, 0)
|
||||
for _, channel := range channels {
|
||||
channelTag := channel.GetTag()
|
||||
if channelTag != "" && !tags[channelTag] {
|
||||
tags[channelTag] = true
|
||||
tagChannel, err := model.GetChannelsByTag(channelTag)
|
||||
if err == nil {
|
||||
tagChannels = append(tagChannels, tagChannel...)
|
||||
}
|
||||
} else {
|
||||
channelData = append(channelData, channel)
|
||||
enableTagMode, _ := strconv.ParseBool(c.Query("tag_mode"))
|
||||
if enableTagMode {
|
||||
tags, err := model.GetPaginatedTags(p*pageSize, pageSize)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
for i, channel := range tagChannels {
|
||||
find := false
|
||||
for _, can := range channelData {
|
||||
if channel.Id == can.Id {
|
||||
find = true
|
||||
break
|
||||
for _, tag := range tags {
|
||||
if tag != nil && *tag != "" {
|
||||
tagChannel, err := model.GetChannelsByTag(*tag)
|
||||
if err == nil {
|
||||
channelData = append(channelData, tagChannel...)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !find {
|
||||
channelData = append(channelData, tagChannels[i])
|
||||
} else {
|
||||
channels, err := model.GetAllChannels(p*pageSize, pageSize, false, idSort)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
channelData = channels
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
|
||||
@@ -14,8 +14,8 @@ services:
|
||||
environment:
|
||||
- SQL_DSN=root:123456@tcp(mysql:3306)/new-api # Point to the mysql service
|
||||
- REDIS_CONN_STRING=redis://redis
|
||||
- SESSION_SECRET=random_string # 修改为随机字符串
|
||||
- TZ=Asia/Shanghai
|
||||
# - SESSION_SECRET=random_string # 多机部署时设置,必须修改这个随机字符串!!!!!!!
|
||||
# - NODE_TYPE=slave # Uncomment for slave node in multi-node deployment
|
||||
# - SYNC_FREQUENCY=60 # Uncomment if regular database syncing is needed
|
||||
# - FRONTEND_BASE_URL=https://openai.justsong.cn # Uncomment for multi-node deployment with front-end URL
|
||||
@@ -43,8 +43,8 @@ services:
|
||||
MYSQL_DATABASE: new-api
|
||||
volumes:
|
||||
- mysql_data:/var/lib/mysql
|
||||
ports:
|
||||
- "3306:3306" # If you want to access MySQL from outside Docker, uncomment
|
||||
# ports:
|
||||
# - "3306:3306" # If you want to access MySQL from outside Docker, uncomment
|
||||
|
||||
volumes:
|
||||
mysql_data:
|
||||
|
||||
@@ -212,6 +212,7 @@ func TokenAuth() func(c *gin.Context) {
|
||||
}
|
||||
c.Set("id", token.UserId)
|
||||
c.Set("token_id", token.Id)
|
||||
c.Set("token_key", token.Key)
|
||||
c.Set("token_name", token.Name)
|
||||
c.Set("token_unlimited_quota", token.UnlimitedQuota)
|
||||
if !token.UnlimitedQuota {
|
||||
|
||||
@@ -2,9 +2,10 @@ package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"gorm.io/gorm"
|
||||
"one-api/common"
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Channel struct {
|
||||
@@ -403,3 +404,9 @@ func DeleteDisabledChannel() (int64, error) {
|
||||
result := DB.Where("status = ? or status = ?", common.ChannelStatusAutoDisabled, common.ChannelStatusManuallyDisabled).Delete(&Channel{})
|
||||
return result.RowsAffected, result.Error
|
||||
}
|
||||
|
||||
func GetPaginatedTags(offset int, limit int) ([]*string, error) {
|
||||
var tags []*string
|
||||
err := DB.Model(&Channel{}).Select("DISTINCT tag").Where("tag != ''").Offset(offset).Limit(limit).Find(&tags).Error
|
||||
return tags, err
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ var ModelList = []string{
|
||||
"360gpt-turbo",
|
||||
"360gpt-turbo-responsibility-8k",
|
||||
"360gpt-pro",
|
||||
"360gpt2-pro",
|
||||
"360GPT_S2_V9",
|
||||
"embedding-bert-512-v1",
|
||||
"embedding_s1_v1",
|
||||
|
||||
@@ -32,11 +32,15 @@ func (a *Adaptor) Init(info *relaycommon.RelayInfo) {
|
||||
|
||||
func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
|
||||
if info.RelayMode == constant.RelayModeRealtime {
|
||||
// trim https
|
||||
baseUrl := strings.TrimPrefix(info.BaseUrl, "https://")
|
||||
baseUrl = strings.TrimPrefix(baseUrl, "http://")
|
||||
baseUrl = "wss://" + baseUrl
|
||||
info.BaseUrl = baseUrl
|
||||
if strings.HasPrefix(info.BaseUrl, "https://") {
|
||||
baseUrl := strings.TrimPrefix(info.BaseUrl, "https://")
|
||||
baseUrl = "wss://" + baseUrl
|
||||
info.BaseUrl = baseUrl
|
||||
} else if strings.HasPrefix(info.BaseUrl, "http://") {
|
||||
baseUrl := strings.TrimPrefix(info.BaseUrl, "http://")
|
||||
baseUrl = "ws://" + baseUrl
|
||||
info.BaseUrl = baseUrl
|
||||
}
|
||||
}
|
||||
switch info.ChannelType {
|
||||
case common.ChannelTypeAzure:
|
||||
|
||||
@@ -14,6 +14,7 @@ type RelayInfo struct {
|
||||
ChannelType int
|
||||
ChannelId int
|
||||
TokenId int
|
||||
TokenKey string
|
||||
UserId int
|
||||
Group string
|
||||
TokenUnlimited bool
|
||||
@@ -58,6 +59,7 @@ func GenRelayInfo(c *gin.Context) *RelayInfo {
|
||||
channelId := c.GetInt("channel_id")
|
||||
|
||||
tokenId := c.GetInt("token_id")
|
||||
tokenKey := c.GetString("token_key")
|
||||
userId := c.GetInt("id")
|
||||
group := c.GetString("group")
|
||||
tokenUnlimited := c.GetBool("token_unlimited_quota")
|
||||
@@ -73,6 +75,7 @@ func GenRelayInfo(c *gin.Context) *RelayInfo {
|
||||
ChannelType: channelType,
|
||||
ChannelId: channelId,
|
||||
TokenId: tokenId,
|
||||
TokenKey: tokenKey,
|
||||
UserId: userId,
|
||||
Group: group,
|
||||
TokenUnlimited: tokenUnlimited,
|
||||
|
||||
@@ -2,11 +2,9 @@ package relay
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/bytedance/sonic"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
@@ -20,6 +18,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@@ -36,7 +36,7 @@ func getAndValidateTextRequest(c *gin.Context, relayInfo *relaycommon.RelayInfo)
|
||||
textRequest.Model = c.Param("model")
|
||||
}
|
||||
|
||||
if textRequest.MaxTokens < 0 || textRequest.MaxTokens > math.MaxInt32/2 {
|
||||
if textRequest.MaxTokens > math.MaxInt32/2 {
|
||||
return nil, errors.New("max_tokens is invalid")
|
||||
}
|
||||
if textRequest.Model == "" {
|
||||
@@ -48,12 +48,12 @@ func getAndValidateTextRequest(c *gin.Context, relayInfo *relaycommon.RelayInfo)
|
||||
return nil, errors.New("field prompt is required")
|
||||
}
|
||||
case relayconstant.RelayModeChatCompletions:
|
||||
if textRequest.Messages == nil || len(textRequest.Messages) == 0 {
|
||||
if len(textRequest.Messages) == 0 {
|
||||
return nil, errors.New("field messages is required")
|
||||
}
|
||||
case relayconstant.RelayModeEmbeddings:
|
||||
case relayconstant.RelayModeModerations:
|
||||
if textRequest.Input == "" || textRequest.Input == nil {
|
||||
if textRequest.Input == nil || textRequest.Input == "" {
|
||||
return nil, errors.New("field input is required")
|
||||
}
|
||||
case relayconstant.RelayModeEdits:
|
||||
@@ -264,7 +264,7 @@ func preConsumeQuota(c *gin.Context, preConsumedQuota int, relayInfo *relaycommo
|
||||
return 0, 0, service.OpenAIErrorWrapperLocal(errors.New("user quota is not enough"), "insufficient_user_quota", http.StatusForbidden)
|
||||
}
|
||||
if userQuota-preConsumedQuota < 0 {
|
||||
return 0, 0, service.OpenAIErrorWrapperLocal(errors.New(fmt.Sprintf("chat pre-consumed quota failed, user quota: %d, need quota: %d", userQuota, preConsumedQuota)), "insufficient_user_quota", http.StatusBadRequest)
|
||||
return 0, 0, service.OpenAIErrorWrapperLocal(fmt.Errorf("chat pre-consumed quota failed, user quota: %d, need quota: %d", userQuota, preConsumedQuota), "insufficient_user_quota", http.StatusBadRequest)
|
||||
}
|
||||
err = model.CacheDecreaseUserQuota(relayInfo.UserId, preConsumedQuota)
|
||||
if err != nil {
|
||||
@@ -298,13 +298,14 @@ func preConsumeQuota(c *gin.Context, preConsumedQuota int, relayInfo *relaycommo
|
||||
|
||||
func returnPreConsumedQuota(c *gin.Context, relayInfo *relaycommon.RelayInfo, userQuota int, preConsumedQuota int) {
|
||||
if preConsumedQuota != 0 {
|
||||
go func(ctx context.Context) {
|
||||
// return pre-consumed quota
|
||||
err := model.PostConsumeTokenQuota(relayInfo, userQuota, -preConsumedQuota, 0, false)
|
||||
go func() {
|
||||
relayInfoCopy := *relayInfo
|
||||
|
||||
err := model.PostConsumeTokenQuota(&relayInfoCopy, userQuota, -preConsumedQuota, 0, false)
|
||||
if err != nil {
|
||||
common.SysError("error return pre-consumed quota: " + err.Error())
|
||||
}
|
||||
}(c)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ func PreWssConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usag
|
||||
return err
|
||||
}
|
||||
|
||||
token, err := model.CacheGetTokenByKey(strings.TrimLeft(relayInfo.ApiKey, "sk-"))
|
||||
token, err := model.CacheGetTokenByKey(strings.TrimLeft(relayInfo.TokenKey, "sk-"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -53,7 +53,7 @@ func PreWssConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usag
|
||||
return errors.New(fmt.Sprintf("用户额度不足,剩余额度为 %d", userQuota))
|
||||
}
|
||||
|
||||
if token.RemainQuota < quota {
|
||||
if !token.UnlimitedQuota && token.RemainQuota < quota {
|
||||
return errors.New(fmt.Sprintf("令牌额度不足,剩余额度为 %d", token.RemainQuota))
|
||||
}
|
||||
|
||||
|
||||
@@ -317,7 +317,7 @@ const ChannelsTable = () => {
|
||||
position={'left'}
|
||||
onConfirm={() => {
|
||||
manageChannel(record.id, 'delete', record).then(() => {
|
||||
removeRecord(record.id);
|
||||
removeRecord(record);
|
||||
});
|
||||
}}
|
||||
>
|
||||
@@ -365,7 +365,7 @@ const ChannelsTable = () => {
|
||||
okType={'danger'}
|
||||
position={'left'}
|
||||
onConfirm={async () => {
|
||||
copySelectedChannel(record.id);
|
||||
copySelectedChannel(record);
|
||||
}}
|
||||
>
|
||||
<Button theme="light" type="primary" style={{ marginRight: 1 }}>
|
||||
@@ -439,12 +439,24 @@ const ChannelsTable = () => {
|
||||
const [editingTag, setEditingTag] = useState('');
|
||||
const [selectedChannels, setSelectedChannels] = useState([]);
|
||||
const [showEditPriority, setShowEditPriority] = useState(false);
|
||||
const [enableTagMode, setEnableTagMode] = useState(false);
|
||||
|
||||
|
||||
const removeRecord = (id) => {
|
||||
const removeRecord = (record) => {
|
||||
let newDataSource = [...channels];
|
||||
if (id != null) {
|
||||
let idx = newDataSource.findIndex((data) => data.id === id);
|
||||
if (record.id != null) {
|
||||
let idx = newDataSource.findIndex((data) => {
|
||||
if (data.children !== undefined) {
|
||||
for (let i = 0; i < data.children.length; i++) {
|
||||
if (data.children[i].id === record.id) {
|
||||
data.children.splice(i, 1);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return data.id === record.id
|
||||
}
|
||||
});
|
||||
|
||||
if (idx > -1) {
|
||||
newDataSource.splice(idx, 1);
|
||||
@@ -453,13 +465,12 @@ const ChannelsTable = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const setChannelFormat = (channels) => {
|
||||
const setChannelFormat = (channels, enableTagMode) => {
|
||||
let channelDates = [];
|
||||
let channelTags = {};
|
||||
for (let i = 0; i < channels.length; i++) {
|
||||
channels[i].key = '' + channels[i].id;
|
||||
|
||||
if (channels[i].tag === '' || channels[i].tag === null) {
|
||||
if (!enableTagMode) {
|
||||
let test_models = [];
|
||||
channels[i].models.split(',').forEach((item, index) => {
|
||||
test_models.push({
|
||||
@@ -543,10 +554,10 @@ const ChannelsTable = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const loadChannels = async (startIdx, pageSize, idSort) => {
|
||||
const loadChannels = async (startIdx, pageSize, idSort, enableTagMode) => {
|
||||
setLoading(true);
|
||||
const res = await API.get(
|
||||
`/api/channel/?p=${startIdx}&page_size=${pageSize}&id_sort=${idSort}`
|
||||
`/api/channel/?p=${startIdx}&page_size=${pageSize}&id_sort=${idSort}&tag_mode=${enableTagMode}`
|
||||
);
|
||||
if (res === undefined) {
|
||||
return;
|
||||
@@ -554,11 +565,11 @@ const ChannelsTable = () => {
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
if (startIdx === 0) {
|
||||
setChannelFormat(data);
|
||||
setChannelFormat(data, enableTagMode);
|
||||
} else {
|
||||
let newChannels = [...channels];
|
||||
newChannels.splice(startIdx * pageSize, data.length, ...data);
|
||||
setChannelFormat(newChannels);
|
||||
setChannelFormat(newChannels, enableTagMode);
|
||||
}
|
||||
} else {
|
||||
showError(message);
|
||||
@@ -566,11 +577,8 @@ const ChannelsTable = () => {
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const copySelectedChannel = async (id) => {
|
||||
const channelToCopy = channels.find(
|
||||
(channel) => String(channel.id) === String(id)
|
||||
);
|
||||
console.log(channelToCopy);
|
||||
const copySelectedChannel = async (record) => {
|
||||
const channelToCopy = record
|
||||
channelToCopy.name += '_复制';
|
||||
channelToCopy.created_time = null;
|
||||
channelToCopy.balance = 0;
|
||||
@@ -594,7 +602,7 @@ const ChannelsTable = () => {
|
||||
};
|
||||
|
||||
const refresh = async () => {
|
||||
await loadChannels(activePage - 1, pageSize, idSort);
|
||||
await loadChannels(activePage - 1, pageSize, idSort, enableTagMode);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -604,7 +612,7 @@ const ChannelsTable = () => {
|
||||
parseInt(localStorage.getItem('page-size')) || ITEMS_PER_PAGE;
|
||||
setIdSort(localIdSort);
|
||||
setPageSize(localPageSize);
|
||||
loadChannels(0, localPageSize, localIdSort)
|
||||
loadChannels(0, localPageSize, localIdSort, enableTagMode)
|
||||
.then()
|
||||
.catch((reason) => {
|
||||
showError(reason);
|
||||
@@ -762,18 +770,22 @@ const ChannelsTable = () => {
|
||||
|
||||
const searchChannels = async (searchKeyword, searchGroup, searchModel) => {
|
||||
if (searchKeyword === '' && searchGroup === '' && searchModel === '') {
|
||||
// if keyword is blank, load files instead.
|
||||
await loadChannels(0, pageSize, idSort);
|
||||
await loadChannels(0, pageSize, idSort, enableTagMode);
|
||||
setActivePage(1);
|
||||
return;
|
||||
}
|
||||
setSearching(true);
|
||||
const res = await API.get(
|
||||
`/api/channel/search?keyword=${searchKeyword}&group=${searchGroup}&model=${searchModel}&id_sort=${idSort}`
|
||||
`/api/channel/search?keyword=${searchKeyword}&group=${searchGroup}&model=${searchModel}&id_sort=${idSort}&tag_mode=${enableTagMode}`
|
||||
);
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
setChannelFormat(data);
|
||||
if (enableTagMode) {
|
||||
setChannelFormat(data, enableTagMode);
|
||||
} else {
|
||||
setChannels(data.map(channel => ({...channel, key: '' + channel.id})));
|
||||
setChannelCount(data.length);
|
||||
}
|
||||
setActivePage(1);
|
||||
} else {
|
||||
showError(message);
|
||||
@@ -879,7 +891,7 @@ const ChannelsTable = () => {
|
||||
setActivePage(page);
|
||||
if (page === Math.ceil(channels.length / pageSize) + 1) {
|
||||
// In this case we have to load more data and then append them.
|
||||
loadChannels(page - 1, pageSize, idSort).then((r) => {
|
||||
loadChannels(page - 1, pageSize, idSort, enableTagMode).then((r) => {
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -888,7 +900,7 @@ const ChannelsTable = () => {
|
||||
localStorage.setItem('page-size', size + '');
|
||||
setPageSize(size);
|
||||
setActivePage(1);
|
||||
loadChannels(0, size, idSort)
|
||||
loadChannels(0, size, idSort, enableTagMode)
|
||||
.then()
|
||||
.catch((reason) => {
|
||||
showError(reason);
|
||||
@@ -1044,7 +1056,7 @@ const ChannelsTable = () => {
|
||||
onChange={(v) => {
|
||||
localStorage.setItem('id-sort', v + '');
|
||||
setIdSort(v);
|
||||
loadChannels(0, pageSize, v)
|
||||
loadChannels(0, pageSize, v, enableTagMode)
|
||||
.then()
|
||||
.catch((reason) => {
|
||||
showError(reason);
|
||||
@@ -1145,6 +1157,22 @@ const ChannelsTable = () => {
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
</div>
|
||||
<div style={{ marginTop: 20 }}>
|
||||
<Space>
|
||||
<Typography.Text strong>标签聚合模式</Typography.Text>
|
||||
<Switch
|
||||
checked={enableTagMode}
|
||||
label="标签聚合模式"
|
||||
uncheckedText="关"
|
||||
aria-label="是否启用标签聚合"
|
||||
onChange={(v) => {
|
||||
setEnableTagMode(v);
|
||||
// 切换模式时重新加载数据
|
||||
loadChannels(0, pageSize, idSort, v);
|
||||
}}
|
||||
/>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
|
||||
<Table
|
||||
|
||||
@@ -6,7 +6,8 @@ export let API = axios.create({
|
||||
? import.meta.env.VITE_REACT_APP_SERVER_URL
|
||||
: '',
|
||||
headers: {
|
||||
'New-API-User': getUserIdFromLocalStorage()
|
||||
'New-API-User': getUserIdFromLocalStorage(),
|
||||
'Cache-Control': 'no-store'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -16,7 +17,8 @@ export function updateAPI() {
|
||||
? import.meta.env.VITE_REACT_APP_SERVER_URL
|
||||
: '',
|
||||
headers: {
|
||||
'New-API-User': getUserIdFromLocalStorage()
|
||||
'New-API-User': getUserIdFromLocalStorage(),
|
||||
'Cache-Control': 'no-store'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -136,9 +136,9 @@ const EditTagModal = (props) => {
|
||||
if (inputs.models.length > 0) {
|
||||
data.models = inputs.models.join(',');
|
||||
}
|
||||
data.newTag = inputs.newTag;
|
||||
data.new_tag = inputs.new_tag;
|
||||
// check have any change
|
||||
if (data.model_mapping === undefined && data.groups === undefined && data.models === undefined && data.newTag === undefined) {
|
||||
if (data.model_mapping === undefined && data.groups === undefined && data.models === undefined && data.new_tag === undefined) {
|
||||
showWarning('没有任何修改!');
|
||||
setLoading(false);
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user