Merge remote-tracking branch 'origin/alpha' into alpha

This commit is contained in:
Apple\Apple
2025-06-13 01:34:26 +08:00
13 changed files with 346 additions and 159 deletions

View File

@@ -166,7 +166,7 @@ func testChannel(channel *model.Channel, testModel string) (err error, openAIErr
milliseconds := tok.Sub(tik).Milliseconds()
consumedTime := float64(milliseconds) / 1000.0
other := service.GenerateTextOtherInfo(c, info, priceData.ModelRatio, priceData.GroupRatio, priceData.CompletionRatio,
usage.PromptTokensDetails.CachedTokens, priceData.CacheRatio, priceData.ModelPrice)
usage.PromptTokensDetails.CachedTokens, priceData.CacheRatio, priceData.ModelPrice, priceData.UserGroupRatio)
model.RecordConsumeLog(c, 1, channel.Id, usage.PromptTokens, usage.CompletionTokens, info.OriginModelName, "模型测试",
quota, "模型测试", 0, quota, int(consumedTime), false, info.Group, other)
common.SysLog(fmt.Sprintf("testing channel #%d, response: \n%s", channel.Id, string(respBody)))

View File

@@ -1,10 +1,11 @@
package controller
import (
"github.com/gin-gonic/gin"
"one-api/model"
"one-api/setting"
"one-api/setting/operation_setting"
"github.com/gin-gonic/gin"
)
func GetPricing(c *gin.Context) {
@@ -20,6 +21,12 @@ func GetPricing(c *gin.Context) {
user, err := model.GetUserCache(userId.(int))
if err == nil {
group = user.Group
for g := range groupRatio {
ratio, ok := setting.GetGroupGroupRatio(group, g)
if ok {
groupRatio[g] = ratio
}
}
}
}

View File

@@ -98,6 +98,7 @@ func InitOptionMap() {
common.OptionMap["ModelPrice"] = operation_setting.ModelPrice2JSONString()
common.OptionMap["CacheRatio"] = operation_setting.CacheRatio2JSONString()
common.OptionMap["GroupRatio"] = setting.GroupRatio2JSONString()
common.OptionMap["GroupGroupRatio"] = setting.GroupGroupRatio2JSONString()
common.OptionMap["UserUsableGroups"] = setting.UserUsableGroups2JSONString()
common.OptionMap["CompletionRatio"] = operation_setting.CompletionRatio2JSONString()
common.OptionMap["TopUpLink"] = common.TopUpLink
@@ -357,6 +358,8 @@ func updateOptionMap(key string, value string) (err error) {
err = operation_setting.UpdateModelRatioByJSONString(value)
case "GroupRatio":
err = setting.UpdateGroupRatioByJSONString(value)
case "GroupGroupRatio":
err = setting.UpdateGroupGroupRatioByJSONString(value)
case "UserUsableGroups":
err = setting.UpdateUserUsableGroupsByJSONString(value)
case "CompletionRatio":

View File

@@ -61,6 +61,7 @@ type RelayInfo struct {
TokenKey string
UserId int
Group string
UserGroup string
TokenUnlimited bool
StartTime time.Time
FirstResponseTime time.Time
@@ -204,6 +205,7 @@ func GenRelayInfo(c *gin.Context) *RelayInfo {
TokenKey: tokenKey,
UserId: userId,
Group: group,
UserGroup: c.GetString(constant.ContextKeyUserGroup),
TokenUnlimited: tokenUnlimited,
StartTime: startTime,
FirstResponseTime: startTime.Add(-time.Second),

View File

@@ -2,12 +2,13 @@ package helper
import (
"fmt"
"github.com/gin-gonic/gin"
"one-api/common"
constant2 "one-api/constant"
relaycommon "one-api/relay/common"
"one-api/setting"
"one-api/setting/operation_setting"
"github.com/gin-gonic/gin"
)
type PriceData struct {
@@ -18,6 +19,7 @@ type PriceData struct {
CacheCreationRatio float64
ImageRatio float64
GroupRatio float64
UserGroupRatio float64
UsePrice bool
ShouldPreConsumedQuota int
}
@@ -29,6 +31,10 @@ func (p PriceData) ToSetting() string {
func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens int, maxTokens int) (PriceData, error) {
modelPrice, usePrice := operation_setting.GetModelPrice(info.OriginModelName, false)
groupRatio := setting.GetGroupRatio(info.Group)
userGroupRatio, ok := setting.GetGroupGroupRatio(info.UserGroup, info.Group)
if ok {
groupRatio = userGroupRatio
}
var preConsumedQuota int
var modelRatio float64
var completionRatio float64
@@ -69,6 +75,7 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens
ModelRatio: modelRatio,
CompletionRatio: completionRatio,
GroupRatio: groupRatio,
UserGroupRatio: userGroupRatio,
UsePrice: usePrice,
CacheRatio: cacheRatio,
ImageRatio: imageRatio,

View File

@@ -363,6 +363,7 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
modelRatio := priceData.ModelRatio
groupRatio := priceData.GroupRatio
modelPrice := priceData.ModelPrice
userGroupRatio := priceData.UserGroupRatio
// Convert values to decimal for precise calculation
dPromptTokens := decimal.NewFromInt(int64(promptTokens))
@@ -510,7 +511,7 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
if extraContent != "" {
logContent += ", " + extraContent
}
other := service.GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, cacheTokens, cacheRatio, modelPrice)
other := service.GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, cacheTokens, cacheRatio, modelPrice, userGroupRatio)
if imageTokens != 0 {
other["image"] = true
other["image_ratio"] = imageRatio

View File

@@ -8,7 +8,7 @@ import (
)
func GenerateTextOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, modelRatio, groupRatio, completionRatio float64,
cacheTokens int, cacheRatio float64, modelPrice float64) map[string]interface{} {
cacheTokens int, cacheRatio float64, modelPrice float64, userGroupRatio float64) map[string]interface{} {
other := make(map[string]interface{})
other["model_ratio"] = modelRatio
other["group_ratio"] = groupRatio
@@ -16,6 +16,7 @@ func GenerateTextOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, m
other["cache_tokens"] = cacheTokens
other["cache_ratio"] = cacheRatio
other["model_price"] = modelPrice
other["user_group_ratio"] = userGroupRatio
other["frt"] = float64(relayInfo.FirstResponseTime.UnixMilli() - relayInfo.StartTime.UnixMilli())
if relayInfo.ReasoningEffort != "" {
other["reasoning_effort"] = relayInfo.ReasoningEffort
@@ -30,8 +31,8 @@ func GenerateTextOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, m
return other
}
func GenerateWssOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage *dto.RealtimeUsage, modelRatio, groupRatio, completionRatio, audioRatio, audioCompletionRatio, modelPrice float64) map[string]interface{} {
info := GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, 0, 0.0, modelPrice)
func GenerateWssOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage *dto.RealtimeUsage, modelRatio, groupRatio, completionRatio, audioRatio, audioCompletionRatio, modelPrice, userGroupRatio float64) map[string]interface{} {
info := GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, 0, 0.0, modelPrice, userGroupRatio)
info["ws"] = true
info["audio_input"] = usage.InputTokenDetails.AudioTokens
info["audio_output"] = usage.OutputTokenDetails.AudioTokens
@@ -42,8 +43,8 @@ func GenerateWssOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, us
return info
}
func GenerateAudioOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage *dto.Usage, modelRatio, groupRatio, completionRatio, audioRatio, audioCompletionRatio, modelPrice float64) map[string]interface{} {
info := GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, 0, 0.0, modelPrice)
func GenerateAudioOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage *dto.Usage, modelRatio, groupRatio, completionRatio, audioRatio, audioCompletionRatio, modelPrice, userGroupRatio float64) map[string]interface{} {
info := GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, 0, 0.0, modelPrice, userGroupRatio)
info["audio"] = true
info["audio_input"] = usage.PromptTokensDetails.AudioTokens
info["audio_output"] = usage.CompletionTokenDetails.AudioTokens
@@ -55,8 +56,8 @@ func GenerateAudioOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
}
func GenerateClaudeOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, modelRatio, groupRatio, completionRatio float64,
cacheTokens int, cacheRatio float64, cacheCreationTokens int, cacheCreationRatio float64, modelPrice float64) map[string]interface{} {
info := GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, cacheTokens, cacheRatio, modelPrice)
cacheTokens int, cacheRatio float64, cacheCreationTokens int, cacheCreationRatio float64, modelPrice float64, userGroupRatio float64) map[string]interface{} {
info := GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, cacheTokens, cacheRatio, modelPrice, userGroupRatio)
info["claude"] = true
info["cache_creation_tokens"] = cacheCreationTokens
info["cache_creation_ratio"] = cacheCreationRatio

View File

@@ -94,6 +94,10 @@ func PreWssConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usag
audioInputTokens := usage.InputTokenDetails.AudioTokens
audioOutTokens := usage.OutputTokenDetails.AudioTokens
groupRatio := setting.GetGroupRatio(relayInfo.Group)
userGroupRatio, ok := setting.GetGroupGroupRatio(relayInfo.UserGroup, relayInfo.Group)
if ok {
groupRatio = userGroupRatio
}
modelRatio, _ := operation_setting.GetModelRatio(modelName)
quotaInfo := QuotaInfo{
@@ -145,6 +149,11 @@ func PostWssConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, mod
audioRatio := decimal.NewFromFloat(operation_setting.GetAudioRatio(relayInfo.OriginModelName))
audioCompletionRatio := decimal.NewFromFloat(operation_setting.GetAudioCompletionRatio(modelName))
actualGroupRatio := groupRatio
userGroupRatio, ok := setting.GetGroupGroupRatio(relayInfo.UserGroup, relayInfo.Group)
if ok {
actualGroupRatio = userGroupRatio
}
quotaInfo := QuotaInfo{
InputDetails: TokenDetails{
TextTokens: textInputTokens,
@@ -157,7 +166,7 @@ func PostWssConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, mod
ModelName: modelName,
UsePrice: usePrice,
ModelRatio: modelRatio,
GroupRatio: groupRatio,
GroupRatio: actualGroupRatio,
}
quota := calculateAudioQuota(quotaInfo)
@@ -189,7 +198,7 @@ func PostWssConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, mod
logContent += ", " + extraContent
}
other := GenerateWssOtherInfo(ctx, relayInfo, usage, modelRatio, groupRatio,
completionRatio.InexactFloat64(), audioRatio.InexactFloat64(), audioCompletionRatio.InexactFloat64(), modelPrice)
completionRatio.InexactFloat64(), audioRatio.InexactFloat64(), audioCompletionRatio.InexactFloat64(), modelPrice, userGroupRatio)
model.RecordConsumeLog(ctx, relayInfo.UserId, relayInfo.ChannelId, usage.InputTokens, usage.OutputTokens, logModel,
tokenName, quota, logContent, relayInfo.TokenId, userQuota, int(useTimeSeconds), relayInfo.IsStream, relayInfo.Group, other)
}
@@ -207,7 +216,7 @@ func PostClaudeConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
modelRatio := priceData.ModelRatio
groupRatio := priceData.GroupRatio
modelPrice := priceData.ModelPrice
userGroupRatio := priceData.UserGroupRatio
cacheRatio := priceData.CacheRatio
cacheTokens := usage.PromptTokensDetails.CachedTokens
@@ -256,7 +265,7 @@ func PostClaudeConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
}
other := GenerateClaudeOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio,
cacheTokens, cacheRatio, cacheCreationTokens, cacheCreationRatio, modelPrice)
cacheTokens, cacheRatio, cacheCreationTokens, cacheCreationRatio, modelPrice, userGroupRatio)
model.RecordConsumeLog(ctx, relayInfo.UserId, relayInfo.ChannelId, promptTokens, completionTokens, modelName,
tokenName, quota, logContent, relayInfo.TokenId, userQuota, int(useTimeSeconds), relayInfo.IsStream, relayInfo.Group, other)
}
@@ -281,6 +290,12 @@ func PostAudioConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
modelPrice := priceData.ModelPrice
usePrice := priceData.UsePrice
actualGroupRatio := groupRatio
userGroupRatio, ok := setting.GetGroupGroupRatio(relayInfo.UserGroup, relayInfo.Group)
if ok {
actualGroupRatio = userGroupRatio
}
quotaInfo := QuotaInfo{
InputDetails: TokenDetails{
TextTokens: textInputTokens,
@@ -293,7 +308,7 @@ func PostAudioConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
ModelName: relayInfo.OriginModelName,
UsePrice: usePrice,
ModelRatio: modelRatio,
GroupRatio: groupRatio,
GroupRatio: actualGroupRatio,
}
quota := calculateAudioQuota(quotaInfo)
@@ -333,7 +348,7 @@ func PostAudioConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
logContent += ", " + extraContent
}
other := GenerateAudioOtherInfo(ctx, relayInfo, usage, modelRatio, groupRatio,
completionRatio.InexactFloat64(), audioRatio.InexactFloat64(), audioCompletionRatio.InexactFloat64(), modelPrice)
completionRatio.InexactFloat64(), audioRatio.InexactFloat64(), audioCompletionRatio.InexactFloat64(), modelPrice, userGroupRatio)
model.RecordConsumeLog(ctx, relayInfo.UserId, relayInfo.ChannelId, usage.PromptTokens, usage.CompletionTokens, logModel,
tokenName, quota, logContent, relayInfo.TokenId, userQuota, int(useTimeSeconds), relayInfo.IsStream, relayInfo.Group, other)
}

View File

@@ -14,6 +14,15 @@ var groupRatio = map[string]float64{
}
var groupRatioMutex sync.RWMutex
var (
GroupGroupRatio = map[string]map[string]float64{
"vip": {
"edit_this": 0.9,
},
}
groupGroupRatioMutex sync.RWMutex
)
func GetGroupRatioCopy() map[string]float64 {
groupRatioMutex.RLock()
defer groupRatioMutex.RUnlock()
@@ -64,6 +73,40 @@ func GetGroupRatio(name string) float64 {
return ratio
}
func GetGroupGroupRatio(group, name string) (float64, bool) {
groupGroupRatioMutex.RLock()
defer groupGroupRatioMutex.RUnlock()
gp, ok := GroupGroupRatio[group]
if !ok {
return -1, false
}
ratio, ok := gp[name]
if !ok {
return -1, false
}
return ratio, true
}
func GroupGroupRatio2JSONString() string {
groupGroupRatioMutex.RLock()
defer groupGroupRatioMutex.RUnlock()
jsonBytes, err := json.Marshal(GroupGroupRatio)
if err != nil {
common.SysError("error marshalling group-group ratio: " + err.Error())
}
return string(jsonBytes)
}
func UpdateGroupGroupRatioByJSONString(jsonStr string) error {
groupGroupRatioMutex.Lock()
defer groupGroupRatioMutex.Unlock()
GroupGroupRatio = make(map[string]map[string]float64)
return json.Unmarshal([]byte(jsonStr), &GroupGroupRatio)
}
func CheckGroupRatio(jsonStr string) error {
checkGroupRatio := make(map[string]float64)
err := json.Unmarshal([]byte(jsonStr), &checkGroupRatio)

View File

@@ -30,6 +30,7 @@ const OperationSetting = () => {
CompletionRatio: '',
ModelPrice: '',
GroupRatio: '',
GroupGroupRatio: '',
UserUsableGroups: '',
TopUpLink: '',
'general_setting.docs_link': '',
@@ -74,6 +75,7 @@ const OperationSetting = () => {
if (
item.key === 'ModelRatio' ||
item.key === 'GroupRatio' ||
item.key === 'GroupGroupRatio' ||
item.key === 'UserUsableGroups' ||
item.key === 'CompletionRatio' ||
item.key === 'ModelPrice' ||

View File

@@ -20,7 +20,7 @@ import {
renderQuota,
stringToColor,
getLogOther,
renderModelTag
renderModelTag,
} from '../../helpers';
import {
@@ -39,11 +39,11 @@ import {
Card,
Typography,
Divider,
Form
Form,
} from '@douyinfe/semi-ui';
import {
IllustrationNoResult,
IllustrationNoResultDark
IllustrationNoResultDark,
} from '@douyinfe/semi-illustrations';
import { ITEMS_PER_PAGE } from '../../constants';
import Paragraph from '@douyinfe/semi-ui/lib/es/typography/paragraph';
@@ -192,7 +192,7 @@ const LogsTable = () => {
if (!modelMapped) {
return renderModelTag(record.model_name, {
onClick: (event) => {
copyText(event, record.model_name).then((r) => { });
copyText(event, record.model_name).then((r) => {});
},
});
} else {
@@ -209,7 +209,7 @@ const LogsTable = () => {
</Text>
{renderModelTag(record.model_name, {
onClick: (event) => {
copyText(event, record.model_name).then((r) => { });
copyText(event, record.model_name).then((r) => {});
},
})}
</div>
@@ -220,7 +220,7 @@ const LogsTable = () => {
{renderModelTag(other.upstream_model_name, {
onClick: (event) => {
copyText(event, other.upstream_model_name).then(
(r) => { },
(r) => {},
);
},
})}
@@ -231,7 +231,7 @@ const LogsTable = () => {
>
{renderModelTag(record.model_name, {
onClick: (event) => {
copyText(event, record.model_name).then((r) => { });
copyText(event, record.model_name).then((r) => {});
},
suffixIcon: (
<Route
@@ -636,21 +636,23 @@ const LogsTable = () => {
}
let content = other?.claude
? renderClaudeModelPriceSimple(
other.model_ratio,
other.model_price,
other.group_ratio,
other.cache_tokens || 0,
other.cache_ratio || 1.0,
other.cache_creation_tokens || 0,
other.cache_creation_ratio || 1.0,
)
other.model_ratio,
other.model_price,
other.group_ratio,
other?.user_group_ratio,
other.cache_tokens || 0,
other.cache_ratio || 1.0,
other.cache_creation_tokens || 0,
other.cache_creation_ratio || 1.0,
)
: renderModelPriceSimple(
other.model_ratio,
other.model_price,
other.group_ratio,
other.cache_tokens || 0,
other.cache_ratio || 1.0,
);
other.model_ratio,
other.model_price,
other.group_ratio,
other?.user_group_ratio,
other.cache_tokens || 0,
other.cache_ratio || 1.0,
);
return (
<Paragraph
ellipsis={{
@@ -780,7 +782,7 @@ const LogsTable = () => {
group: '',
dateRange: [
timestamp2string(getTodayStartTimestamp()),
timestamp2string(now.getTime() / 1000 + 3600)
timestamp2string(now.getTime() / 1000 + 3600),
],
logType: '0',
};
@@ -801,7 +803,11 @@ const LogsTable = () => {
let start_timestamp = timestamp2string(getTodayStartTimestamp());
let end_timestamp = timestamp2string(now.getTime() / 1000 + 3600);
if (formValues.dateRange && Array.isArray(formValues.dateRange) && formValues.dateRange.length === 2) {
if (
formValues.dateRange &&
Array.isArray(formValues.dateRange) &&
formValues.dateRange.length === 2
) {
start_timestamp = formValues.dateRange[0];
end_timestamp = formValues.dateRange[1];
}
@@ -979,27 +985,27 @@ const LogsTable = () => {
key: t('日志详情'),
value: other?.claude
? renderClaudeLogContent(
other?.model_ratio,
other.completion_ratio,
other.model_price,
other.group_ratio,
other.cache_ratio || 1.0,
other.cache_creation_ratio || 1.0,
)
other?.model_ratio,
other.completion_ratio,
other.model_price,
other.group_ratio,
other?.user_group_ratio,
other.cache_ratio || 1.0,
other.cache_creation_ratio || 1.0,
)
: renderLogContent(
other?.model_ratio,
other.completion_ratio,
other.model_price,
other.group_ratio,
other?.user_group_ratio,
false,
1.0,
undefined,
other.web_search || false,
other.web_search_call_count || 0,
other.file_search || false,
other.file_search_call_count || 0,
),
other?.model_ratio,
other.completion_ratio,
other.model_price,
other.group_ratio,
other?.user_group_ratio,
false,
1.0,
other.web_search || false,
other.web_search_call_count || 0,
other.file_search || false,
other.file_search_call_count || 0,
),
});
}
if (logs[i].type === 2) {
@@ -1030,6 +1036,7 @@ const LogsTable = () => {
other?.audio_ratio,
other?.audio_completion_ratio,
other?.group_ratio,
other?.user_group_ratio,
other?.cache_tokens || 0,
other?.cache_ratio || 1.0,
);
@@ -1041,6 +1048,7 @@ const LogsTable = () => {
other.model_price,
other.completion_ratio,
other.group_ratio,
other?.user_group_ratio,
other.cache_tokens || 0,
other.cache_ratio || 1.0,
other.cache_creation_tokens || 0,
@@ -1054,6 +1062,7 @@ const LogsTable = () => {
other?.model_price,
other?.completion_ratio,
other?.group_ratio,
other?.user_group_ratio,
other?.cache_tokens || 0,
other?.cache_ratio || 1.0,
other?.image || false,
@@ -1104,7 +1113,12 @@ const LogsTable = () => {
} = getFormValues();
// 使用传入的 logType 或者表单中的 logType 或者状态中的 logType
const currentLogType = customLogType !== null ? customLogType : formLogType !== undefined ? formLogType : logType;
const currentLogType =
customLogType !== null
? customLogType
: formLogType !== undefined
? formLogType
: logType;
let localStartTimestamp = Date.parse(start_timestamp) / 1000;
let localEndTimestamp = Date.parse(end_timestamp) / 1000;
@@ -1131,7 +1145,7 @@ const LogsTable = () => {
const handlePageChange = (page) => {
setActivePage(page);
loadLogs(page, pageSize).then((r) => { }); // 不传入logType让其从表单获取最新值
loadLogs(page, pageSize).then((r) => {}); // 不传入logType让其从表单获取最新值
};
const handlePageSizeChange = async (size) => {
@@ -1246,9 +1260,9 @@ const LogsTable = () => {
getFormApi={(api) => setFormApi(api)}
onSubmit={refresh}
allowEmpty={true}
autoComplete="off"
layout="vertical"
trigger="change"
autoComplete='off'
layout='vertical'
trigger='change'
stopValidateWithError={false}
>
<div className='flex flex-col gap-4'>
@@ -1332,12 +1346,24 @@ const LogsTable = () => {
}, 0);
}}
>
<Form.Select.Option value='0'>{t('全部')}</Form.Select.Option>
<Form.Select.Option value='1'>{t('充值')}</Form.Select.Option>
<Form.Select.Option value='2'>{t('消费')}</Form.Select.Option>
<Form.Select.Option value='3'>{t('管理')}</Form.Select.Option>
<Form.Select.Option value='4'>{t('系统')}</Form.Select.Option>
<Form.Select.Option value='5'>{t('错误')}</Form.Select.Option>
<Form.Select.Option value='0'>
{t('全部')}
</Form.Select.Option>
<Form.Select.Option value='1'>
{t('充值')}
</Form.Select.Option>
<Form.Select.Option value='2'>
{t('消费')}
</Form.Select.Option>
<Form.Select.Option value='3'>
{t('管理')}
</Form.Select.Option>
<Form.Select.Option value='4'>
{t('系统')}
</Form.Select.Option>
<Form.Select.Option value='5'>
{t('错误')}
</Form.Select.Option>
</Form.Select>
</div>
@@ -1389,7 +1415,8 @@ const LogsTable = () => {
{...(hasExpandableRows() && {
expandedRowRender: expandRowRender,
expandRowByClick: true,
rowExpandable: (record) => expandData[record.key] && expandData[record.key].length > 0
rowExpandable: (record) =>
expandData[record.key] && expandData[record.key].length > 0,
})}
dataSource={logs}
rowKey='key'
@@ -1399,8 +1426,12 @@ const LogsTable = () => {
size='middle'
empty={
<Empty
image={<IllustrationNoResult style={{ width: 150, height: 150 }} />}
darkModeImage={<IllustrationNoResultDark style={{ width: 150, height: 150 }} />}
image={
<IllustrationNoResult style={{ width: 150, height: 150 }} />
}
darkModeImage={
<IllustrationNoResultDark style={{ width: 150, height: 150 }} />
}
description={t('搜索无结果')}
style={{ padding: 30 }}
/>

View File

@@ -30,7 +30,7 @@ import {
Dify,
Coze,
SiliconCloud,
FastGPT
FastGPT,
} from '@lobehub/icons';
import {
@@ -46,7 +46,7 @@ import {
Gift,
User,
Settings,
CircleUser
CircleUser,
} from 'lucide-react';
// 侧边栏图标颜色映射
@@ -315,7 +315,6 @@ export const getModelCategories = (() => {
};
})();
/**
* 根据渠道类型返回对应的厂商图标
* @param {number} channelType - 渠道类型值
@@ -868,6 +867,30 @@ export function renderQuota(quota, digits = 2) {
return renderNumber(quota);
}
function isValidGroupRatio(ratio) {
return Number.isFinite(ratio) && ratio !== -1;
}
/**
* Helper function to get effective ratio and label
* @param {number} groupRatio - The default group ratio
* @param {number} user_group_ratio - The user-specific group ratio
* @returns {Object} - Object containing { ratio, label, useUserGroupRatio }
*/
function getEffectiveRatio(groupRatio, user_group_ratio) {
const useUserGroupRatio = isValidGroupRatio(user_group_ratio);
const ratioLabel = useUserGroupRatio
? i18next.t('专属倍率')
: i18next.t('分组倍率');
const effectiveRatio = useUserGroupRatio ? user_group_ratio : groupRatio;
return {
ratio: effectiveRatio,
label: ratioLabel,
useUserGroupRatio: useUserGroupRatio
};
}
export function renderModelPrice(
inputTokens,
completionTokens,
@@ -875,6 +898,7 @@ export function renderModelPrice(
modelPrice = -1,
completionRatio,
groupRatio,
user_group_ratio,
cacheTokens = 0,
cacheRatio = 1.0,
image = false,
@@ -890,13 +914,17 @@ export function renderModelPrice(
audioInputTokens = 0,
audioInputPrice = 0,
) {
const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(groupRatio, user_group_ratio);
groupRatio = effectiveGroupRatio;
if (modelPrice !== -1) {
return i18next.t(
'模型价格:${{price}} * 分组倍率{{ratio}} = ${{total}}',
'模型价格:${{price}} * {{ratioType}}{{ratio}} = ${{total}}',
{
price: modelPrice,
ratio: groupRatio,
total: modelPrice * groupRatio,
ratioType: ratioLabel,
},
);
} else {
@@ -1033,11 +1061,12 @@ export function renderModelPrice(
// 构建输出部分描述
const outputDesc = i18next.t(
'输出 {{completion}} tokens / 1M tokens * ${{compPrice}}) * 分组倍率 {{ratio}}',
'输出 {{completion}} tokens / 1M tokens * ${{compPrice}}) * {{ratioType}} {{ratio}}',
{
completion: completionTokens,
compPrice: completionRatioPrice,
ratio: groupRatio,
ratioType: ratioLabel,
},
);
@@ -1045,23 +1074,25 @@ export function renderModelPrice(
const extraServices = [
webSearch && webSearchCallCount > 0
? i18next.t(
' + Web搜索 {{count}}次 / 1K 次 * ${{price}} * 分组倍率 {{ratio}}',
{
count: webSearchCallCount,
price: webSearchPrice,
ratio: groupRatio,
},
)
' + Web搜索 {{count}}次 / 1K 次 * ${{price}} * {{ratioType}} {{ratio}}',
{
count: webSearchCallCount,
price: webSearchPrice,
ratio: groupRatio,
ratioType: ratioLabel,
},
)
: '',
fileSearch && fileSearchCallCount > 0
? i18next.t(
' + 文件搜索 {{count}}次 / 1K 次 * ${{price}} * 分组倍率 {{ratio}}',
{
count: fileSearchCallCount,
price: fileSearchPrice,
ratio: groupRatio,
},
)
' + 文件搜索 {{count}}次 / 1K 次 * ${{price}} * {{ratioType}} {{ratio}}',
{
count: fileSearchCallCount,
price: fileSearchPrice,
ratio: groupRatio,
ratioType: ratioLabel,
},
)
: '',
].join('');
@@ -1091,16 +1122,12 @@ export function renderLogContent(
user_group_ratio,
image = false,
imageRatio = 1.0,
useUserGroupRatio = undefined,
webSearch = false,
webSearchCallCount = 0,
fileSearch = false,
fileSearchCallCount = 0,
) {
const ratioLabel = useUserGroupRatio
? i18next.t('专属倍率')
: i18next.t('分组倍率');
const ratio = useUserGroupRatio ? user_group_ratio : groupRatio;
const { ratio, label: ratioLabel, useUserGroupRatio: useUserGroupRatio } = getEffectiveRatio(groupRatio, user_group_ratio);
if (modelPrice !== -1) {
return i18next.t('模型价格 ${{price}}{{ratioType}} {{ratio}}', {
@@ -1149,14 +1176,18 @@ export function renderModelPriceSimple(
modelRatio,
modelPrice = -1,
groupRatio,
user_group_ratio,
cacheTokens = 0,
cacheRatio = 1.0,
image = false,
imageRatio = 1.0,
) {
const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(groupRatio, user_group_ratio);
groupRatio = effectiveGroupRatio;
if (modelPrice !== -1) {
return i18next.t('价格:${{price}} * 分组{{ratio}}', {
return i18next.t('价格:${{price}} * {{ratioType}}{{ratio}}', {
price: modelPrice,
ratioType: ratioLabel,
ratio: groupRatio,
});
} else {
@@ -1191,8 +1222,9 @@ export function renderModelPriceSimple(
},
);
} else {
return i18next.t('模型: {{ratio}} * 分组: {{groupRatio}}', {
return i18next.t('模型: {{ratio}} * {{ratioType}}{{groupRatio}}', {
ratio: modelRatio,
ratioType: ratioLabel,
groupRatio: groupRatio,
});
}
@@ -1210,17 +1242,21 @@ export function renderAudioModelPrice(
audioRatio,
audioCompletionRatio,
groupRatio,
user_group_ratio,
cacheTokens = 0,
cacheRatio = 1.0,
) {
const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(groupRatio, user_group_ratio);
groupRatio = effectiveGroupRatio;
// 1 ratio = $0.002 / 1K tokens
if (modelPrice !== -1) {
return i18next.t(
'模型价格:${{price}} * 分组倍率{{ratio}} = ${{total}}',
'模型价格:${{price}} * {{ratioType}}{{ratio}} = ${{total}}',
{
price: modelPrice,
ratio: groupRatio,
total: modelPrice * groupRatio,
ratioType: ratioLabel,
},
);
} else {
@@ -1245,10 +1281,10 @@ export function renderAudioModelPrice(
let audioPrice =
(audioInputTokens / 1000000) * inputRatioPrice * audioRatio * groupRatio +
(audioCompletionTokens / 1000000) *
inputRatioPrice *
audioRatio *
audioCompletionRatio *
groupRatio;
inputRatioPrice *
audioRatio *
audioCompletionRatio *
groupRatio;
let price = textPrice + audioPrice;
return (
<>
@@ -1304,27 +1340,27 @@ export function renderAudioModelPrice(
<p>
{cacheTokens > 0
? i18next.t(
'文字提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}',
{
nonCacheInput: inputTokens - cacheTokens,
cacheInput: cacheTokens,
cachePrice: inputRatioPrice * cacheRatio,
price: inputRatioPrice,
completion: completionTokens,
compPrice: completionRatioPrice,
total: textPrice.toFixed(6),
},
)
'文字提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}',
{
nonCacheInput: inputTokens - cacheTokens,
cacheInput: cacheTokens,
cachePrice: inputRatioPrice * cacheRatio,
price: inputRatioPrice,
completion: completionTokens,
compPrice: completionRatioPrice,
total: textPrice.toFixed(6),
},
)
: i18next.t(
'文字提示 {{input}} tokens / 1M tokens * ${{price}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}',
{
input: inputTokens,
price: inputRatioPrice,
completion: completionTokens,
compPrice: completionRatioPrice,
total: textPrice.toFixed(6),
},
)}
'文字提示 {{input}} tokens / 1M tokens * ${{price}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}',
{
input: inputTokens,
price: inputRatioPrice,
completion: completionTokens,
compPrice: completionRatioPrice,
total: textPrice.toFixed(6),
},
)}
</p>
<p>
{i18next.t(
@@ -1374,12 +1410,14 @@ export function renderClaudeModelPrice(
modelPrice = -1,
completionRatio,
groupRatio,
user_group_ratio,
cacheTokens = 0,
cacheRatio = 1.0,
cacheCreationTokens = 0,
cacheCreationRatio = 1.0,
) {
const ratioLabel = false ? i18next.t('专属倍率') : i18next.t('分组倍率');
const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(groupRatio, user_group_ratio);
groupRatio = effectiveGroupRatio;
if (modelPrice !== -1) {
return i18next.t(
@@ -1461,33 +1499,35 @@ export function renderClaudeModelPrice(
<p>
{cacheTokens > 0 || cacheCreationTokens > 0
? i18next.t(
'提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 缓存创建 {{cacheCreationInput}} tokens / 1M tokens * ${{cacheCreationPrice}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}',
{
nonCacheInput: nonCachedTokens,
cacheInput: cacheTokens,
cacheRatio: cacheRatio,
cacheCreationInput: cacheCreationTokens,
cacheCreationRatio: cacheCreationRatio,
cachePrice: cacheRatioPrice,
cacheCreationPrice: cacheCreationRatioPrice,
price: inputRatioPrice,
completion: completionTokens,
compPrice: completionRatioPrice,
ratio: groupRatio,
total: price.toFixed(6),
},
)
'提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 缓存创建 {{cacheCreationInput}} tokens / 1M tokens * ${{cacheCreationPrice}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * {{ratioType}} {{ratio}} = ${{total}}',
{
nonCacheInput: nonCachedTokens,
cacheInput: cacheTokens,
cacheRatio: cacheRatio,
cacheCreationInput: cacheCreationTokens,
cacheCreationRatio: cacheCreationRatio,
cachePrice: cacheRatioPrice,
cacheCreationPrice: cacheCreationRatioPrice,
price: inputRatioPrice,
completion: completionTokens,
compPrice: completionRatioPrice,
ratio: groupRatio,
ratioType: ratioLabel,
total: price.toFixed(6),
},
)
: i18next.t(
'提示 {{input}} tokens / 1M tokens * ${{price}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}',
{
input: inputTokens,
price: inputRatioPrice,
completion: completionTokens,
compPrice: completionRatioPrice,
ratio: groupRatio,
total: price.toFixed(6),
},
)}
'提示 {{input}} tokens / 1M tokens * ${{price}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * {{ratioType}} {{ratio}} = ${{total}}',
{
input: inputTokens,
price: inputRatioPrice,
completion: completionTokens,
compPrice: completionRatioPrice,
ratio: groupRatio,
ratioType: ratioLabel,
total: price.toFixed(6),
},
)}
</p>
<p>{i18next.t('仅供参考,以实际扣费为准')}</p>
</article>
@@ -1501,10 +1541,12 @@ export function renderClaudeLogContent(
completionRatio,
modelPrice = -1,
groupRatio,
user_group_ratio,
cacheRatio = 1.0,
cacheCreationRatio = 1.0,
) {
const ratioLabel = false ? i18next.t('专属倍率') : i18next.t('分组倍率');
const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(groupRatio, user_group_ratio);
groupRatio = effectiveGroupRatio;
if (modelPrice !== -1) {
return i18next.t('模型价格 ${{price}}{{ratioType}} {{ratio}}', {
@@ -1531,12 +1573,14 @@ export function renderClaudeModelPriceSimple(
modelRatio,
modelPrice = -1,
groupRatio,
user_group_ratio,
cacheTokens = 0,
cacheRatio = 1.0,
cacheCreationTokens = 0,
cacheCreationRatio = 1.0,
) {
const ratioLabel = false ? i18next.t('专属倍率') : i18next.t('分组');
const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(groupRatio, user_group_ratio);
groupRatio = effectiveGroupRatio;
if (modelPrice !== -1) {
return i18next.t('价格:${{price}} * {{ratioType}}{{ratio}}', {

View File

@@ -16,6 +16,7 @@ export default function GroupRatioSettings(props) {
const [inputs, setInputs] = useState({
GroupRatio: '',
UserUsableGroups: '',
GroupGroupRatio: '',
});
const refForm = useRef();
const [inputsRow, setInputsRow] = useState(inputs);
@@ -99,6 +100,9 @@ export default function GroupRatioSettings(props) {
<Form.TextArea
label={t('分组倍率')}
placeholder={t('为一个 JSON 文本,键为分组名称,值为倍率')}
extraText={t(
'分组倍率设置,可以在此处新增分组或修改现有分组的倍率,格式为 JSON 字符串,例如:{"vip": 0.5, "test": 1},表示 vip 分组的倍率为 0.5test 分组的倍率为 1',
)}
field={'GroupRatio'}
autosize={{ minRows: 6, maxRows: 12 }}
trigger='blur'
@@ -120,6 +124,9 @@ export default function GroupRatioSettings(props) {
<Form.TextArea
label={t('用户可选分组')}
placeholder={t('为一个 JSON 文本,键为分组名称,值为分组描述')}
extraText={t(
'用户新建令牌时可选的分组,格式为 JSON 字符串,例如:{"vip": "VIP 用户", "test": "测试"},表示用户可以选择 vip 分组和 test 分组',
)}
field={'UserUsableGroups'}
autosize={{ minRows: 6, maxRows: 12 }}
trigger='blur'
@@ -136,6 +143,30 @@ export default function GroupRatioSettings(props) {
/>
</Col>
</Row>
<Row gutter={16}>
<Col xs={24} sm={16}>
<Form.TextArea
label={t('分组特殊倍率')}
placeholder={t('为一个 JSON 文本')}
extraText={t(
'键为分组名称,值为另一个 JSON 对象,键为分组名称,值为该分组的用户的特殊分组倍率,例如:{"vip": {"default": 0.5, "test": 1}},表示 vip 分组的用户在使用default分组的令牌时倍率为0.5使用test分组时倍率为1',
)}
field={'GroupGroupRatio'}
autosize={{ minRows: 6, maxRows: 12 }}
trigger='blur'
stopValidateWithError
rules={[
{
validator: (rule, value) => verifyJSON(value),
message: t('不是合法的 JSON 字符串'),
},
]}
onChange={(value) =>
setInputs({ ...inputs, GroupGroupRatio: value })
}
/>
</Col>
</Row>
</Form.Section>
</Form>
<Button onClick={onSubmit}>{t('保存分组倍率设置')}</Button>