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