mirror of
https://github.com/songquanpeng/one-api.git
synced 2025-10-23 01:43:42 +08:00
Compare commits
7 Commits
v0.2.6-alp
...
v0.2.6-alp
Author | SHA1 | Date | |
---|---|---|---|
|
cc3072c4df | ||
|
bffee4e91d | ||
|
79dc53ff0d | ||
|
68e53d3e10 | ||
|
d267211ee7 | ||
|
570b3bc71c | ||
|
225176aae9 |
@@ -11,6 +11,7 @@ import (
|
||||
"one-api/model"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -19,7 +20,7 @@ func GetAllChannels(c *gin.Context) {
|
||||
if p < 0 {
|
||||
p = 0
|
||||
}
|
||||
channels, err := model.GetAllChannels(p*common.ItemsPerPage, common.ItemsPerPage)
|
||||
channels, err := model.GetAllChannels(p*common.ItemsPerPage, common.ItemsPerPage, false)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
@@ -89,7 +90,6 @@ func AddChannel(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
channel.CreatedTime = common.GetTimestamp()
|
||||
channel.AccessedTime = common.GetTimestamp()
|
||||
keys := strings.Split(channel.Key, "\n")
|
||||
channels := make([]model.Channel, 0)
|
||||
for _, key := range keys {
|
||||
@@ -207,6 +207,19 @@ func testChannel(channel *model.Channel, request *ChatRequest) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildTestRequest(c *gin.Context) *ChatRequest {
|
||||
model_ := c.Query("model")
|
||||
testRequest := &ChatRequest{
|
||||
Model: model_,
|
||||
}
|
||||
testMessage := Message{
|
||||
Role: "user",
|
||||
Content: "echo hi",
|
||||
}
|
||||
testRequest.Messages = append(testRequest.Messages, testMessage)
|
||||
return testRequest
|
||||
}
|
||||
|
||||
func TestChannel(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
@@ -224,19 +237,13 @@ func TestChannel(c *gin.Context) {
|
||||
})
|
||||
return
|
||||
}
|
||||
model_ := c.Query("model")
|
||||
chatRequest := &ChatRequest{
|
||||
Model: model_,
|
||||
}
|
||||
testMessage := Message{
|
||||
Role: "user",
|
||||
Content: "echo hi",
|
||||
}
|
||||
chatRequest.Messages = append(chatRequest.Messages, testMessage)
|
||||
testRequest := buildTestRequest(c)
|
||||
tik := time.Now()
|
||||
err = testChannel(channel, chatRequest)
|
||||
err = testChannel(channel, testRequest)
|
||||
tok := time.Now()
|
||||
consumedTime := float64(tok.Sub(tik).Milliseconds()) / 1000.0
|
||||
milliseconds := tok.Sub(tik).Milliseconds()
|
||||
go channel.UpdateResponseTime(milliseconds)
|
||||
consumedTime := float64(milliseconds) / 1000.0
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
@@ -252,3 +259,70 @@ func TestChannel(c *gin.Context) {
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var testAllChannelsLock sync.Mutex
|
||||
|
||||
func testAllChannels(c *gin.Context) error {
|
||||
ok := testAllChannelsLock.TryLock()
|
||||
if !ok {
|
||||
return errors.New("测试已在运行")
|
||||
}
|
||||
defer testAllChannelsLock.Unlock()
|
||||
channels, err := model.GetAllChannels(0, 0, true)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
testRequest := buildTestRequest(c)
|
||||
var disableThreshold int64 = 5000 // TODO: make it configurable
|
||||
email := model.GetRootUserEmail()
|
||||
go func() {
|
||||
for _, channel := range channels {
|
||||
if channel.Status != common.ChannelStatusEnabled {
|
||||
continue
|
||||
}
|
||||
tik := time.Now()
|
||||
err := testChannel(channel, testRequest)
|
||||
tok := time.Now()
|
||||
milliseconds := tok.Sub(tik).Milliseconds()
|
||||
if err != nil || milliseconds > disableThreshold {
|
||||
if milliseconds > disableThreshold {
|
||||
err = errors.New(fmt.Sprintf("响应时间 %.2fs 超过阈值 %.2fs", float64(milliseconds)/1000.0, float64(disableThreshold)/1000.0))
|
||||
}
|
||||
// disable & notify
|
||||
channel.UpdateStatus(common.ChannelStatusDisabled)
|
||||
subject := fmt.Sprintf("通道「%s」(#%d)已被禁用", channel.Name, channel.Id)
|
||||
content := fmt.Sprintf("通道「%s」(#%d)已被禁用,原因:%s", channel.Name, channel.Id, err.Error())
|
||||
err = common.SendEmail(subject, email, content)
|
||||
if err != nil {
|
||||
common.SysError(fmt.Sprintf("发送邮件失败:%s", err.Error()))
|
||||
}
|
||||
}
|
||||
channel.UpdateResponseTime(milliseconds)
|
||||
}
|
||||
err := common.SendEmail("通道测试完成", email, "通道测试完成")
|
||||
if err != nil {
|
||||
common.SysError(fmt.Sprintf("发送邮件失败:%s", err.Error()))
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestAllChannels(c *gin.Context) {
|
||||
err := testAllChannels(c)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
@@ -117,6 +117,9 @@ func relayHelper(c *gin.Context) error {
|
||||
task := strings.TrimPrefix(requestURL, "/v1/")
|
||||
model_ := textRequest.Model
|
||||
model_ = strings.Replace(model_, ".", "", -1)
|
||||
// https://github.com/songquanpeng/one-api/issues/67
|
||||
model_ = strings.TrimSuffix(model_, "-0301")
|
||||
model_ = strings.TrimSuffix(model_, "-0314")
|
||||
fullRequestURL = fmt.Sprintf("%s/openai/deployments/%s/%s", baseURL, model_, task)
|
||||
}
|
||||
req, err := http.NewRequest(c.Request.Method, fullRequestURL, c.Request.Body)
|
||||
|
@@ -112,7 +112,7 @@ func TokenAuth() func(c *gin.Context) {
|
||||
c.Set("token_id", token.Id)
|
||||
requestURL := c.Request.URL.String()
|
||||
consumeQuota := !token.UnlimitedQuota
|
||||
if strings.HasPrefix(requestURL, "/models") {
|
||||
if strings.HasPrefix(requestURL, "/v1/models") {
|
||||
consumeQuota = false
|
||||
}
|
||||
c.Set("consume_quota", consumeQuota)
|
||||
|
@@ -13,15 +13,20 @@ type Channel struct {
|
||||
Name string `json:"name" gorm:"index"`
|
||||
Weight int `json:"weight"`
|
||||
CreatedTime int64 `json:"created_time" gorm:"bigint"`
|
||||
AccessedTime int64 `json:"accessed_time" gorm:"bigint"`
|
||||
TestTime int64 `json:"test_time" gorm:"bigint"`
|
||||
ResponseTime int `json:"response_time"` // in milliseconds
|
||||
BaseURL string `json:"base_url" gorm:"column:base_url"`
|
||||
Other string `json:"other"`
|
||||
}
|
||||
|
||||
func GetAllChannels(startIdx int, num int) ([]*Channel, error) {
|
||||
func GetAllChannels(startIdx int, num int, selectAll bool) ([]*Channel, error) {
|
||||
var channels []*Channel
|
||||
var err error
|
||||
err = DB.Order("id desc").Limit(num).Offset(startIdx).Omit("key").Find(&channels).Error
|
||||
if selectAll {
|
||||
err = DB.Order("id desc").Find(&channels).Error
|
||||
} else {
|
||||
err = DB.Order("id desc").Limit(num).Offset(startIdx).Omit("key").Find(&channels).Error
|
||||
}
|
||||
return channels, err
|
||||
}
|
||||
|
||||
@@ -71,6 +76,23 @@ func (channel *Channel) Update() error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (channel *Channel) UpdateResponseTime(responseTime int64) {
|
||||
err := DB.Model(channel).Select("response_time", "test_time").Updates(Channel{
|
||||
TestTime: common.GetTimestamp(),
|
||||
ResponseTime: int(responseTime),
|
||||
}).Error
|
||||
if err != nil {
|
||||
common.SysError("failed to update response time: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (channel *Channel) UpdateStatus(status int) {
|
||||
err := DB.Model(channel).Update("status", status).Error
|
||||
if err != nil {
|
||||
common.SysError("failed to update response time: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (channel *Channel) Delete() error {
|
||||
var err error
|
||||
err = DB.Delete(channel).Error
|
||||
|
@@ -234,3 +234,8 @@ func DecreaseUserQuota(id int, quota int) (err error) {
|
||||
err = DB.Model(&User{}).Where("id = ?", id).Update("quota", gorm.Expr("quota - ?", quota)).Error
|
||||
return err
|
||||
}
|
||||
|
||||
func GetRootUserEmail() (email string) {
|
||||
DB.Model(&User{}).Where("role = ?", common.RoleRootUser).Select("email").Find(&email)
|
||||
return email
|
||||
}
|
||||
|
@@ -63,6 +63,7 @@ func SetApiRouter(router *gin.Engine) {
|
||||
channelRoute.GET("/", controller.GetAllChannels)
|
||||
channelRoute.GET("/search", controller.SearchChannels)
|
||||
channelRoute.GET("/:id", controller.GetChannel)
|
||||
channelRoute.GET("/test", controller.TestAllChannels)
|
||||
channelRoute.GET("/test/:id", controller.TestChannel)
|
||||
channelRoute.POST("/", controller.AddChannel)
|
||||
channelRoute.PUT("/", controller.UpdateChannel)
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Form, Label, Pagination, Popup, Table } from 'semantic-ui-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { API, copy, showError, showInfo, showSuccess, timestamp2string } from '../helpers';
|
||||
import { API, showError, showInfo, showSuccess, timestamp2string } from '../helpers';
|
||||
|
||||
import { CHANNEL_OPTIONS, ITEMS_PER_PAGE } from '../constants';
|
||||
|
||||
@@ -120,6 +120,22 @@ const ChannelsTable = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const renderResponseTime = (responseTime) => {
|
||||
let time = responseTime / 1000;
|
||||
time = time.toFixed(2) + " 秒";
|
||||
if (responseTime === 0) {
|
||||
return <Label basic color='grey'>未测试</Label>;
|
||||
} else if (responseTime <= 1000) {
|
||||
return <Label basic color='green'>{time}</Label>;
|
||||
} else if (responseTime <= 3000) {
|
||||
return <Label basic color='olive'>{time}</Label>;
|
||||
} else if (responseTime <= 5000) {
|
||||
return <Label basic color='yellow'>{time}</Label>;
|
||||
} else {
|
||||
return <Label basic color='red'>{time}</Label>;
|
||||
}
|
||||
};
|
||||
|
||||
const searchChannels = async () => {
|
||||
if (searchKeyword === '') {
|
||||
// if keyword is blank, load files instead.
|
||||
@@ -139,11 +155,26 @@ const ChannelsTable = () => {
|
||||
setSearching(false);
|
||||
};
|
||||
|
||||
const testChannel = async (id, name) => {
|
||||
const testChannel = async (id, name, idx) => {
|
||||
const res = await API.get(`/api/channel/test/${id}/`);
|
||||
const { success, message, time } = res.data;
|
||||
if (success) {
|
||||
showInfo(`通道 ${name} 测试成功,耗时 ${time} 秒。`);
|
||||
let newChannels = [...channels];
|
||||
let realIdx = (activePage - 1) * ITEMS_PER_PAGE + idx;
|
||||
newChannels[realIdx].response_time = time * 1000;
|
||||
newChannels[realIdx].test_time = Date.now() / 1000;
|
||||
setChannels(newChannels);
|
||||
showInfo(`通道 ${name} 测试成功,耗时 ${time.toFixed(2)} 秒。`);
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
};
|
||||
|
||||
const testAllChannels = async () => {
|
||||
const res = await API.get(`/api/channel/test`);
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
showSuccess("已成功开始测试所有已启用通道,请刷新页面查看结果。");
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
@@ -219,18 +250,18 @@ const ChannelsTable = () => {
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
sortChannel('created_time');
|
||||
sortChannel('response_time');
|
||||
}}
|
||||
>
|
||||
创建时间
|
||||
响应时间
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
sortChannel('accessed_time');
|
||||
sortChannel('test_time');
|
||||
}}
|
||||
>
|
||||
访问时间
|
||||
测试时间
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell>操作</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
@@ -250,15 +281,15 @@ const ChannelsTable = () => {
|
||||
<Table.Cell>{channel.name ? channel.name : '无'}</Table.Cell>
|
||||
<Table.Cell>{renderType(channel.type)}</Table.Cell>
|
||||
<Table.Cell>{renderStatus(channel.status)}</Table.Cell>
|
||||
<Table.Cell>{renderTimestamp(channel.created_time)}</Table.Cell>
|
||||
<Table.Cell>{renderTimestamp(channel.accessed_time)}</Table.Cell>
|
||||
<Table.Cell>{renderResponseTime(channel.response_time)}</Table.Cell>
|
||||
<Table.Cell>{channel.test_time ? renderTimestamp(channel.test_time) : "未测试"}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<div>
|
||||
<Button
|
||||
size={'small'}
|
||||
positive
|
||||
onClick={() => {
|
||||
testChannel(channel.id, channel.name);
|
||||
testChannel(channel.id, channel.name, idx);
|
||||
}}
|
||||
>
|
||||
测试
|
||||
@@ -314,6 +345,9 @@ const ChannelsTable = () => {
|
||||
<Button size='small' as={Link} to='/channel/add' loading={loading}>
|
||||
添加新的渠道
|
||||
</Button>
|
||||
<Button size='small' loading={loading} onClick={testAllChannels}>
|
||||
测试所有已启用通道
|
||||
</Button>
|
||||
<Pagination
|
||||
floated='right'
|
||||
activePage={activePage}
|
||||
|
Reference in New Issue
Block a user