mirror of
https://github.com/songquanpeng/one-api.git
synced 2025-09-17 01:06:37 +08:00
feat: able to query test log
This commit is contained in:
parent
4f68f3e1b3
commit
fa2a772731
@ -410,6 +410,7 @@ graph LR
|
||||
27. `INITIAL_ROOT_TOKEN`:如果设置了该值,则在系统首次启动时会自动创建一个值为该环境变量值的 root 用户令牌。
|
||||
28. `INITIAL_ROOT_ACCESS_TOKEN`:如果设置了该值,则在系统首次启动时会自动创建一个值为该环境变量的 root 用户创建系统管理令牌。
|
||||
29. `ENFORCE_INCLUDE_USAGE`:是否强制在 stream 模型下返回 usage,默认不开启,可选值为 `true` 和 `false`。
|
||||
30. `TEST_PROMPT`:测试模型时的用户 prompt,默认为 `Print your model name exactly and do not output without any other text.`。
|
||||
|
||||
### 命令行参数
|
||||
1. `--port <port_number>`: 指定服务器监听的端口号,默认为 `3000`。
|
||||
|
@ -1,13 +1,14 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/songquanpeng/one-api/common/env"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/songquanpeng/one-api/common/env"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
@ -162,3 +163,4 @@ var UserContentRequestProxy = env.String("USER_CONTENT_REQUEST_PROXY", "")
|
||||
var UserContentRequestTimeout = env.Int("USER_CONTENT_REQUEST_TIMEOUT", 30)
|
||||
|
||||
var EnforceIncludeUsage = env.Bool("ENFORCE_INCLUDE_USAGE", false)
|
||||
var TestPrompt = env.String("TEST_PROMPT", "Print your model name exactly and do not output without any other text.")
|
||||
|
@ -2,6 +2,7 @@ package controller
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@ -15,14 +16,17 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/songquanpeng/one-api/common/config"
|
||||
"github.com/songquanpeng/one-api/common/ctxkey"
|
||||
"github.com/songquanpeng/one-api/common/helper"
|
||||
"github.com/songquanpeng/one-api/common/logger"
|
||||
"github.com/songquanpeng/one-api/common/message"
|
||||
"github.com/songquanpeng/one-api/middleware"
|
||||
"github.com/songquanpeng/one-api/model"
|
||||
"github.com/songquanpeng/one-api/monitor"
|
||||
relay "github.com/songquanpeng/one-api/relay"
|
||||
"github.com/songquanpeng/one-api/relay"
|
||||
"github.com/songquanpeng/one-api/relay/adaptor/openai"
|
||||
"github.com/songquanpeng/one-api/relay/channeltype"
|
||||
"github.com/songquanpeng/one-api/relay/controller"
|
||||
"github.com/songquanpeng/one-api/relay/meta"
|
||||
@ -35,18 +39,34 @@ func buildTestRequest(model string) *relaymodel.GeneralOpenAIRequest {
|
||||
model = "gpt-3.5-turbo"
|
||||
}
|
||||
testRequest := &relaymodel.GeneralOpenAIRequest{
|
||||
MaxTokens: 2,
|
||||
Model: model,
|
||||
}
|
||||
testMessage := relaymodel.Message{
|
||||
Role: "user",
|
||||
Content: "hi",
|
||||
Content: config.TestPrompt,
|
||||
}
|
||||
testRequest.Messages = append(testRequest.Messages, testMessage)
|
||||
return testRequest
|
||||
}
|
||||
|
||||
func testChannel(channel *model.Channel, request *relaymodel.GeneralOpenAIRequest) (err error, openaiErr *relaymodel.Error) {
|
||||
func parseTestResponse(resp string) (*openai.TextResponse, string, error) {
|
||||
var response openai.TextResponse
|
||||
err := json.Unmarshal([]byte(resp), &response)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if len(response.Choices) == 0 {
|
||||
return nil, "", errors.New("response has no choices")
|
||||
}
|
||||
stringContent, ok := response.Choices[0].Content.(string)
|
||||
if !ok {
|
||||
return nil, "", errors.New("response content is not string")
|
||||
}
|
||||
return &response, stringContent, nil
|
||||
}
|
||||
|
||||
func testChannel(ctx context.Context, channel *model.Channel, request *relaymodel.GeneralOpenAIRequest) (responseMessage string, err error, openaiErr *relaymodel.Error) {
|
||||
startTime := time.Now()
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Request = &http.Request{
|
||||
@ -66,7 +86,7 @@ func testChannel(channel *model.Channel, request *relaymodel.GeneralOpenAIReques
|
||||
apiType := channeltype.ToAPIType(channel.Type)
|
||||
adaptor := relay.GetAdaptor(apiType)
|
||||
if adaptor == nil {
|
||||
return fmt.Errorf("invalid api type: %d, adaptor is nil", apiType), nil
|
||||
return "", fmt.Errorf("invalid api type: %d, adaptor is nil", apiType), nil
|
||||
}
|
||||
adaptor.Init(meta)
|
||||
modelName := request.Model
|
||||
@ -84,41 +104,69 @@ func testChannel(channel *model.Channel, request *relaymodel.GeneralOpenAIReques
|
||||
request.Model = modelName
|
||||
convertedRequest, err := adaptor.ConvertRequest(c, relaymode.ChatCompletions, request)
|
||||
if err != nil {
|
||||
return err, nil
|
||||
return "", err, nil
|
||||
}
|
||||
jsonData, err := json.Marshal(convertedRequest)
|
||||
if err != nil {
|
||||
return err, nil
|
||||
return "", err, nil
|
||||
}
|
||||
defer func() {
|
||||
logContent := fmt.Sprintf("渠道 %s 测试成功,响应:%s", channel.Name, responseMessage)
|
||||
if err != nil || openaiErr != nil {
|
||||
errorMessage := ""
|
||||
if err != nil {
|
||||
errorMessage = err.Error()
|
||||
} else {
|
||||
errorMessage = openaiErr.Message
|
||||
}
|
||||
logContent = fmt.Sprintf("渠道 %s 测试失败,错误:%s", channel.Name, errorMessage)
|
||||
}
|
||||
go model.RecordTestLog(ctx, &model.Log{
|
||||
ChannelId: channel.Id,
|
||||
ModelName: modelName,
|
||||
Content: logContent,
|
||||
ElapsedTime: helper.CalcElapsedTime(startTime),
|
||||
})
|
||||
}()
|
||||
logger.SysLog(string(jsonData))
|
||||
requestBody := bytes.NewBuffer(jsonData)
|
||||
c.Request.Body = io.NopCloser(requestBody)
|
||||
resp, err := adaptor.DoRequest(c, meta, requestBody)
|
||||
if err != nil {
|
||||
return err, nil
|
||||
return "", err, nil
|
||||
}
|
||||
if resp != nil && resp.StatusCode != http.StatusOK {
|
||||
err := controller.RelayErrorHandler(resp)
|
||||
return fmt.Errorf("status code %d: %s", resp.StatusCode, err.Error.Message), &err.Error
|
||||
errorMessage := err.Error.Message
|
||||
if errorMessage != "" {
|
||||
errorMessage = ", error message: " + errorMessage
|
||||
}
|
||||
return "", fmt.Errorf("http status code: %d%s", resp.StatusCode, errorMessage), &err.Error
|
||||
}
|
||||
usage, respErr := adaptor.DoResponse(c, resp, meta)
|
||||
if respErr != nil {
|
||||
return fmt.Errorf("%s", respErr.Error.Message), &respErr.Error
|
||||
return "", fmt.Errorf("%s", respErr.Error.Message), &respErr.Error
|
||||
}
|
||||
if usage == nil {
|
||||
return errors.New("usage is nil"), nil
|
||||
return "", errors.New("usage is nil"), nil
|
||||
}
|
||||
rawResponse := w.Body.String()
|
||||
_, responseMessage, err = parseTestResponse(rawResponse)
|
||||
if err != nil {
|
||||
return "", err, nil
|
||||
}
|
||||
result := w.Result()
|
||||
// print result.Body
|
||||
respBody, err := io.ReadAll(result.Body)
|
||||
if err != nil {
|
||||
return err, nil
|
||||
return "", err, nil
|
||||
}
|
||||
logger.SysLog(fmt.Sprintf("testing channel #%d, response: \n%s", channel.Id, string(respBody)))
|
||||
return nil, nil
|
||||
return responseMessage, nil, nil
|
||||
}
|
||||
|
||||
func TestChannel(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
@ -135,10 +183,10 @@ func TestChannel(c *gin.Context) {
|
||||
})
|
||||
return
|
||||
}
|
||||
model := c.Query("model")
|
||||
testRequest := buildTestRequest(model)
|
||||
modelName := c.Query("model")
|
||||
testRequest := buildTestRequest(modelName)
|
||||
tik := time.Now()
|
||||
err, _ = testChannel(channel, testRequest)
|
||||
responseMessage, err, _ := testChannel(ctx, channel, testRequest)
|
||||
tok := time.Now()
|
||||
milliseconds := tok.Sub(tik).Milliseconds()
|
||||
if err != nil {
|
||||
@ -151,15 +199,15 @@ func TestChannel(c *gin.Context) {
|
||||
"success": false,
|
||||
"message": err.Error(),
|
||||
"time": consumedTime,
|
||||
"model": model,
|
||||
"modelName": modelName,
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "",
|
||||
"message": responseMessage,
|
||||
"time": consumedTime,
|
||||
"model": model,
|
||||
"modelName": modelName,
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -167,7 +215,7 @@ func TestChannel(c *gin.Context) {
|
||||
var testAllChannelsLock sync.Mutex
|
||||
var testAllChannelsRunning bool = false
|
||||
|
||||
func testChannels(notify bool, scope string) error {
|
||||
func testChannels(ctx context.Context, notify bool, scope string) error {
|
||||
if config.RootUserEmail == "" {
|
||||
config.RootUserEmail = model.GetRootUserEmail()
|
||||
}
|
||||
@ -191,7 +239,7 @@ func testChannels(notify bool, scope string) error {
|
||||
isChannelEnabled := channel.Status == model.ChannelStatusEnabled
|
||||
tik := time.Now()
|
||||
testRequest := buildTestRequest("")
|
||||
err, openaiErr := testChannel(channel, testRequest)
|
||||
_, err, openaiErr := testChannel(ctx, channel, testRequest)
|
||||
tok := time.Now()
|
||||
milliseconds := tok.Sub(tik).Milliseconds()
|
||||
if isChannelEnabled && milliseconds > disableThreshold {
|
||||
@ -225,11 +273,12 @@ func testChannels(notify bool, scope string) error {
|
||||
}
|
||||
|
||||
func TestChannels(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
scope := c.Query("scope")
|
||||
if scope == "" {
|
||||
scope = "all"
|
||||
}
|
||||
err := testChannels(true, scope)
|
||||
err := testChannels(ctx, true, scope)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
@ -245,10 +294,11 @@ func TestChannels(c *gin.Context) {
|
||||
}
|
||||
|
||||
func AutomaticallyTestChannels(frequency int) {
|
||||
ctx := context.Background()
|
||||
for {
|
||||
time.Sleep(time.Duration(frequency) * time.Minute)
|
||||
logger.SysLog("testing all channels")
|
||||
_ = testChannels(false, "all")
|
||||
_ = testChannels(ctx, false, "all")
|
||||
logger.SysLog("channel test finished")
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ const (
|
||||
LogTypeConsume
|
||||
LogTypeManage
|
||||
LogTypeSystem
|
||||
LogTypeTest
|
||||
)
|
||||
|
||||
func recordLogHelper(ctx context.Context, log *Log) {
|
||||
@ -86,6 +87,12 @@ func RecordConsumeLog(ctx context.Context, log *Log) {
|
||||
recordLogHelper(ctx, log)
|
||||
}
|
||||
|
||||
func RecordTestLog(ctx context.Context, log *Log) {
|
||||
log.CreatedAt = helper.GetTimestamp()
|
||||
log.Type = LogTypeTest
|
||||
recordLogHelper(ctx, log)
|
||||
}
|
||||
|
||||
func GetAllLogs(logType int, startTimestamp int64, endTimestamp int64, modelName string, username string, tokenName string, startIdx int, num int, channel int) (logs []*Log, err error) {
|
||||
var tx *gorm.DB
|
||||
if logType == LogTypeUnknown {
|
||||
|
@ -1,5 +1,15 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Dropdown, Form, Input, Label, Message, Pagination, Popup, Table } from 'semantic-ui-react';
|
||||
import {
|
||||
Button,
|
||||
Dropdown,
|
||||
Form,
|
||||
Input,
|
||||
Label,
|
||||
Message,
|
||||
Pagination,
|
||||
Popup,
|
||||
Table,
|
||||
} from 'semantic-ui-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {
|
||||
API,
|
||||
@ -9,31 +19,31 @@ import {
|
||||
showError,
|
||||
showInfo,
|
||||
showSuccess,
|
||||
timestamp2string
|
||||
timestamp2string,
|
||||
} from '../helpers';
|
||||
|
||||
import { CHANNEL_OPTIONS, ITEMS_PER_PAGE } from '../constants';
|
||||
import { renderGroup, renderNumber } from '../helpers/render';
|
||||
|
||||
function renderTimestamp(timestamp) {
|
||||
return (
|
||||
<>
|
||||
{timestamp2string(timestamp)}
|
||||
</>
|
||||
);
|
||||
return <>{timestamp2string(timestamp)}</>;
|
||||
}
|
||||
|
||||
let type2label = undefined;
|
||||
|
||||
function renderType(type) {
|
||||
if (!type2label) {
|
||||
type2label = new Map;
|
||||
type2label = new Map();
|
||||
for (let i = 0; i < CHANNEL_OPTIONS.length; i++) {
|
||||
type2label[CHANNEL_OPTIONS[i].value] = CHANNEL_OPTIONS[i];
|
||||
}
|
||||
type2label[0] = { value: 0, text: '未知类型', color: 'grey' };
|
||||
}
|
||||
return <Label basic color={type2label[type]?.color}>{type2label[type] ? type2label[type].text : type}</Label>;
|
||||
return (
|
||||
<Label basic color={type2label[type]?.color}>
|
||||
{type2label[type] ? type2label[type].text : type}
|
||||
</Label>
|
||||
);
|
||||
}
|
||||
|
||||
function renderBalance(type, balance) {
|
||||
@ -62,10 +72,10 @@ function renderBalance(type, balance) {
|
||||
}
|
||||
|
||||
function isShowDetail() {
|
||||
return localStorage.getItem("show_detail") === "true";
|
||||
return localStorage.getItem('show_detail') === 'true';
|
||||
}
|
||||
|
||||
const promptID = "detail"
|
||||
const promptID = 'detail';
|
||||
|
||||
const ChannelsTable = () => {
|
||||
const [channels, setChannels] = useState([]);
|
||||
@ -84,7 +94,7 @@ const ChannelsTable = () => {
|
||||
let localChannels = data.map((channel) => {
|
||||
if (channel.models === '') {
|
||||
channel.models = [];
|
||||
channel.test_model = "";
|
||||
channel.test_model = '';
|
||||
} else {
|
||||
channel.models = channel.models.split(',');
|
||||
if (channel.models.length > 0) {
|
||||
@ -95,9 +105,9 @@ const ChannelsTable = () => {
|
||||
key: model,
|
||||
text: model,
|
||||
value: model,
|
||||
}
|
||||
})
|
||||
console.log('channel', channel)
|
||||
};
|
||||
});
|
||||
console.log('channel', channel);
|
||||
}
|
||||
return channel;
|
||||
});
|
||||
@ -105,7 +115,11 @@ const ChannelsTable = () => {
|
||||
setChannels(localChannels);
|
||||
} else {
|
||||
let newChannels = [...channels];
|
||||
newChannels.splice(startIdx * ITEMS_PER_PAGE, data.length, ...localChannels);
|
||||
newChannels.splice(
|
||||
startIdx * ITEMS_PER_PAGE,
|
||||
data.length,
|
||||
...localChannels
|
||||
);
|
||||
setChannels(newChannels);
|
||||
}
|
||||
} else {
|
||||
@ -131,8 +145,8 @@ const ChannelsTable = () => {
|
||||
|
||||
const toggleShowDetail = () => {
|
||||
setShowDetail(!showDetail);
|
||||
localStorage.setItem("show_detail", (!showDetail).toString());
|
||||
}
|
||||
localStorage.setItem('show_detail', (!showDetail).toString());
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadChannels(0)
|
||||
@ -196,13 +210,19 @@ const ChannelsTable = () => {
|
||||
const renderStatus = (status) => {
|
||||
switch (status) {
|
||||
case 1:
|
||||
return <Label basic color='green'>已启用</Label>;
|
||||
return (
|
||||
<Label basic color='green'>
|
||||
已启用
|
||||
</Label>
|
||||
);
|
||||
case 2:
|
||||
return (
|
||||
<Popup
|
||||
trigger={<Label basic color='red'>
|
||||
trigger={
|
||||
<Label basic color='red'>
|
||||
已禁用
|
||||
</Label>}
|
||||
</Label>
|
||||
}
|
||||
content='本渠道被手动禁用'
|
||||
basic
|
||||
/>
|
||||
@ -210,9 +230,11 @@ const ChannelsTable = () => {
|
||||
case 3:
|
||||
return (
|
||||
<Popup
|
||||
trigger={<Label basic color='yellow'>
|
||||
trigger={
|
||||
<Label basic color='yellow'>
|
||||
已禁用
|
||||
</Label>}
|
||||
</Label>
|
||||
}
|
||||
content='本渠道被程序自动禁用'
|
||||
basic
|
||||
/>
|
||||
@ -230,15 +252,35 @@ const ChannelsTable = () => {
|
||||
let time = responseTime / 1000;
|
||||
time = time.toFixed(2) + ' 秒';
|
||||
if (responseTime === 0) {
|
||||
return <Label basic color='grey'>未测试</Label>;
|
||||
return (
|
||||
<Label basic color='grey'>
|
||||
未测试
|
||||
</Label>
|
||||
);
|
||||
} else if (responseTime <= 1000) {
|
||||
return <Label basic color='green'>{time}</Label>;
|
||||
return (
|
||||
<Label basic color='green'>
|
||||
{time}
|
||||
</Label>
|
||||
);
|
||||
} else if (responseTime <= 3000) {
|
||||
return <Label basic color='olive'>{time}</Label>;
|
||||
return (
|
||||
<Label basic color='olive'>
|
||||
{time}
|
||||
</Label>
|
||||
);
|
||||
} else if (responseTime <= 5000) {
|
||||
return <Label basic color='yellow'>{time}</Label>;
|
||||
return (
|
||||
<Label basic color='yellow'>
|
||||
{time}
|
||||
</Label>
|
||||
);
|
||||
} else {
|
||||
return <Label basic color='red'>{time}</Label>;
|
||||
return (
|
||||
<Label basic color='red'>
|
||||
{time}
|
||||
</Label>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@ -277,7 +319,11 @@ const ChannelsTable = () => {
|
||||
newChannels[realIdx].response_time = time * 1000;
|
||||
newChannels[realIdx].test_time = Date.now() / 1000;
|
||||
setChannels(newChannels);
|
||||
showInfo(`渠道 ${name} 测试成功,模型 ${model},耗时 ${time.toFixed(2)} 秒。`);
|
||||
showInfo(
|
||||
`渠道 ${name} 测试成功,模型 ${model},耗时 ${time.toFixed(
|
||||
2
|
||||
)} 秒,模型输出:${message}`
|
||||
);
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
@ -360,7 +406,6 @@ const ChannelsTable = () => {
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form onSubmit={searchChannels}>
|
||||
@ -374,20 +419,22 @@ const ChannelsTable = () => {
|
||||
onChange={handleKeywordChange}
|
||||
/>
|
||||
</Form>
|
||||
{
|
||||
showPrompt && (
|
||||
<Message onDismiss={() => {
|
||||
{showPrompt && (
|
||||
<Message
|
||||
onDismiss={() => {
|
||||
setShowPrompt(false);
|
||||
setPromptShown(promptID);
|
||||
}}>
|
||||
OpenAI 渠道已经不再支持通过 key 获取余额,因此余额显示为 0。对于支持的渠道类型,请点击余额进行刷新。
|
||||
<br/>
|
||||
渠道测试仅支持 chat 模型,优先使用 gpt-3.5-turbo,如果该模型不可用则使用你所配置的模型列表中的第一个模型。
|
||||
<br/>
|
||||
}}
|
||||
>
|
||||
OpenAI 渠道已经不再支持通过 key 获取余额,因此余额显示为
|
||||
0。对于支持的渠道类型,请点击余额进行刷新。
|
||||
<br />
|
||||
渠道测试仅支持 chat 模型,优先使用
|
||||
gpt-3.5-turbo,如果该模型不可用则使用你所配置的模型列表中的第一个模型。
|
||||
<br />
|
||||
点击下方详情按钮可以显示余额以及设置额外的测试模型。
|
||||
</Message>
|
||||
)
|
||||
}
|
||||
)}
|
||||
<Table basic compact size='small'>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
@ -478,7 +525,11 @@ const ChannelsTable = () => {
|
||||
<Table.Cell>{renderStatus(channel.status)}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Popup
|
||||
content={channel.test_time ? renderTimestamp(channel.test_time) : '未测试'}
|
||||
content={
|
||||
channel.test_time
|
||||
? renderTimestamp(channel.test_time)
|
||||
: '未测试'
|
||||
}
|
||||
key={channel.id}
|
||||
trigger={renderResponseTime(channel.response_time)}
|
||||
basic
|
||||
@ -486,27 +537,38 @@ const ChannelsTable = () => {
|
||||
</Table.Cell>
|
||||
<Table.Cell hidden={!showDetail}>
|
||||
<Popup
|
||||
trigger={<span onClick={() => {
|
||||
trigger={
|
||||
<span
|
||||
onClick={() => {
|
||||
updateChannelBalance(channel.id, channel.name, idx);
|
||||
}} style={{ cursor: 'pointer' }}>
|
||||
}}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
{renderBalance(channel.type, channel.balance)}
|
||||
</span>}
|
||||
</span>
|
||||
}
|
||||
content='点击更新'
|
||||
basic
|
||||
/>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Popup
|
||||
trigger={<Input type='number' defaultValue={channel.priority} onBlur={(event) => {
|
||||
trigger={
|
||||
<Input
|
||||
type='number'
|
||||
defaultValue={channel.priority}
|
||||
onBlur={(event) => {
|
||||
manageChannel(
|
||||
channel.id,
|
||||
'priority',
|
||||
idx,
|
||||
event.target.value
|
||||
);
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<input style={{ maxWidth: '60px' }} />
|
||||
</Input>}
|
||||
</Input>
|
||||
}
|
||||
content='渠道选择优先级,越高越优先'
|
||||
basic
|
||||
/>
|
||||
@ -528,7 +590,12 @@ const ChannelsTable = () => {
|
||||
size={'small'}
|
||||
positive
|
||||
onClick={() => {
|
||||
testChannel(channel.id, channel.name, idx, channel.test_model);
|
||||
testChannel(
|
||||
channel.id,
|
||||
channel.name,
|
||||
idx,
|
||||
channel.test_model
|
||||
);
|
||||
}}
|
||||
>
|
||||
测试
|
||||
@ -590,14 +657,31 @@ const ChannelsTable = () => {
|
||||
|
||||
<Table.Footer>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell colSpan={showDetail ? "10" : "8"}>
|
||||
<Button size='small' as={Link} to='/channel/add' loading={loading}>
|
||||
<Table.HeaderCell colSpan={showDetail ? '10' : '8'}>
|
||||
<Button
|
||||
size='small'
|
||||
as={Link}
|
||||
to='/channel/add'
|
||||
loading={loading}
|
||||
>
|
||||
添加新的渠道
|
||||
</Button>
|
||||
<Button size='small' loading={loading} onClick={()=>{testChannels("all")}}>
|
||||
<Button
|
||||
size='small'
|
||||
loading={loading}
|
||||
onClick={() => {
|
||||
testChannels('all');
|
||||
}}
|
||||
>
|
||||
测试所有渠道
|
||||
</Button>
|
||||
<Button size='small' loading={loading} onClick={()=>{testChannels("disabled")}}>
|
||||
<Button
|
||||
size='small'
|
||||
loading={loading}
|
||||
onClick={() => {
|
||||
testChannels('disabled');
|
||||
}}
|
||||
>
|
||||
测试禁用渠道
|
||||
</Button>
|
||||
{/*<Button size='small' onClick={updateAllChannelsBalance}*/}
|
||||
@ -612,7 +696,12 @@ const ChannelsTable = () => {
|
||||
flowing
|
||||
hoverable
|
||||
>
|
||||
<Button size='small' loading={loading} negative onClick={deleteAllDisabledChannels}>
|
||||
<Button
|
||||
size='small'
|
||||
loading={loading}
|
||||
negative
|
||||
onClick={deleteAllDisabledChannels}
|
||||
>
|
||||
确认删除
|
||||
</Button>
|
||||
</Popup>
|
||||
@ -627,8 +716,12 @@ const ChannelsTable = () => {
|
||||
(channels.length % ITEMS_PER_PAGE === 0 ? 1 : 0)
|
||||
}
|
||||
/>
|
||||
<Button size='small' onClick={refresh} loading={loading}>刷新</Button>
|
||||
<Button size='small' onClick={toggleShowDetail}>{showDetail ? "隐藏详情" : "详情"}</Button>
|
||||
<Button size='small' onClick={refresh} loading={loading}>
|
||||
刷新
|
||||
</Button>
|
||||
<Button size='small' onClick={toggleShowDetail}>
|
||||
{showDetail ? '隐藏详情' : '详情'}
|
||||
</Button>
|
||||
</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Footer>
|
||||
|
@ -21,6 +21,7 @@ import {
|
||||
|
||||
import { ITEMS_PER_PAGE } from '../constants';
|
||||
import { renderColorLabel, renderQuota } from '../helpers/render';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
function renderTimestamp(timestamp, request_id) {
|
||||
return (
|
||||
@ -50,6 +51,7 @@ const LOG_OPTIONS = [
|
||||
{ key: '2', text: '消费', value: 2 },
|
||||
{ key: '3', text: '管理', value: 3 },
|
||||
{ key: '4', text: '系统', value: 4 },
|
||||
{ key: '5', text: '测试', value: 5 },
|
||||
];
|
||||
|
||||
function renderType(type) {
|
||||
@ -78,6 +80,12 @@ function renderType(type) {
|
||||
系统
|
||||
</Label>
|
||||
);
|
||||
case 5:
|
||||
return (
|
||||
<Label basic color='violet'>
|
||||
测试
|
||||
</Label>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<Label basic color='black'>
|
||||
@ -203,6 +211,10 @@ const LogsTable = () => {
|
||||
setShowStat(!showStat);
|
||||
};
|
||||
|
||||
const showUserTokenQuota = () => {
|
||||
return logType !== 5;
|
||||
};
|
||||
|
||||
const loadLogs = async (startIdx) => {
|
||||
let url = '';
|
||||
let localStartTimestamp = Date.parse(start_timestamp) / 1000;
|
||||
@ -399,6 +411,26 @@ const LogsTable = () => {
|
||||
渠道
|
||||
</Table.HeaderCell>
|
||||
)}
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
sortLog('type');
|
||||
}}
|
||||
width={1}
|
||||
>
|
||||
类型
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
sortLog('model_name');
|
||||
}}
|
||||
width={2}
|
||||
>
|
||||
模型
|
||||
</Table.HeaderCell>
|
||||
{showUserTokenQuota() && (
|
||||
<>
|
||||
{isAdminUser && (
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
@ -419,24 +451,6 @@ const LogsTable = () => {
|
||||
>
|
||||
令牌
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
sortLog('type');
|
||||
}}
|
||||
width={1}
|
||||
>
|
||||
类型
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
sortLog('model_name');
|
||||
}}
|
||||
width={2}
|
||||
>
|
||||
模型
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
@ -464,6 +478,8 @@ const LogsTable = () => {
|
||||
>
|
||||
额度
|
||||
</Table.HeaderCell>
|
||||
</>
|
||||
)}
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
@ -491,25 +507,46 @@ const LogsTable = () => {
|
||||
</Table.Cell>
|
||||
{isAdminUser && (
|
||||
<Table.Cell>
|
||||
{log.channel ? <Label basic>{log.channel}</Label> : ''}
|
||||
{log.channel ? (
|
||||
<Label
|
||||
basic
|
||||
as={Link}
|
||||
to={`/channel/edit/${log.channel}`}
|
||||
>
|
||||
{log.channel}
|
||||
</Label>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</Table.Cell>
|
||||
)}
|
||||
<Table.Cell>{renderType(log.type)}</Table.Cell>
|
||||
<Table.Cell>
|
||||
{log.model_name ? renderColorLabel(log.model_name) : ''}
|
||||
</Table.Cell>
|
||||
{showUserTokenQuota() && (
|
||||
<>
|
||||
{isAdminUser && (
|
||||
<Table.Cell>
|
||||
{log.username ? (
|
||||
<Label basic>{log.username}</Label>
|
||||
<Label
|
||||
basic
|
||||
as={Link}
|
||||
to={`/user/edit/${log.user_id}`}
|
||||
>
|
||||
{log.username}
|
||||
</Label>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</Table.Cell>
|
||||
)}
|
||||
<Table.Cell>
|
||||
{log.token_name ? renderColorLabel(log.token_name) : ''}
|
||||
</Table.Cell>
|
||||
<Table.Cell>{renderType(log.type)}</Table.Cell>
|
||||
<Table.Cell>
|
||||
{log.model_name ? renderColorLabel(log.model_name) : ''}
|
||||
{log.token_name
|
||||
? renderColorLabel(log.token_name)
|
||||
: ''}
|
||||
</Table.Cell>
|
||||
|
||||
<Table.Cell>
|
||||
{log.prompt_tokens ? log.prompt_tokens : ''}
|
||||
</Table.Cell>
|
||||
@ -519,6 +556,9 @@ const LogsTable = () => {
|
||||
<Table.Cell>
|
||||
{log.quota ? renderQuota(log.quota, 6) : ''}
|
||||
</Table.Cell>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Table.Cell>{renderDetail(log)}</Table.Cell>
|
||||
</Table.Row>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user