From 675de89c696a64de5a071a3e9011aef76df5c43d Mon Sep 17 00:00:00 2001 From: kakingone Date: Mon, 6 May 2024 17:18:04 +0800 Subject: [PATCH 01/11] --amend --- web/src/components/MjLogsTable.js | 34 +++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/web/src/components/MjLogsTable.js b/web/src/components/MjLogsTable.js index f22ddae..e86ee31 100644 --- a/web/src/components/MjLogsTable.js +++ b/web/src/components/MjLogsTable.js @@ -236,6 +236,31 @@ const renderTimestamp = (timestampInSeconds) => { return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; // 格式化输出 }; +// 修改renderDuration函数以包含颜色逻辑 +function renderDuration(submit_time, finishTime) { + // 确保startTime和finishTime都是有效的时间戳 + if (!submit_time || !finishTime) return 'N/A'; + + // 将时间戳转换为Date对象 + const start = new Date(submit_time); + const finish = new Date(finishTime); + + // 计算时间差(毫秒) + const durationMs = finish - start; + + // 将时间差转换为秒,并保留一位小数 + const durationSec = (durationMs / 1000).toFixed(1); + + // 设置颜色:大于60秒则为红色,小于等于60秒则为绿色 + const color = durationSec > 60 ? 'red' : 'green'; + + // 返回带有样式的颜色标签 + return ( + + {durationSec} 秒 + + ); +} const LogsTable = () => { const [isModalOpen, setIsModalOpen] = useState(false); @@ -248,6 +273,15 @@ const LogsTable = () => { return
{renderTimestamp(text / 1000)}
; }, }, + { + title: '花费时间', + dataIndex: 'finish_time', // 以finish_time作为dataIndex + key: 'finish_time', + render: (finish, record) => { + // 假设record.start_time是存在的,并且finish是完成时间的时间戳 + return renderDuration(record.submit_time, finish); + }, + }, { title: '渠道', dataIndex: 'channel_id', From 2bf404507ffce05a771299a8d396b71dfb60ddc2 Mon Sep 17 00:00:00 2001 From: "1808837298@qq.com" <1808837298@qq.com> Date: Wed, 8 May 2024 16:46:06 +0800 Subject: [PATCH 02/11] fix: update user (#230) --- model/user.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/model/user.go b/model/user.go index 3e7169a..eab8ced 100644 --- a/model/user.go +++ b/model/user.go @@ -253,14 +253,17 @@ func (user *User) Edit(updatePassword bool) error { } } newUser := *user - DB.First(&user, user.Id) - err = DB.Model(user).Updates(map[string]interface{}{ + updates := map[string]interface{}{ "username": newUser.Username, - "password": newUser.Password, "display_name": newUser.DisplayName, "group": newUser.Group, "quota": newUser.Quota, - }).Error + } + if updatePassword { + updates["password"] = newUser.Password + } + DB.First(&user, user.Id) + err = DB.Model(user).Updates(updates).Error if err == nil { if common.RedisEnabled { _ = common.RedisSet(fmt.Sprintf("user_group:%d", user.Id), user.Group, time.Duration(UserId2GroupCacheSeconds)*time.Second) From d7a343e2f686fba0aa1cb3294dcf2f9c5bbe51c0 Mon Sep 17 00:00:00 2001 From: "1808837298@qq.com" <1808837298@qq.com> Date: Wed, 8 May 2024 16:57:11 +0800 Subject: [PATCH 03/11] feat: update model ratio --- common/model-ratio.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/common/model-ratio.go b/common/model-ratio.go index 9d6c446..b0a97c0 100644 --- a/common/model-ratio.go +++ b/common/model-ratio.go @@ -111,6 +111,8 @@ var DefaultModelRatio = map[string]float64{ "command-light-nightly": 0.5, "command-r": 0.25, "command-r-plus ": 1.5, + "deepseek-chat": 0.07, + "deepseek-coder": 0.07, } var DefaultModelPrice = map[string]float64{ @@ -240,6 +242,9 @@ func GetCompletionRatio(name string) float64 { return 2 } } + if strings.HasPrefix(name, "deepseek") { + return 2 + } switch name { case "llama2-70b-4096": return 0.8 / 0.7 From 4641d446155377665f5abb46b294107c41542447 Mon Sep 17 00:00:00 2001 From: CaIon <1808837298@qq.com> Date: Sat, 11 May 2024 21:18:30 +0800 Subject: [PATCH 04/11] =?UTF-8?q?feat:=20=E9=99=90=E5=88=B6=E9=82=AE?= =?UTF-8?q?=E7=AE=B1=E5=88=AB=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/misc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/misc.go b/controller/misc.go index 273a618..8c59952 100644 --- a/controller/misc.go +++ b/controller/misc.go @@ -147,7 +147,7 @@ func SendEmailVerification(c *gin.Context) { } } if common.EmailAliasRestrictionEnabled { - containsSpecialSymbols := strings.Contains(localPart, "+") || strings.Count(localPart, ".") > 1 + containsSpecialSymbols := strings.Contains(localPart, "+") || strings.Contains(localPart, ".") if containsSpecialSymbols { c.JSON(http.StatusOK, gin.H{ "success": false, From 6fb1fbfe96962e72341e6f59657de6369667f74e Mon Sep 17 00:00:00 2001 From: CaIon <1808837298@qq.com> Date: Sun, 12 May 2024 15:35:57 +0800 Subject: [PATCH 05/11] =?UTF-8?q?feat:=20=E6=97=A5=E5=BF=97=E8=AF=A6?= =?UTF-8?q?=E6=83=85=E5=B1=95=E7=A4=BA=E6=A8=A1=E5=9E=8B=E4=BB=B7=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/model-ratio.go | 1 + common/utils.go | 9 ++++++ model/log.go | 5 ++- relay/relay-audio.go | 5 ++- relay/relay-image.go | 5 ++- relay/relay-mj.go | 10 ++++-- relay/relay-text.go | 7 ++++- web/src/components/LogsTable.js | 54 +++++++++++++++++++++++++++------ web/src/helpers/render.js | 26 ++++++++++++++++ 9 files changed, 106 insertions(+), 16 deletions(-) diff --git a/common/model-ratio.go b/common/model-ratio.go index b0a97c0..9ddbe12 100644 --- a/common/model-ratio.go +++ b/common/model-ratio.go @@ -28,6 +28,7 @@ var DefaultModelRatio = map[string]float64{ "gpt-4-vision-preview": 5, // $0.01 / 1K tokens "gpt-4-1106-vision-preview": 5, // $0.01 / 1K tokens "gpt-4-turbo": 5, // $0.01 / 1K tokens + "gpt-4-turbo-2024-04-09": 5, // $0.01 / 1K tokens "gpt-3.5-turbo": 0.25, // $0.0015 / 1K tokens //"gpt-3.5-turbo-0301": 0.75, //deprecated "gpt-3.5-turbo-0613": 0.75, diff --git a/common/utils.go b/common/utils.go index d540c2e..657ffd4 100644 --- a/common/utils.go +++ b/common/utils.go @@ -1,6 +1,7 @@ package common import ( + "encoding/json" "fmt" "github.com/google/uuid" "html/template" @@ -241,3 +242,11 @@ func RandomSleep() { // Sleep for 0-3000 ms time.Sleep(time.Duration(rand.Intn(3000)) * time.Millisecond) } + +func MapToJsonStr(m map[string]interface{}) string { + bytes, err := json.Marshal(m) + if err != nil { + return "" + } + return string(bytes) +} diff --git a/model/log.go b/model/log.go index 2740c5a..57c64d0 100644 --- a/model/log.go +++ b/model/log.go @@ -24,6 +24,7 @@ type Log struct { IsStream bool `json:"is_stream" gorm:"default:false"` ChannelId int `json:"channel" gorm:"index"` TokenId int `json:"token_id" gorm:"default:0;index"` + Other string `json:"other"` } const ( @@ -57,12 +58,13 @@ func RecordLog(userId int, logType int, content string) { } } -func RecordConsumeLog(ctx context.Context, userId int, channelId int, promptTokens int, completionTokens int, modelName string, tokenName string, quota int, content string, tokenId int, userQuota int, useTimeSeconds int, isStream bool) { +func RecordConsumeLog(ctx context.Context, userId int, channelId int, promptTokens int, completionTokens int, modelName string, tokenName string, quota int, content string, tokenId int, userQuota int, useTimeSeconds int, isStream bool, other map[string]interface{}) { common.LogInfo(ctx, fmt.Sprintf("record consume log: userId=%d, 用户调用前余额=%d, channelId=%d, promptTokens=%d, completionTokens=%d, modelName=%s, tokenName=%s, quota=%d, content=%s", userId, userQuota, channelId, promptTokens, completionTokens, modelName, tokenName, quota, content)) if !common.LogConsumeEnabled { return } username, _ := CacheGetUsername(userId) + otherStr := common.MapToJsonStr(other) log := &Log{ UserId: userId, Username: username, @@ -78,6 +80,7 @@ func RecordConsumeLog(ctx context.Context, userId int, channelId int, promptToke TokenId: tokenId, UseTime: useTimeSeconds, IsStream: isStream, + Other: otherStr, } err := DB.Create(log).Error if err != nil { diff --git a/relay/relay-audio.go b/relay/relay-audio.go index 09ac2a0..ef89597 100644 --- a/relay/relay-audio.go +++ b/relay/relay-audio.go @@ -196,7 +196,10 @@ func AudioHelper(c *gin.Context, relayMode int) *dto.OpenAIErrorWithStatusCode { if quota != 0 { tokenName := c.GetString("token_name") logContent := fmt.Sprintf("模型倍率 %.2f,分组倍率 %.2f", modelRatio, groupRatio) - model.RecordConsumeLog(ctx, userId, channelId, promptTokens, 0, audioRequest.Model, tokenName, quota, logContent, tokenId, userQuota, int(useTimeSeconds), false) + other := make(map[string]interface{}) + other["model_ratio"] = modelRatio + other["group_ratio"] = groupRatio + model.RecordConsumeLog(ctx, userId, channelId, promptTokens, 0, audioRequest.Model, tokenName, quota, logContent, tokenId, userQuota, int(useTimeSeconds), false, other) model.UpdateUserUsedQuotaAndRequestCount(userId, quota) channelId := c.GetInt("channel_id") model.UpdateChannelUsedQuota(channelId, quota) diff --git a/relay/relay-image.go b/relay/relay-image.go index ce072d1..7f8cd9e 100644 --- a/relay/relay-image.go +++ b/relay/relay-image.go @@ -191,7 +191,10 @@ func RelayImageHelper(c *gin.Context, relayMode int) *dto.OpenAIErrorWithStatusC quality = "hd" } logContent := fmt.Sprintf("模型倍率 %.2f,分组倍率 %.2f, 大小 %s, 品质 %s", modelRatio, groupRatio, imageRequest.Size, quality) - model.RecordConsumeLog(ctx, userId, channelId, 0, 0, imageRequest.Model, tokenName, quota, logContent, tokenId, userQuota, int(useTimeSeconds), false) + other := make(map[string]interface{}) + other["model_ratio"] = modelRatio + other["group_ratio"] = groupRatio + model.RecordConsumeLog(ctx, userId, channelId, 0, 0, imageRequest.Model, tokenName, quota, logContent, tokenId, userQuota, int(useTimeSeconds), false, other) model.UpdateUserUsedQuotaAndRequestCount(userId, quota) channelId := c.GetInt("channel_id") model.UpdateChannelUsedQuota(channelId, quota) diff --git a/relay/relay-mj.go b/relay/relay-mj.go index 27b4c6d..16ad412 100644 --- a/relay/relay-mj.go +++ b/relay/relay-mj.go @@ -202,7 +202,10 @@ func RelaySwapFace(c *gin.Context) *dto.MidjourneyResponse { if quota != 0 { tokenName := c.GetString("token_name") logContent := fmt.Sprintf("模型固定价格 %.2f,分组倍率 %.2f,操作 %s", modelPrice, groupRatio, constant.MjActionSwapFace) - model.RecordConsumeLog(ctx, userId, channelId, 0, 0, modelName, tokenName, quota, logContent, tokenId, userQuota, 0, false) + other := make(map[string]interface{}) + other["model_price"] = modelPrice + other["group_ratio"] = groupRatio + model.RecordConsumeLog(ctx, userId, channelId, 0, 0, modelName, tokenName, quota, logContent, tokenId, userQuota, 0, false, other) model.UpdateUserUsedQuotaAndRequestCount(userId, quota) channelId := c.GetInt("channel_id") model.UpdateChannelUsedQuota(channelId, quota) @@ -498,7 +501,10 @@ func RelayMidjourneySubmit(c *gin.Context, relayMode int) *dto.MidjourneyRespons if quota != 0 { tokenName := c.GetString("token_name") logContent := fmt.Sprintf("模型固定价格 %.2f,分组倍率 %.2f,操作 %s", modelPrice, groupRatio, midjRequest.Action) - model.RecordConsumeLog(ctx, userId, channelId, 0, 0, modelName, tokenName, quota, logContent, tokenId, userQuota, 0, false) + other := make(map[string]interface{}) + other["model_price"] = modelPrice + other["group_ratio"] = groupRatio + model.RecordConsumeLog(ctx, userId, channelId, 0, 0, modelName, tokenName, quota, logContent, tokenId, userQuota, 0, false, other) model.UpdateUserUsedQuotaAndRequestCount(userId, quota) channelId := c.GetInt("channel_id") model.UpdateChannelUsedQuota(channelId, quota) diff --git a/relay/relay-text.go b/relay/relay-text.go index 7c2720e..9010381 100644 --- a/relay/relay-text.go +++ b/relay/relay-text.go @@ -315,7 +315,12 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, textRe logModel = "gpt-4-gizmo-*" logContent += fmt.Sprintf(",模型 %s", textRequest.Model) } - model.RecordConsumeLog(ctx, relayInfo.UserId, relayInfo.ChannelId, promptTokens, completionTokens, logModel, tokenName, quota, logContent, relayInfo.TokenId, userQuota, int(useTimeSeconds), relayInfo.IsStream) + other := make(map[string]interface{}) + other["model_ratio"] = modelRatio + other["group_ratio"] = groupRatio + other["completion_ratio"] = completionRatio + other["model_price"] = modelPrice + model.RecordConsumeLog(ctx, relayInfo.UserId, relayInfo.ChannelId, promptTokens, completionTokens, logModel, tokenName, quota, logContent, relayInfo.TokenId, userQuota, int(useTimeSeconds), relayInfo.IsStream, other) //if quota != 0 { // diff --git a/web/src/components/LogsTable.js b/web/src/components/LogsTable.js index 9331fa7..0de3632 100644 --- a/web/src/components/LogsTable.js +++ b/web/src/components/LogsTable.js @@ -19,9 +19,15 @@ import { Spin, Table, Tag, + Tooltip, } from '@douyinfe/semi-ui'; import { ITEMS_PER_PAGE } from '../constants'; -import { renderNumber, renderQuota, stringToColor } from '../helpers/render'; +import { + renderModelPrice, + renderNumber, + renderQuota, + stringToColor, +} from '../helpers/render'; import Paragraph from '@douyinfe/semi-ui/lib/es/typography/paragraph'; const { Header } = Layout; @@ -292,16 +298,44 @@ const LogsTable = () => { title: '详情', dataIndex: 'content', render: (text, record, index) => { + if (record.other === '') { + return ( + + {text} + + ); + } + let other = JSON.parse(record.other); + let content = renderModelPrice( + other.model_ratio, + other.model_price, + other.completion_ratio, + other.group_ratio, + ); return ( - - {text} - + + + {text} + + ); }, }, diff --git a/web/src/helpers/render.js b/web/src/helpers/render.js index 8cea432..9d36682 100644 --- a/web/src/helpers/render.js +++ b/web/src/helpers/render.js @@ -135,6 +135,32 @@ export function renderQuota(quota, digits = 2) { return renderNumber(quota); } +export function renderModelPrice( + modelRatio, + modelPrice = -1, + completionRatio, + groupRatio, +) { + // 1 ratio = $0.002 / 1K tokens + if (modelPrice !== -1) { + return '模型价格:$' + modelPrice * groupRatio; + } else { + if (completionRatio === undefined) { + completionRatio = 0; + } + let inputRatioPrice = modelRatio * 0.002 * groupRatio; + let completionRatioPrice = + modelRatio * completionRatio * 0.002 * groupRatio; + return ( + '输入:$' + + inputRatioPrice.toFixed(3) + + '/1K tokens,补全:$' + + completionRatioPrice.toFixed(3) + + '/1K tokens' + ); + } +} + export function renderQuotaWithPrompt(quota, digits) { let displayInCurrency = localStorage.getItem('display_in_currency'); displayInCurrency = displayInCurrency === 'true'; From b427f0278f1047558f0a676dfbb9d697322b6ac0 Mon Sep 17 00:00:00 2001 From: CaIon <1808837298@qq.com> Date: Sun, 12 May 2024 16:06:19 +0800 Subject: [PATCH 06/11] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B9=E4=BE=BF?= =?UTF-8?q?=E5=9C=B0=E7=BC=96=E8=BE=91=E7=94=A8=E6=88=B7=E9=A2=9D=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/pages/User/EditUser.js | 56 ++++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/web/src/pages/User/EditUser.js b/web/src/pages/User/EditUser.js index 596edcc..90bbc4b 100644 --- a/web/src/pages/User/EditUser.js +++ b/web/src/pages/User/EditUser.js @@ -1,12 +1,13 @@ import React, { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { API, isMobile, showError, showSuccess } from '../../helpers'; -import { renderQuotaWithPrompt } from '../../helpers/render'; +import { renderQuota, renderQuotaWithPrompt } from '../../helpers/render'; import Title from '@douyinfe/semi-ui/lib/es/typography/title'; import { Button, Divider, Input, + Modal, Select, SideSheet, Space, @@ -17,6 +18,8 @@ import { const EditUser = (props) => { const userId = props.editingUser.id; const [loading, setLoading] = useState(true); + const [addQuotaModalOpen, setIsModalOpen] = useState(false); + const [addQuotaLocal, setAddQuotaLocal] = useState(0); const [inputs, setInputs] = useState({ username: '', display_name: '', @@ -107,6 +110,16 @@ const EditUser = (props) => { setLoading(false); }; + const addLocalQuota = () => { + let newQuota = parseInt(quota) + addQuotaLocal; + setInputs((inputs) => ({ ...inputs, quota: newQuota })); + }; + + const openAddQuotaModal = () => { + setAddQuotaLocal(0); + setIsModalOpen(true); + }; + return ( <> {
{`剩余额度${renderQuotaWithPrompt(quota)}`}
- handleInputChange('quota', value)} - value={quota} - type={'number'} - autoComplete='new-password' - /> + + handleInputChange('quota', value)} + value={quota} + type={'number'} + autoComplete='new-password' + /> + + )} 以下信息不可修改 @@ -245,6 +261,28 @@ const EditUser = (props) => { />
+ { + addLocalQuota(); + setIsModalOpen(false); + }} + onCancel={() => setIsModalOpen(false)} + closable={null} + > +
+ {`新额度${renderQuota(quota)} + ${renderQuota(addQuotaLocal)} = ${renderQuota(quota + addQuotaLocal)}`} +
+ setAddQuotaLocal(parseInt(value))} + value={addQuotaLocal} + type={'number'} + autoComplete='new-password' + /> +
); }; From d8c006046f5874aea5d193deef2b4e5c7866b737 Mon Sep 17 00:00:00 2001 From: CaIon <1808837298@qq.com> Date: Sun, 12 May 2024 16:12:31 +0800 Subject: [PATCH 07/11] =?UTF-8?q?feat:=20=E7=BC=96=E8=BE=91=E9=A2=9D?= =?UTF-8?q?=E5=BA=A6=E6=94=AF=E6=8C=81=E8=B4=9F=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/pages/User/EditUser.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/web/src/pages/User/EditUser.js b/web/src/pages/User/EditUser.js index 90bbc4b..fa5f890 100644 --- a/web/src/pages/User/EditUser.js +++ b/web/src/pages/User/EditUser.js @@ -19,7 +19,7 @@ const EditUser = (props) => { const userId = props.editingUser.id; const [loading, setLoading] = useState(true); const [addQuotaModalOpen, setIsModalOpen] = useState(false); - const [addQuotaLocal, setAddQuotaLocal] = useState(0); + const [addQuotaLocal, setAddQuotaLocal] = useState(''); const [inputs, setInputs] = useState({ username: '', display_name: '', @@ -111,12 +111,12 @@ const EditUser = (props) => { }; const addLocalQuota = () => { - let newQuota = parseInt(quota) + addQuotaLocal; + let newQuota = parseInt(quota) + parseInt(addQuotaLocal); setInputs((inputs) => ({ ...inputs, quota: newQuota })); }; const openAddQuotaModal = () => { - setAddQuotaLocal(0); + setAddQuotaLocal('0'); setIsModalOpen(true); }; @@ -272,12 +272,14 @@ const EditUser = (props) => { closable={null} >
- {`新额度${renderQuota(quota)} + ${renderQuota(addQuotaLocal)} = ${renderQuota(quota + addQuotaLocal)}`} + {`新额度${renderQuota(quota)} + ${renderQuota(addQuotaLocal)} = ${renderQuota(quota + parseInt(addQuotaLocal))}`}
setAddQuotaLocal(parseInt(value))} + placeholder={'需要添加的额度(支持负数)'} + onChange={(value) => { + setAddQuotaLocal(value); + }} value={addQuotaLocal} type={'number'} autoComplete='new-password' From 2dbf50dc0776e2de605357b541e6a1ea329864f6 Mon Sep 17 00:00:00 2001 From: CaIon <1808837298@qq.com> Date: Sun, 12 May 2024 19:07:33 +0800 Subject: [PATCH 08/11] =?UTF-8?q?feat:=20=E5=A1=AB=E5=85=A5=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/constants.go | 2 + controller/model.go | 35 +++++++-- relay/channel/ai360/constants.go | 2 + relay/channel/ollama/constants.go | 4 +- relay/channel/openai/constant.go | 2 +- relay/constant/api_type.go | 12 ++- router/api-router.go | 1 + web/src/components/ChannelsTable.js | 30 ++++--- web/src/components/utils.js | 29 +++++++ web/src/constants/channel.constants.js | 4 +- web/src/pages/Channel/EditChannel.js | 105 ++++--------------------- 11 files changed, 111 insertions(+), 115 deletions(-) diff --git a/common/constants.go b/common/constants.go index 99c78ac..cb58391 100644 --- a/common/constants.go +++ b/common/constants.go @@ -208,6 +208,8 @@ const ( ChannelTypeLingYiWanWu = 31 ChannelTypeAws = 33 ChannelTypeCohere = 34 + + ChannelTypeDummy // this one is only for count, do not add any channel after this ) var ChannelBaseURLs = []string{ diff --git a/controller/model.go b/controller/model.go index 1e78b4c..4d8e5e8 100644 --- a/controller/model.go +++ b/controller/model.go @@ -3,14 +3,17 @@ package controller import ( "fmt" "github.com/gin-gonic/gin" + "log" "net/http" + "one-api/common" "one-api/constant" "one-api/dto" "one-api/model" "one-api/relay" "one-api/relay/channel/ai360" - "one-api/relay/channel/moonshot" "one-api/relay/channel/lingyiwanwu" + "one-api/relay/channel/moonshot" + relaycommon "one-api/relay/common" relayconstant "one-api/relay/constant" ) @@ -43,6 +46,7 @@ type OpenAIModels struct { var openAIModels []OpenAIModels var openAIModelsMap map[string]OpenAIModels +var channelId2Models map[int][]string func init() { var permission []OpenAIModelPermission @@ -85,7 +89,7 @@ func init() { Id: modelName, Object: "model", Created: 1626777600, - OwnedBy: "360", + OwnedBy: ai360.ChannelName, Permission: permission, Root: modelName, Parent: nil, @@ -128,6 +132,18 @@ func init() { for _, model := range openAIModels { openAIModelsMap[model.Id] = model } + channelId2Models = make(map[int][]string) + for i := 1; i <= common.ChannelTypeDummy; i++ { + apiType := relayconstant.ChannelType2APIType(i) + if apiType == -1 || apiType == relayconstant.APITypeAIProxyLibrary { + continue + } + log.Println(apiType) + meta := &relaycommon.RelayInfo{ChannelType: i} + adaptor := relay.GetAdaptor(apiType) + adaptor.Init(meta, dto.GeneralOpenAIRequest{}) + channelId2Models[i] = adaptor.GetModelList() + } } func ListModels(c *gin.Context) { @@ -148,15 +164,22 @@ func ListModels(c *gin.Context) { } } c.JSON(200, gin.H{ - "object": "list", - "data": userOpenAiModels, + "success": true, + "data": userOpenAiModels, }) } func ChannelListModels(c *gin.Context) { c.JSON(200, gin.H{ - "object": "list", - "data": openAIModels, + "success": true, + "data": openAIModels, + }) +} + +func DashboardListModels(c *gin.Context) { + c.JSON(200, gin.H{ + "success": true, + "data": channelId2Models, }) } diff --git a/relay/channel/ai360/constants.go b/relay/channel/ai360/constants.go index cfc3cb2..82698fa 100644 --- a/relay/channel/ai360/constants.go +++ b/relay/channel/ai360/constants.go @@ -6,3 +6,5 @@ var ModelList = []string{ "embedding_s1_v1", "semantic_similarity_s1_v1", } + +var ChannelName = "ai360" diff --git a/relay/channel/ollama/constants.go b/relay/channel/ollama/constants.go index 970e977..682626a 100644 --- a/relay/channel/ollama/constants.go +++ b/relay/channel/ollama/constants.go @@ -1,5 +1,7 @@ package ollama -var ModelList []string +var ModelList = []string{ + "llama3-7b", +} var ChannelName = "ollama" diff --git a/relay/channel/openai/constant.go b/relay/channel/openai/constant.go index 91f4e51..8c560c7 100644 --- a/relay/channel/openai/constant.go +++ b/relay/channel/openai/constant.go @@ -6,7 +6,7 @@ var ModelList = []string{ "gpt-3.5-turbo-instruct", "gpt-4", "gpt-4-0314", "gpt-4-0613", "gpt-4-1106-preview", "gpt-4-0125-preview", "gpt-4-32k", "gpt-4-32k-0314", "gpt-4-32k-0613", - "gpt-4-turbo-preview", + "gpt-4-turbo-preview", "gpt-4-turbo", "gpt-4-turbo-2024-04-09", "gpt-4-vision-preview", "text-embedding-ada-002", "text-embedding-3-small", "text-embedding-3-large", "text-curie-001", "text-babbage-001", "text-ada-001", "text-davinci-002", "text-davinci-003", diff --git a/relay/constant/api_type.go b/relay/constant/api_type.go index 7f11ae2..1bc8b47 100644 --- a/relay/constant/api_type.go +++ b/relay/constant/api_type.go @@ -25,8 +25,18 @@ const ( ) func ChannelType2APIType(channelType int) int { - apiType := APITypeOpenAI + apiType := -1 switch channelType { + case common.ChannelTypeOpenAI: + apiType = APITypeOpenAI + case common.ChannelTypeAzure: + apiType = APITypeOpenAI + case common.ChannelTypeMoonshot: + apiType = APITypeOpenAI + case common.ChannelTypeLingYiWanWu: + apiType = APITypeOpenAI + case common.ChannelType360: + apiType = APITypeOpenAI case common.ChannelTypeAnthropic: apiType = APITypeAnthropic case common.ChannelTypeBaidu: diff --git a/router/api-router.go b/router/api-router.go index 8547454..8c0ae30 100644 --- a/router/api-router.go +++ b/router/api-router.go @@ -14,6 +14,7 @@ func SetApiRouter(router *gin.Engine) { apiRouter.Use(middleware.GlobalAPIRateLimit()) { apiRouter.GET("/status", controller.GetStatus) + apiRouter.GET("/models", middleware.UserAuth(), controller.DashboardListModels) apiRouter.GET("/status/test", middleware.AdminAuth(), controller.TestStatus) apiRouter.GET("/notice", controller.GetNotice) apiRouter.GET("/about", controller.GetAbout) diff --git a/web/src/components/ChannelsTable.js b/web/src/components/ChannelsTable.js index ebfcf4d..452309c 100644 --- a/web/src/components/ChannelsTable.js +++ b/web/src/components/ChannelsTable.js @@ -31,6 +31,7 @@ import { } from '@douyinfe/semi-ui'; import EditChannel from '../pages/Channel/EditChannel'; import { IconTreeTriangleDown } from '@douyinfe/semi-icons'; +import { loadChannelModels } from './utils.js'; function renderTimestamp(timestamp) { return <>{timestamp2string(timestamp)}; @@ -354,27 +355,29 @@ const ChannelsTable = () => { }; const copySelectedChannel = async (id) => { - const channelToCopy = channels.find(channel => String(channel.id) === String(id)); - console.log(channelToCopy) + const channelToCopy = channels.find( + (channel) => String(channel.id) === String(id), + ); + console.log(channelToCopy); channelToCopy.name += '_复制'; channelToCopy.created_time = null; channelToCopy.balance = 0; channelToCopy.used_quota = 0; if (!channelToCopy) { - showError("渠道未找到,请刷新页面后重试。"); - return; + showError('渠道未找到,请刷新页面后重试。'); + return; } try { - const newChannel = {...channelToCopy, id: undefined}; - const response = await API.post('/api/channel/', newChannel); - if (response.data.success) { - showSuccess("渠道复制成功"); - await refresh(); - } else { - showError(response.data.message); - } + const newChannel = { ...channelToCopy, id: undefined }; + const response = await API.post('/api/channel/', newChannel); + if (response.data.success) { + showSuccess('渠道复制成功'); + await refresh(); + } else { + showError(response.data.message); + } } catch (error) { - showError("渠道复制失败: " + error.message); + showError('渠道复制失败: ' + error.message); } }; @@ -395,6 +398,7 @@ const ChannelsTable = () => { showError(reason); }); fetchGroups().then(); + loadChannelModels().then(); }, []); const manageChannel = async (id, action, record, value) => { diff --git a/web/src/components/utils.js b/web/src/components/utils.js index 59e3a01..1f0ee30 100644 --- a/web/src/components/utils.js +++ b/web/src/components/utils.js @@ -18,3 +18,32 @@ export async function onGitHubOAuthClicked(github_client_id) { `https://github.com/login/oauth/authorize?client_id=${github_client_id}&state=${state}&scope=user:email`, ); } + +let channelModels = undefined; +export async function loadChannelModels() { + const res = await API.get('/api/models'); + const { success, data } = res.data; + if (!success) { + return; + } + channelModels = data; + localStorage.setItem('channel_models', JSON.stringify(data)); +} + +export function getChannelModels(type) { + if (channelModels !== undefined && type in channelModels) { + if (!channelModels[type]) { + return []; + } + return channelModels[type]; + } + let models = localStorage.getItem('channel_models'); + if (!models) { + return []; + } + channelModels = JSON.parse(models); + if (type in channelModels) { + return channelModels[type]; + } + return []; +} diff --git a/web/src/constants/channel.constants.js b/web/src/constants/channel.constants.js index c6f0899..94b2fc6 100644 --- a/web/src/constants/channel.constants.js +++ b/web/src/constants/channel.constants.js @@ -86,13 +86,13 @@ export const CHANNEL_OPTIONS = [ label: '智谱 ChatGLM', }, { - key: 16, + key: 26, text: '智谱 GLM-4V', value: 26, color: 'purple', label: '智谱 GLM-4V', }, - { key: 16, text: 'Moonshot', value: 25, color: 'green', label: 'Moonshot' }, + { key: 25, text: 'Moonshot', value: 25, color: 'green', label: 'Moonshot' }, { key: 19, text: '360 智脑', value: 19, color: 'blue', label: '360 智脑' }, { key: 23, text: '腾讯混元', value: 23, color: 'teal', label: '腾讯混元' }, { key: 31, text: '零一万物', value: 31, color: 'green', label: '零一万物' }, diff --git a/web/src/pages/Channel/EditChannel.js b/web/src/pages/Channel/EditChannel.js index cc8707d..79e54b4 100644 --- a/web/src/pages/Channel/EditChannel.js +++ b/web/src/pages/Channel/EditChannel.js @@ -23,6 +23,7 @@ import { Banner, } from '@douyinfe/semi-ui'; import { Divider } from 'semantic-ui-react'; +import { getChannelModels, loadChannelModels } from '../../components/utils.js'; const MODEL_MAPPING_EXAMPLE = { 'gpt-3.5-turbo-0301': 'gpt-3.5-turbo', @@ -87,97 +88,9 @@ const EditChannel = (props) => { const [customModel, setCustomModel] = useState(''); const handleInputChange = (name, value) => { setInputs((inputs) => ({ ...inputs, [name]: value })); - if (name === 'type' && inputs.models.length === 0) { + if (name === 'type') { let localModels = []; switch (value) { - case 33: - case 14: - localModels = [ - 'claude-instant-1.2', - 'claude-2', - 'claude-2.0', - 'claude-2.1', - 'claude-3-opus-20240229', - 'claude-3-sonnet-20240229', - 'claude-3-haiku-20240307', - ]; - break; - case 11: - localModels = ['PaLM-2']; - break; - case 15: - localModels = [ - 'ERNIE-Bot', - 'ERNIE-Bot-turbo', - 'ERNIE-Bot-4', - 'Embedding-V1', - ]; - break; - case 17: - localModels = [ - 'qwen-turbo', - 'qwen-plus', - 'qwen-max', - 'qwen-max-longcontext', - 'text-embedding-v1', - ]; - break; - case 16: - localModels = ['chatglm_pro', 'chatglm_std', 'chatglm_lite']; - break; - case 18: - localModels = [ - 'SparkDesk', - 'SparkDesk-v1.1', - 'SparkDesk-v2.1', - 'SparkDesk-v3.1', - 'SparkDesk-v3.5', - ]; - break; - case 19: - localModels = [ - '360GPT_S2_V9', - 'embedding-bert-512-v1', - 'embedding_s1_v1', - 'semantic_similarity_s1_v1', - ]; - break; - case 23: - localModels = ['hunyuan']; - break; - case 24: - localModels = [ - 'gemini-1.0-pro-001', - 'gemini-1.0-pro-vision-001', - 'gemini-1.5-pro', - 'gemini-1.5-pro-latest', - 'gemini-pro', - 'gemini-pro-vision', - ]; - break; - case 34: - localModels = [ - 'command-r', - 'command-r-plus', - 'command-light', - 'command-light-nightly', - 'command', - 'command-nightly', - ]; - break; - case 25: - localModels = [ - 'moonshot-v1-8k', - 'moonshot-v1-32k', - 'moonshot-v1-128k', - ]; - break; - case 26: - localModels = ['glm-4', 'glm-4v', 'glm-3-turbo']; - break; - case 31: - localModels = ['yi-34b-chat-0205', 'yi-34b-chat-200k', 'yi-vl-plus']; - break; case 2: localModels = [ 'mj_imagine', @@ -207,8 +120,14 @@ const EditChannel = (props) => { 'mj_pan', ]; break; + default: + localModels = getChannelModels(value); + break; } - setInputs((inputs) => ({ ...inputs, models: localModels })); + if (inputs.models.length === 0) { + setInputs((inputs) => ({ ...inputs, models: localModels })); + } + setBasicModels(localModels); } //setAutoBan }; @@ -244,6 +163,7 @@ const EditChannel = (props) => { } else { setAutoBan(true); } + setBasicModels(getChannelModels(data.type)); // console.log(data); } else { showError(message); @@ -312,6 +232,9 @@ const EditChannel = (props) => { loadChannel().then(() => {}); } else { setInputs(originInputs); + let localModels = getChannelModels(inputs.type); + setBasicModels(localModels); + setInputs((inputs) => ({ ...inputs, models: localModels })); } }, [props.editingChannel.id]); @@ -596,7 +519,7 @@ const EditChannel = (props) => { handleInputChange('models', basicModels); }} > - 填入基础模型 + 填入相关模型