Compare commits

..

7 Commits

Author SHA1 Message Date
creamlike1024
797c7acd13 fix: 修复multipart表单字段内容复制问题 2025-10-31 20:13:27 +08:00
creamlike1024
f15b85f745 fix(: 修复multipart请求边界设置和文件字段处理问题 2025-10-31 20:06:01 +08:00
creamlike1024
10a473993b refactor(relay): remove IsModelMapped properties 2025-10-31 19:53:46 +08:00
creamlike1024
ff11c92713 Merge branch 'main' into task-model-mapper 2025-10-31 19:49:05 +08:00
creamlike1024
347ad047f9 feat: 保存重定向信息到 task.Properties 2025-10-31 19:45:37 +08:00
creamlike1024
c651727bab fix(adaptor): 修复解析multipart请求时获取boundary的问题 2025-10-31 19:16:55 +08:00
creamlike1024
7fc25a57cf feat(relay): 添加视频模型映射功能支持 2025-10-31 18:58:03 +08:00
22 changed files with 199 additions and 498 deletions

View File

@@ -4,7 +4,6 @@ import (
"embed" "embed"
"io/fs" "io/fs"
"net/http" "net/http"
"os"
"github.com/gin-contrib/static" "github.com/gin-contrib/static"
) )
@@ -15,7 +14,7 @@ type embedFileSystem struct {
http.FileSystem http.FileSystem
} }
func (e *embedFileSystem) Exists(prefix string, path string) bool { func (e embedFileSystem) Exists(prefix string, path string) bool {
_, err := e.Open(path) _, err := e.Open(path)
if err != nil { if err != nil {
return false return false
@@ -23,22 +22,12 @@ func (e *embedFileSystem) Exists(prefix string, path string) bool {
return true return true
} }
func (e *embedFileSystem) Open(name string) (http.File, error) {
if name == "/" {
// This will make sure the index page goes to NoRouter handler,
// which will use the replaced index bytes with analytic codes.
return nil, os.ErrNotExist
}
return e.FileSystem.Open(name)
}
// requested subtree cannot be opened.
func EmbedFolder(fsEmbed embed.FS, targetPath string) static.ServeFileSystem { func EmbedFolder(fsEmbed embed.FS, targetPath string) static.ServeFileSystem {
efs, err := fs.Sub(fsEmbed, targetPath) efs, err := fs.Sub(fsEmbed, targetPath)
if err != nil { if err != nil {
panic(err) panic(err)
} }
return &embedFileSystem{ return embedFileSystem{
FileSystem: http.FS(efs), FileSystem: http.FS(efs),
} }
} }

View File

@@ -510,44 +510,11 @@ func (c *ClaudeResponse) GetClaudeError() *types.ClaudeError {
} }
type ClaudeUsage struct { type ClaudeUsage struct {
InputTokens int `json:"input_tokens"` InputTokens int `json:"input_tokens"`
CacheCreationInputTokens int `json:"cache_creation_input_tokens"` CacheCreationInputTokens int `json:"cache_creation_input_tokens"`
CacheReadInputTokens int `json:"cache_read_input_tokens"` CacheReadInputTokens int `json:"cache_read_input_tokens"`
OutputTokens int `json:"output_tokens"` OutputTokens int `json:"output_tokens"`
CacheCreation *ClaudeCacheCreationUsage `json:"cache_creation,omitempty"` ServerToolUse *ClaudeServerToolUse `json:"server_tool_use,omitempty"`
// claude cache 1h
ClaudeCacheCreation5mTokens int `json:"claude_cache_creation_5_m_tokens"`
ClaudeCacheCreation1hTokens int `json:"claude_cache_creation_1_h_tokens"`
ServerToolUse *ClaudeServerToolUse `json:"server_tool_use,omitempty"`
}
type ClaudeCacheCreationUsage struct {
Ephemeral5mInputTokens int `json:"ephemeral_5m_input_tokens,omitempty"`
Ephemeral1hInputTokens int `json:"ephemeral_1h_input_tokens,omitempty"`
}
func (u *ClaudeUsage) GetCacheCreation5mTokens() int {
if u == nil || u.CacheCreation == nil {
return 0
}
return u.CacheCreation.Ephemeral5mInputTokens
}
func (u *ClaudeUsage) GetCacheCreation1hTokens() int {
if u == nil || u.CacheCreation == nil {
return 0
}
return u.CacheCreation.Ephemeral1hInputTokens
}
func (u *ClaudeUsage) GetCacheCreationTotalTokens() int {
if u == nil {
return 0
}
if u.CacheCreationInputTokens > 0 {
return u.CacheCreationInputTokens
}
return u.GetCacheCreation5mTokens() + u.GetCacheCreation1hTokens()
} }
type ClaudeServerToolUse struct { type ClaudeServerToolUse struct {

View File

@@ -230,11 +230,6 @@ type Usage struct {
InputTokens int `json:"input_tokens"` InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"` OutputTokens int `json:"output_tokens"`
InputTokensDetails *InputTokenDetails `json:"input_tokens_details"` InputTokensDetails *InputTokenDetails `json:"input_tokens_details"`
// claude cache 1h
ClaudeCacheCreation5mTokens int `json:"claude_cache_creation_5_m_tokens"`
ClaudeCacheCreation1hTokens int `json:"claude_cache_creation_1_h_tokens"`
// OpenRouter Params // OpenRouter Params
Cost any `json:"cost,omitempty"` Cost any `json:"cost,omitempty"`
} }

View File

@@ -98,9 +98,9 @@ func oaiFormEdit2AliImageEdit(c *gin.Context, info *relaycommon.RelayInfo, reque
return nil, errors.New("image is required") return nil, errors.New("image is required")
} }
//if len(imageFiles) > 1 { if len(imageFiles) > 1 {
// return nil, errors.New("only one image is supported for qwen edit") return nil, errors.New("only one image is supported for qwen edit")
//} }
// 获取base64编码的图片 // 获取base64编码的图片
var imageBase64s []string var imageBase64s []string

View File

@@ -596,8 +596,6 @@ func FormatClaudeResponseInfo(requestMode int, claudeResponse *dto.ClaudeRespons
claudeInfo.Usage.PromptTokens = claudeResponse.Message.Usage.InputTokens claudeInfo.Usage.PromptTokens = claudeResponse.Message.Usage.InputTokens
claudeInfo.Usage.PromptTokensDetails.CachedTokens = claudeResponse.Message.Usage.CacheReadInputTokens claudeInfo.Usage.PromptTokensDetails.CachedTokens = claudeResponse.Message.Usage.CacheReadInputTokens
claudeInfo.Usage.PromptTokensDetails.CachedCreationTokens = claudeResponse.Message.Usage.CacheCreationInputTokens claudeInfo.Usage.PromptTokensDetails.CachedCreationTokens = claudeResponse.Message.Usage.CacheCreationInputTokens
claudeInfo.Usage.ClaudeCacheCreation5mTokens = claudeResponse.Message.Usage.GetCacheCreation5mTokens()
claudeInfo.Usage.ClaudeCacheCreation1hTokens = claudeResponse.Message.Usage.GetCacheCreation1hTokens()
claudeInfo.Usage.CompletionTokens = claudeResponse.Message.Usage.OutputTokens claudeInfo.Usage.CompletionTokens = claudeResponse.Message.Usage.OutputTokens
} else if claudeResponse.Type == "content_block_delta" { } else if claudeResponse.Type == "content_block_delta" {
if claudeResponse.Delta.Text != nil { if claudeResponse.Delta.Text != nil {
@@ -742,8 +740,6 @@ func HandleClaudeResponseData(c *gin.Context, info *relaycommon.RelayInfo, claud
claudeInfo.Usage.TotalTokens = claudeResponse.Usage.InputTokens + claudeResponse.Usage.OutputTokens claudeInfo.Usage.TotalTokens = claudeResponse.Usage.InputTokens + claudeResponse.Usage.OutputTokens
claudeInfo.Usage.PromptTokensDetails.CachedTokens = claudeResponse.Usage.CacheReadInputTokens claudeInfo.Usage.PromptTokensDetails.CachedTokens = claudeResponse.Usage.CacheReadInputTokens
claudeInfo.Usage.PromptTokensDetails.CachedCreationTokens = claudeResponse.Usage.CacheCreationInputTokens claudeInfo.Usage.PromptTokensDetails.CachedCreationTokens = claudeResponse.Usage.CacheCreationInputTokens
claudeInfo.Usage.ClaudeCacheCreation5mTokens = claudeResponse.Usage.GetCacheCreation5mTokens()
claudeInfo.Usage.ClaudeCacheCreation1hTokens = claudeResponse.Usage.GetCacheCreation1hTokens()
} }
var responseData []byte var responseData []byte
switch info.RelayFormat { switch info.RelayFormat {

View File

@@ -122,10 +122,6 @@ func OaiStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Re
var usage = &dto.Usage{} var usage = &dto.Usage{}
var streamItems []string // store stream items var streamItems []string // store stream items
var lastStreamData string var lastStreamData string
var secondLastStreamData string // 存储倒数第二个stream data用于音频模型
// 检查是否为音频模型
isAudioModel := strings.Contains(strings.ToLower(model), "audio")
helper.StreamScannerHandler(c, resp, info, func(data string) bool { helper.StreamScannerHandler(c, resp, info, func(data string) bool {
if lastStreamData != "" { if lastStreamData != "" {
@@ -135,35 +131,12 @@ func OaiStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Re
} }
} }
if len(data) > 0 { if len(data) > 0 {
// 对音频模型保存倒数第二个stream data
if isAudioModel && lastStreamData != "" {
secondLastStreamData = lastStreamData
}
lastStreamData = data lastStreamData = data
streamItems = append(streamItems, data) streamItems = append(streamItems, data)
} }
return true return true
}) })
// 对音频模型从倒数第二个stream data中提取usage信息
if isAudioModel && secondLastStreamData != "" {
var streamResp struct {
Usage *dto.Usage `json:"usage"`
}
err := json.Unmarshal([]byte(secondLastStreamData), &streamResp)
if err == nil && streamResp.Usage != nil && service.ValidUsage(streamResp.Usage) {
usage = streamResp.Usage
containStreamUsage = true
if common.DebugEnabled {
logger.LogDebug(c, fmt.Sprintf("Audio model usage extracted from second last SSE: PromptTokens=%d, CompletionTokens=%d, TotalTokens=%d, InputTokens=%d, OutputTokens=%d",
usage.PromptTokens, usage.CompletionTokens, usage.TotalTokens,
usage.InputTokens, usage.OutputTokens))
}
}
}
// 处理最后的响应 // 处理最后的响应
shouldSendLastResp := true shouldSendLastResp := true
if err := handleLastResponse(lastStreamData, &responseId, &createAt, &systemFingerprint, &model, &usage, if err := handleLastResponse(lastStreamData, &responseId, &createAt, &systemFingerprint, &model, &usage,

View File

@@ -5,12 +5,10 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"strconv"
"strings" "strings"
"github.com/QuantumNous/new-api/common" "github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/dto" "github.com/QuantumNous/new-api/dto"
"github.com/QuantumNous/new-api/logger"
"github.com/QuantumNous/new-api/model" "github.com/QuantumNous/new-api/model"
"github.com/QuantumNous/new-api/relay/channel" "github.com/QuantumNous/new-api/relay/channel"
relaycommon "github.com/QuantumNous/new-api/relay/common" relaycommon "github.com/QuantumNous/new-api/relay/common"
@@ -110,7 +108,6 @@ type TaskAdaptor struct {
ChannelType int ChannelType int
apiKey string apiKey string
baseURL string baseURL string
aliReq *AliVideoRequest
} }
func (a *TaskAdaptor) Init(info *relaycommon.RelayInfo) { func (a *TaskAdaptor) Init(info *relaycommon.RelayInfo) {
@@ -121,16 +118,6 @@ func (a *TaskAdaptor) Init(info *relaycommon.RelayInfo) {
func (a *TaskAdaptor) ValidateRequestAndSetAction(c *gin.Context, info *relaycommon.RelayInfo) (taskErr *dto.TaskError) { func (a *TaskAdaptor) ValidateRequestAndSetAction(c *gin.Context, info *relaycommon.RelayInfo) (taskErr *dto.TaskError) {
// 阿里通义万相支持 JSON 格式,不使用 multipart // 阿里通义万相支持 JSON 格式,不使用 multipart
var taskReq relaycommon.TaskSubmitReq
if err := common.UnmarshalBodyReusable(c, &taskReq); err != nil {
return service.TaskErrorWrapper(err, "unmarshal_task_request_failed", http.StatusBadRequest)
}
aliReq, err := a.convertToAliRequest(info, taskReq)
if err != nil {
return service.TaskErrorWrapper(err, "convert_to_ali_request_failed", http.StatusInternalServerError)
}
a.aliReq = aliReq
logger.LogJson(c, "ali video request body", aliReq)
return relaycommon.ValidateMultipartDirect(c, info) return relaycommon.ValidateMultipartDirect(c, info)
} }
@@ -147,7 +134,13 @@ func (a *TaskAdaptor) BuildRequestHeader(c *gin.Context, req *http.Request, info
} }
func (a *TaskAdaptor) BuildRequestBody(c *gin.Context, info *relaycommon.RelayInfo) (io.Reader, error) { func (a *TaskAdaptor) BuildRequestBody(c *gin.Context, info *relaycommon.RelayInfo) (io.Reader, error) {
bodyBytes, err := common.Marshal(a.aliReq) var taskReq relaycommon.TaskSubmitReq
if err := common.UnmarshalBodyReusable(c, &taskReq); err != nil {
return nil, errors.Wrap(err, "unmarshal_task_request_failed")
}
aliReq := a.convertToAliRequest(taskReq)
bodyBytes, err := common.Marshal(aliReq)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "marshal_ali_request_failed") return nil, errors.Wrap(err, "marshal_ali_request_failed")
} }
@@ -155,31 +148,7 @@ func (a *TaskAdaptor) BuildRequestBody(c *gin.Context, info *relaycommon.RelayIn
return bytes.NewReader(bodyBytes), nil return bytes.NewReader(bodyBytes), nil
} }
func (a *TaskAdaptor) convertToAliRequest(info *relaycommon.RelayInfo, req relaycommon.TaskSubmitReq) (*AliVideoRequest, error) { func (a *TaskAdaptor) convertToAliRequest(req relaycommon.TaskSubmitReq) *AliVideoRequest {
otherRatios := map[string]map[string]float64{
"wan2.5-i2v-preview": {
"480P": 1,
"720P": 2,
"1080P": 1 / 0.3,
},
"wan2.2-i2v-plus": {
"480P": 1,
"1080P": 0.7 / 0.14,
},
"wan2.2-kf2v-flash": {
"480P": 1,
"720P": 2,
"1080P": 4.8,
},
"wan2.2-i2v-flash": {
"480P": 1,
"720P": 2,
},
"wan2.2-s2v": {
"480P": 1,
"720P": 0.9 / 0.5,
},
}
aliReq := &AliVideoRequest{ aliReq := &AliVideoRequest{
Model: req.Model, Model: req.Model,
Input: AliVideoInput{ Input: AliVideoInput{
@@ -216,13 +185,6 @@ func (a *TaskAdaptor) convertToAliRequest(info *relaycommon.RelayInfo, req relay
// 处理时长 // 处理时长
if req.Duration > 0 { if req.Duration > 0 {
aliReq.Parameters.Duration = req.Duration aliReq.Parameters.Duration = req.Duration
} else if req.Seconds != "" {
seconds, err := strconv.Atoi(req.Seconds)
if err != nil {
return nil, errors.Wrap(err, "convert seconds to int failed")
} else {
aliReq.Parameters.Duration = seconds
}
} else { } else {
aliReq.Parameters.Duration = 5 // 默认5秒 aliReq.Parameters.Duration = 5 // 默认5秒
} }
@@ -230,32 +192,11 @@ func (a *TaskAdaptor) convertToAliRequest(info *relaycommon.RelayInfo, req relay
// 从 metadata 中提取额外参数 // 从 metadata 中提取额外参数
if req.Metadata != nil { if req.Metadata != nil {
if metadataBytes, err := common.Marshal(req.Metadata); err == nil { if metadataBytes, err := common.Marshal(req.Metadata); err == nil {
err = common.Unmarshal(metadataBytes, aliReq) _ = common.Unmarshal(metadataBytes, aliReq)
if err != nil {
return nil, errors.Wrap(err, "unmarshal metadata failed")
}
} else {
return nil, errors.Wrap(err, "marshal metadata failed")
} }
} }
if aliReq.Model != req.Model { return aliReq
return nil, errors.New("can't change model with metadata")
}
info.PriceData.OtherRatios = map[string]float64{
"seconds": float64(aliReq.Parameters.Duration),
}
if otherRatio, ok := otherRatios[req.Model]; ok {
if ratio, ok := otherRatio[aliReq.Parameters.Resolution]; ok {
info.PriceData.OtherRatios[fmt.Sprintf("resolution-%s", aliReq.Parameters.Resolution)] = ratio
}
}
// println(fmt.Sprintf("other ratios: %v", info.PriceData.OtherRatios))
return aliReq, nil
} }
// DoRequest delegates to common helper // DoRequest delegates to common helper

View File

@@ -2,9 +2,13 @@ package sora
import ( import (
"bytes" "bytes"
"encoding/json"
"fmt" "fmt"
"io" "io"
"mime"
"mime/multipart"
"net/http" "net/http"
"strings"
"github.com/QuantumNous/new-api/common" "github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/dto" "github.com/QuantumNous/new-api/dto"
@@ -87,9 +91,107 @@ func (a *TaskAdaptor) BuildRequestBody(c *gin.Context, info *relaycommon.RelayIn
if err != nil { if err != nil {
return nil, errors.Wrap(err, "get_request_body_failed") return nil, errors.Wrap(err, "get_request_body_failed")
} }
// 检查是否需要模型重定向
if !info.IsModelMapped {
// 如果不需要重定向,直接返回原始请求体
return bytes.NewReader(cachedBody), nil
}
contentType := c.Request.Header.Get("Content-Type")
// 处理multipart/form-data请求
if strings.Contains(contentType, "multipart/form-data") {
return buildRequestBodyWithMappedModel(cachedBody, contentType, info.UpstreamModelName)
}
// 处理JSON请求
if strings.Contains(contentType, "application/json") {
var jsonData map[string]interface{}
if err := json.Unmarshal(cachedBody, &jsonData); err != nil {
return nil, errors.Wrap(err, "unmarshal_json_failed")
}
// 替换model字段为映射后的模型名
jsonData["model"] = info.UpstreamModelName
// 重新编码为JSON
newBody, err := json.Marshal(jsonData)
if err != nil {
return nil, errors.Wrap(err, "marshal_json_failed")
}
return bytes.NewReader(newBody), nil
}
return bytes.NewReader(cachedBody), nil return bytes.NewReader(cachedBody), nil
} }
func buildRequestBodyWithMappedModel(originalBody []byte, contentType, redirectedModel string) (io.Reader, error) {
newBuffer := &bytes.Buffer{}
writer := multipart.NewWriter(newBuffer)
_, params, err := mime.ParseMediaType(contentType)
if err != nil {
return nil, errors.Wrap(err, "parse_content_type_failed")
}
boundary, ok := params["boundary"]
if !ok {
return nil, errors.New("boundary_not_found_in_content_type")
}
if err := writer.SetBoundary(boundary); err != nil {
return nil, errors.Wrap(err, "set_boundary_failed")
}
r := multipart.NewReader(bytes.NewReader(originalBody), boundary)
for {
part, err := r.NextPart()
if err == io.EOF {
break
}
if err != nil {
return nil, errors.Wrap(err, "read_multipart_part_failed")
}
fieldName := part.FormName()
if fieldName == "model" {
// 修改 model 字段为映射后的模型名
if err := writer.WriteField("model", redirectedModel); err != nil {
return nil, errors.Wrap(err, "write_model_field_failed")
}
} else {
// 对于其他字段,保留原始内容
if part.FileName() != "" {
newPart, err := writer.CreatePart(part.Header)
if err != nil {
return nil, errors.Wrap(err, "create_form_file_failed")
}
if _, err := io.Copy(newPart, part); err != nil {
return nil, errors.Wrap(err, "copy_file_content_failed")
}
} else {
newPart, err := writer.CreatePart(part.Header)
if err != nil {
return nil, errors.Wrap(err, "create_form_field_failed")
}
if _, err := io.Copy(newPart, part); err != nil {
return nil, errors.Wrap(err, "copy_field_content_failed")
}
}
}
if err := part.Close(); err != nil {
return nil, errors.Wrap(err, "close_part_failed")
}
}
if err := writer.Close(); err != nil {
return nil, errors.Wrap(err, "close_multipart_writer_failed")
}
return newBuffer, nil
}
// DoRequest delegates to common helper. // DoRequest delegates to common helper.
func (a *TaskAdaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, requestBody io.Reader) (*http.Response, error) { func (a *TaskAdaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, requestBody io.Reader) (*http.Response, error) {
return channel.DoTaskApiRequest(a, c, info, requestBody) return channel.DoTaskApiRequest(a, c, info, requestBody)

View File

@@ -121,7 +121,6 @@ func ValidateMultipartDirect(c *gin.Context, info *RelayInfo) *dto.TaskError {
prompt = req.Prompt prompt = req.Prompt
model = req.Model model = req.Model
size = req.Size
seconds, _ = strconv.Atoi(req.Seconds) seconds, _ = strconv.Atoi(req.Seconds)
if seconds == 0 { if seconds == 0 {
seconds = req.Duration seconds = req.Duration
@@ -224,6 +223,11 @@ func ValidateBasicTaskRequest(c *gin.Context, info *RelayInfo, action string) *d
} }
} }
// 模型映射
if info.IsModelMapped {
req.Model = info.UpstreamModelName
}
storeTaskRequest(c, info, action, req) storeTaskRequest(c, info, action, req)
return nil return nil
} }

View File

@@ -13,9 +13,6 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
// https://docs.claude.com/en/docs/build-with-claude/prompt-caching#1-hour-cache-duration
const claudeCacheCreation1hMultiplier = 6 / 3.75
// HandleGroupRatio checks for "auto_group" in the context and updates the group ratio and relayInfo.UsingGroup if present // HandleGroupRatio checks for "auto_group" in the context and updates the group ratio and relayInfo.UsingGroup if present
func HandleGroupRatio(ctx *gin.Context, relayInfo *relaycommon.RelayInfo) types.GroupRatioInfo { func HandleGroupRatio(ctx *gin.Context, relayInfo *relaycommon.RelayInfo) types.GroupRatioInfo {
groupRatioInfo := types.GroupRatioInfo{ groupRatioInfo := types.GroupRatioInfo{
@@ -56,8 +53,6 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens
var cacheRatio float64 var cacheRatio float64
var imageRatio float64 var imageRatio float64
var cacheCreationRatio float64 var cacheCreationRatio float64
var cacheCreationRatio5m float64
var cacheCreationRatio1h float64
var audioRatio float64 var audioRatio float64
var audioCompletionRatio float64 var audioCompletionRatio float64
var freeModel bool var freeModel bool
@@ -81,9 +76,6 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens
completionRatio = ratio_setting.GetCompletionRatio(info.OriginModelName) completionRatio = ratio_setting.GetCompletionRatio(info.OriginModelName)
cacheRatio, _ = ratio_setting.GetCacheRatio(info.OriginModelName) cacheRatio, _ = ratio_setting.GetCacheRatio(info.OriginModelName)
cacheCreationRatio, _ = ratio_setting.GetCreateCacheRatio(info.OriginModelName) cacheCreationRatio, _ = ratio_setting.GetCreateCacheRatio(info.OriginModelName)
cacheCreationRatio5m = cacheCreationRatio
// 固定1h和5min缓存写入价格的比例
cacheCreationRatio1h = cacheCreationRatio * claudeCacheCreation1hMultiplier
imageRatio, _ = ratio_setting.GetImageRatio(info.OriginModelName) imageRatio, _ = ratio_setting.GetImageRatio(info.OriginModelName)
audioRatio = ratio_setting.GetAudioRatio(info.OriginModelName) audioRatio = ratio_setting.GetAudioRatio(info.OriginModelName)
audioCompletionRatio = ratio_setting.GetAudioCompletionRatio(info.OriginModelName) audioCompletionRatio = ratio_setting.GetAudioCompletionRatio(info.OriginModelName)
@@ -124,8 +116,6 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens
AudioRatio: audioRatio, AudioRatio: audioRatio,
AudioCompletionRatio: audioCompletionRatio, AudioCompletionRatio: audioCompletionRatio,
CacheCreationRatio: cacheCreationRatio, CacheCreationRatio: cacheCreationRatio,
CacheCreation5mRatio: cacheCreationRatio5m,
CacheCreation1hRatio: cacheCreationRatio1h,
QuotaToPreConsume: preConsumedQuota, QuotaToPreConsume: preConsumedQuota,
} }

View File

@@ -17,6 +17,7 @@ import (
"github.com/QuantumNous/new-api/relay/channel" "github.com/QuantumNous/new-api/relay/channel"
relaycommon "github.com/QuantumNous/new-api/relay/common" relaycommon "github.com/QuantumNous/new-api/relay/common"
relayconstant "github.com/QuantumNous/new-api/relay/constant" relayconstant "github.com/QuantumNous/new-api/relay/constant"
"github.com/QuantumNous/new-api/relay/helper"
"github.com/QuantumNous/new-api/service" "github.com/QuantumNous/new-api/service"
"github.com/QuantumNous/new-api/setting/ratio_setting" "github.com/QuantumNous/new-api/setting/ratio_setting"
@@ -38,6 +39,11 @@ func RelayTaskSubmit(c *gin.Context, info *relaycommon.RelayInfo) (taskErr *dto.
} }
info.InitChannelMeta(c) info.InitChannelMeta(c)
// 模型映射
if err := helper.ModelMappedHelper(c, info, nil); err != nil {
return service.TaskErrorWrapper(err, "model_mapped_failed", http.StatusBadRequest)
}
adaptor := GetTaskAdaptor(platform) adaptor := GetTaskAdaptor(platform)
if adaptor == nil { if adaptor == nil {
return service.TaskErrorWrapperLocal(fmt.Errorf("invalid api platform: %s", platform), "invalid_api_platform", http.StatusBadRequest) return service.TaskErrorWrapperLocal(fmt.Errorf("invalid api platform: %s", platform), "invalid_api_platform", http.StatusBadRequest)
@@ -208,6 +214,10 @@ func RelayTaskSubmit(c *gin.Context, info *relaycommon.RelayInfo) (taskErr *dto.
task.Quota = quota task.Quota = quota
task.Data = taskData task.Data = taskData
task.Action = info.Action task.Action = info.Action
task.Properties = model.Properties{
UpstreamModelName: info.UpstreamModelName,
OriginModelName: info.OriginModelName,
}
err = task.Insert() err = task.Insert()
if err != nil { if err != nil {
taskErr = service.TaskErrorWrapper(err, "insert_task_failed", http.StatusInternalServerError) taskErr = service.TaskErrorWrapper(err, "insert_task_failed", http.StatusInternalServerError)

View File

@@ -92,23 +92,11 @@ func GenerateAudioOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
} }
func GenerateClaudeOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, modelRatio, groupRatio, completionRatio float64, func GenerateClaudeOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, modelRatio, groupRatio, completionRatio float64,
cacheTokens int, cacheRatio float64, cacheTokens int, cacheRatio float64, cacheCreationTokens int, cacheCreationRatio float64, modelPrice float64, userGroupRatio float64) map[string]interface{} {
cacheCreationTokens int, cacheCreationRatio float64,
cacheCreationTokens5m int, cacheCreationRatio5m float64,
cacheCreationTokens1h int, cacheCreationRatio1h float64,
modelPrice float64, userGroupRatio float64) map[string]interface{} {
info := GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, cacheTokens, cacheRatio, modelPrice, userGroupRatio) info := GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, cacheTokens, cacheRatio, modelPrice, userGroupRatio)
info["claude"] = true info["claude"] = true
info["cache_creation_tokens"] = cacheCreationTokens info["cache_creation_tokens"] = cacheCreationTokens
info["cache_creation_ratio"] = cacheCreationRatio info["cache_creation_ratio"] = cacheCreationRatio
if cacheCreationTokens5m != 0 {
info["cache_creation_tokens_5m"] = cacheCreationTokens5m
info["cache_creation_ratio_5m"] = cacheCreationRatio5m
}
if cacheCreationTokens1h != 0 {
info["cache_creation_tokens_1h"] = cacheCreationTokens1h
info["cache_creation_ratio_1h"] = cacheCreationRatio1h
}
return info return info
} }

View File

@@ -251,11 +251,7 @@ func PostClaudeConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
cacheTokens := usage.PromptTokensDetails.CachedTokens cacheTokens := usage.PromptTokensDetails.CachedTokens
cacheCreationRatio := relayInfo.PriceData.CacheCreationRatio cacheCreationRatio := relayInfo.PriceData.CacheCreationRatio
cacheCreationRatio5m := relayInfo.PriceData.CacheCreation5mRatio
cacheCreationRatio1h := relayInfo.PriceData.CacheCreation1hRatio
cacheCreationTokens := usage.PromptTokensDetails.CachedCreationTokens cacheCreationTokens := usage.PromptTokensDetails.CachedCreationTokens
cacheCreationTokens5m := usage.ClaudeCacheCreation5mTokens
cacheCreationTokens1h := usage.ClaudeCacheCreation1hTokens
if relayInfo.ChannelType == constant.ChannelTypeOpenRouter { if relayInfo.ChannelType == constant.ChannelTypeOpenRouter {
promptTokens -= cacheTokens promptTokens -= cacheTokens
@@ -273,12 +269,7 @@ func PostClaudeConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
if !relayInfo.PriceData.UsePrice { if !relayInfo.PriceData.UsePrice {
calculateQuota = float64(promptTokens) calculateQuota = float64(promptTokens)
calculateQuota += float64(cacheTokens) * cacheRatio calculateQuota += float64(cacheTokens) * cacheRatio
calculateQuota += float64(cacheCreationTokens5m) * cacheCreationRatio5m calculateQuota += float64(cacheCreationTokens) * cacheCreationRatio
calculateQuota += float64(cacheCreationTokens1h) * cacheCreationRatio1h
remainingCacheCreationTokens := cacheCreationTokens - cacheCreationTokens5m - cacheCreationTokens1h
if remainingCacheCreationTokens > 0 {
calculateQuota += float64(remainingCacheCreationTokens) * cacheCreationRatio
}
calculateQuota += float64(completionTokens) * completionRatio calculateQuota += float64(completionTokens) * completionRatio
calculateQuota = calculateQuota * groupRatio * modelRatio calculateQuota = calculateQuota * groupRatio * modelRatio
} else { } else {
@@ -331,11 +322,7 @@ func PostClaudeConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
} }
other := GenerateClaudeOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, other := GenerateClaudeOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio,
cacheTokens, cacheRatio, cacheTokens, cacheRatio, cacheCreationTokens, cacheCreationRatio, modelPrice, relayInfo.PriceData.GroupRatioInfo.GroupSpecialRatio)
cacheCreationTokens, cacheCreationRatio,
cacheCreationTokens5m, cacheCreationRatio5m,
cacheCreationTokens1h, cacheCreationRatio1h,
modelPrice, relayInfo.PriceData.GroupRatioInfo.GroupSpecialRatio)
model.RecordConsumeLog(ctx, relayInfo.UserId, model.RecordConsumeLogParams{ model.RecordConsumeLog(ctx, relayInfo.UserId, model.RecordConsumeLogParams{
ChannelId: relayInfo.ChannelId, ChannelId: relayInfo.ChannelId,
PromptTokens: promptTokens, PromptTokens: promptTokens,

View File

@@ -15,8 +15,6 @@ type PriceData struct {
CompletionRatio float64 CompletionRatio float64
CacheRatio float64 CacheRatio float64
CacheCreationRatio float64 CacheCreationRatio float64
CacheCreation5mRatio float64
CacheCreation1hRatio float64
ImageRatio float64 ImageRatio float64
AudioRatio float64 AudioRatio float64
AudioCompletionRatio float64 AudioCompletionRatio float64
@@ -33,5 +31,5 @@ type PerCallPriceData struct {
} }
func (p PriceData) ToSetting() string { func (p PriceData) ToSetting() string {
return fmt.Sprintf("ModelPrice: %f, ModelRatio: %f, CompletionRatio: %f, CacheRatio: %f, GroupRatio: %f, UsePrice: %t, CacheCreationRatio: %f, CacheCreation5mRatio: %f, CacheCreation1hRatio: %f, QuotaToPreConsume: %d, ImageRatio: %f, AudioRatio: %f, AudioCompletionRatio: %f", p.ModelPrice, p.ModelRatio, p.CompletionRatio, p.CacheRatio, p.GroupRatioInfo.GroupRatio, p.UsePrice, p.CacheCreationRatio, p.CacheCreation5mRatio, p.CacheCreation1hRatio, p.QuotaToPreConsume, p.ImageRatio, p.AudioRatio, p.AudioCompletionRatio) return fmt.Sprintf("ModelPrice: %f, ModelRatio: %f, CompletionRatio: %f, CacheRatio: %f, GroupRatio: %f, UsePrice: %t, CacheCreationRatio: %f, QuotaToPreConsume: %d, ImageRatio: %f, AudioRatio: %f, AudioCompletionRatio: %f", p.ModelPrice, p.ModelRatio, p.CompletionRatio, p.CacheRatio, p.GroupRatioInfo.GroupRatio, p.UsePrice, p.CacheCreationRatio, p.QuotaToPreConsume, p.ImageRatio, p.AudioRatio, p.AudioCompletionRatio)
} }

View File

@@ -551,10 +551,6 @@ export const getLogsColumns = ({
other.cache_ratio || 1.0, other.cache_ratio || 1.0,
other.cache_creation_tokens || 0, other.cache_creation_tokens || 0,
other.cache_creation_ratio || 1.0, other.cache_creation_ratio || 1.0,
other.cache_creation_tokens_5m || 0,
other.cache_creation_ratio_5m || other.cache_creation_ratio || 1.0,
other.cache_creation_tokens_1h || 0,
other.cache_creation_ratio_1h || other.cache_creation_ratio || 1.0,
false, false,
1.0, 1.0,
other?.is_system_prompt_overwritten, other?.is_system_prompt_overwritten,
@@ -569,10 +565,6 @@ export const getLogsColumns = ({
other.cache_ratio || 1.0, other.cache_ratio || 1.0,
0, 0,
1.0, 1.0,
0,
1.0,
0,
1.0,
false, false,
1.0, 1.0,
other?.is_system_prompt_overwritten, other?.is_system_prompt_overwritten,

View File

@@ -1046,10 +1046,6 @@ function renderPriceSimpleCore({
cacheRatio = 1.0, cacheRatio = 1.0,
cacheCreationTokens = 0, cacheCreationTokens = 0,
cacheCreationRatio = 1.0, cacheCreationRatio = 1.0,
cacheCreationTokens5m = 0,
cacheCreationRatio5m = 1.0,
cacheCreationTokens1h = 0,
cacheCreationRatio1h = 1.0,
image = false, image = false,
imageRatio = 1.0, imageRatio = 1.0,
isSystemPromptOverride = false, isSystemPromptOverride = false,
@@ -1068,40 +1064,17 @@ function renderPriceSimpleCore({
}); });
} }
const hasSplitCacheCreation =
cacheCreationTokens5m > 0 || cacheCreationTokens1h > 0;
const shouldShowLegacyCacheCreation =
!hasSplitCacheCreation && cacheCreationTokens !== 0;
const shouldShowCache = cacheTokens !== 0;
const shouldShowCacheCreation5m =
hasSplitCacheCreation && cacheCreationTokens5m > 0;
const shouldShowCacheCreation1h =
hasSplitCacheCreation && cacheCreationTokens1h > 0;
const parts = []; const parts = [];
// base: model ratio // base: model ratio
parts.push(i18next.t('模型: {{ratio}}')); parts.push(i18next.t('模型: {{ratio}}'));
// cache part (label differs when with image) // cache part (label differs when with image)
if (shouldShowCache) { if (cacheTokens !== 0) {
parts.push(i18next.t('缓存: {{cacheRatio}}')); parts.push(i18next.t('缓存: {{cacheRatio}}'));
} }
if (hasSplitCacheCreation) { // cache creation part (Claude specific if passed)
if (shouldShowCacheCreation5m && shouldShowCacheCreation1h) { if (cacheCreationTokens !== 0) {
parts.push(
i18next.t(
'缓存创建: 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}',
),
);
} else if (shouldShowCacheCreation5m) {
parts.push(i18next.t('缓存创建: 5m {{cacheCreationRatio5m}}'));
} else if (shouldShowCacheCreation1h) {
parts.push(i18next.t('缓存创建: 1h {{cacheCreationRatio1h}}'));
}
} else if (shouldShowLegacyCacheCreation) {
parts.push(i18next.t('缓存创建: {{cacheCreationRatio}}')); parts.push(i18next.t('缓存创建: {{cacheCreationRatio}}'));
} }
@@ -1118,8 +1091,6 @@ function renderPriceSimpleCore({
groupRatio: finalGroupRatio, groupRatio: finalGroupRatio,
cacheRatio: cacheRatio, cacheRatio: cacheRatio,
cacheCreationRatio: cacheCreationRatio, cacheCreationRatio: cacheCreationRatio,
cacheCreationRatio5m: cacheCreationRatio5m,
cacheCreationRatio1h: cacheCreationRatio1h,
imageRatio: imageRatio, imageRatio: imageRatio,
}); });
@@ -1479,10 +1450,6 @@ export function renderModelPriceSimple(
cacheRatio = 1.0, cacheRatio = 1.0,
cacheCreationTokens = 0, cacheCreationTokens = 0,
cacheCreationRatio = 1.0, cacheCreationRatio = 1.0,
cacheCreationTokens5m = 0,
cacheCreationRatio5m = 1.0,
cacheCreationTokens1h = 0,
cacheCreationRatio1h = 1.0,
image = false, image = false,
imageRatio = 1.0, imageRatio = 1.0,
isSystemPromptOverride = false, isSystemPromptOverride = false,
@@ -1497,10 +1464,6 @@ export function renderModelPriceSimple(
cacheRatio, cacheRatio,
cacheCreationTokens, cacheCreationTokens,
cacheCreationRatio, cacheCreationRatio,
cacheCreationTokens5m,
cacheCreationRatio5m,
cacheCreationTokens1h,
cacheCreationRatio1h,
image, image,
imageRatio, imageRatio,
isSystemPromptOverride, isSystemPromptOverride,
@@ -1718,10 +1681,6 @@ export function renderClaudeModelPrice(
cacheRatio = 1.0, cacheRatio = 1.0,
cacheCreationTokens = 0, cacheCreationTokens = 0,
cacheCreationRatio = 1.0, cacheCreationRatio = 1.0,
cacheCreationTokens5m = 0,
cacheCreationRatio5m = 1.0,
cacheCreationTokens1h = 0,
cacheCreationRatio1h = 1.0,
) { ) {
const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio( const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(
groupRatio, groupRatio,
@@ -1751,121 +1710,20 @@ export function renderClaudeModelPrice(
const completionRatioValue = completionRatio || 0; const completionRatioValue = completionRatio || 0;
const inputRatioPrice = modelRatio * 2.0; const inputRatioPrice = modelRatio * 2.0;
const completionRatioPrice = modelRatio * 2.0 * completionRatioValue; const completionRatioPrice = modelRatio * 2.0 * completionRatioValue;
const cacheRatioPrice = modelRatio * 2.0 * cacheRatio; let cacheRatioPrice = (modelRatio * 2.0 * cacheRatio).toFixed(2);
const cacheCreationRatioPrice = modelRatio * 2.0 * cacheCreationRatio; let cacheCreationRatioPrice = modelRatio * 2.0 * cacheCreationRatio;
const cacheCreationRatioPrice5m = modelRatio * 2.0 * cacheCreationRatio5m;
const cacheCreationRatioPrice1h = modelRatio * 2.0 * cacheCreationRatio1h;
const hasSplitCacheCreation =
cacheCreationTokens5m > 0 || cacheCreationTokens1h > 0;
const shouldShowCache = cacheTokens > 0;
const shouldShowLegacyCacheCreation =
!hasSplitCacheCreation && cacheCreationTokens > 0;
const shouldShowCacheCreation5m =
hasSplitCacheCreation && cacheCreationTokens5m > 0;
const shouldShowCacheCreation1h =
hasSplitCacheCreation && cacheCreationTokens1h > 0;
// Calculate effective input tokens (non-cached + cached with ratio applied + cache creation with ratio applied) // Calculate effective input tokens (non-cached + cached with ratio applied + cache creation with ratio applied)
const nonCachedTokens = inputTokens; const nonCachedTokens = inputTokens;
const effectiveInputTokens = const effectiveInputTokens =
nonCachedTokens + nonCachedTokens +
cacheTokens * cacheRatio + cacheTokens * cacheRatio +
cacheCreationTokens * cacheCreationRatio + cacheCreationTokens * cacheCreationRatio;
cacheCreationTokens5m * cacheCreationRatio5m +
cacheCreationTokens1h * cacheCreationRatio1h;
let price = let price =
(effectiveInputTokens / 1000000) * inputRatioPrice * groupRatio + (effectiveInputTokens / 1000000) * inputRatioPrice * groupRatio +
(completionTokens / 1000000) * completionRatioPrice * groupRatio; (completionTokens / 1000000) * completionRatioPrice * groupRatio;
const inputUnitPrice = inputRatioPrice * rate;
const completionUnitPrice = completionRatioPrice * rate;
const cacheUnitPrice = cacheRatioPrice * rate;
const cacheCreationUnitPrice = cacheCreationRatioPrice * rate;
const cacheCreationUnitPrice5m = cacheCreationRatioPrice5m * rate;
const cacheCreationUnitPrice1h = cacheCreationRatioPrice1h * rate;
const cacheCreationUnitPriceTotal =
cacheCreationUnitPrice5m + cacheCreationUnitPrice1h;
const breakdownSegments = [
i18next.t('提示 {{input}} tokens / 1M tokens * {{symbol}}{{price}}', {
input: inputTokens,
symbol,
price: inputUnitPrice.toFixed(6),
}),
];
if (shouldShowCache) {
breakdownSegments.push(
i18next.t(
'缓存 {{tokens}} tokens / 1M tokens * {{symbol}}{{price}} (倍率: {{ratio}})',
{
tokens: cacheTokens,
symbol,
price: cacheUnitPrice.toFixed(6),
ratio: cacheRatio,
},
),
);
}
if (shouldShowLegacyCacheCreation) {
breakdownSegments.push(
i18next.t(
'缓存创建 {{tokens}} tokens / 1M tokens * {{symbol}}{{price}} (倍率: {{ratio}})',
{
tokens: cacheCreationTokens,
symbol,
price: cacheCreationUnitPrice.toFixed(6),
ratio: cacheCreationRatio,
},
),
);
}
if (shouldShowCacheCreation5m) {
breakdownSegments.push(
i18next.t(
'5m缓存创建 {{tokens}} tokens / 1M tokens * {{symbol}}{{price}} (倍率: {{ratio}})',
{
tokens: cacheCreationTokens5m,
symbol,
price: cacheCreationUnitPrice5m.toFixed(6),
ratio: cacheCreationRatio5m,
},
),
);
}
if (shouldShowCacheCreation1h) {
breakdownSegments.push(
i18next.t(
'1h缓存创建 {{tokens}} tokens / 1M tokens * {{symbol}}{{price}} (倍率: {{ratio}})',
{
tokens: cacheCreationTokens1h,
symbol,
price: cacheCreationUnitPrice1h.toFixed(6),
ratio: cacheCreationRatio1h,
},
),
);
}
breakdownSegments.push(
i18next.t(
'补全 {{completion}} tokens / 1M tokens * {{symbol}}{{price}}',
{
completion: completionTokens,
symbol,
price: completionUnitPrice.toFixed(6),
},
),
);
const breakdownText = breakdownSegments.join(' + ');
return ( return (
<> <>
<article> <article>
@@ -1886,7 +1744,7 @@ export function renderClaudeModelPrice(
}, },
)} )}
</p> </p>
{shouldShowCache && ( {cacheTokens > 0 && (
<p> <p>
{i18next.t( {i18next.t(
'缓存价格:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (缓存倍率: {{cacheRatio}})', '缓存价格:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (缓存倍率: {{cacheRatio}})',
@@ -1894,13 +1752,13 @@ export function renderClaudeModelPrice(
symbol: symbol, symbol: symbol,
price: (inputRatioPrice * rate).toFixed(6), price: (inputRatioPrice * rate).toFixed(6),
ratio: cacheRatio, ratio: cacheRatio,
total: cacheUnitPrice.toFixed(6), total: (cacheRatioPrice * rate).toFixed(2),
cacheRatio: cacheRatio, cacheRatio: cacheRatio,
}, },
)} )}
</p> </p>
)} )}
{shouldShowLegacyCacheCreation && ( {cacheCreationTokens > 0 && (
<p> <p>
{i18next.t( {i18next.t(
'缓存创建价格:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (缓存创建倍率: {{cacheCreationRatio}})', '缓存创建价格:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (缓存创建倍率: {{cacheCreationRatio}})',
@@ -1908,65 +1766,49 @@ export function renderClaudeModelPrice(
symbol: symbol, symbol: symbol,
price: (inputRatioPrice * rate).toFixed(6), price: (inputRatioPrice * rate).toFixed(6),
ratio: cacheCreationRatio, ratio: cacheCreationRatio,
total: cacheCreationUnitPrice.toFixed(6), total: (cacheCreationRatioPrice * rate).toFixed(6),
cacheCreationRatio: cacheCreationRatio, cacheCreationRatio: cacheCreationRatio,
}, },
)} )}
</p> </p>
)} )}
{shouldShowCacheCreation5m && (
<p>
{i18next.t(
'5m缓存创建价格{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (5m缓存创建倍率: {{cacheCreationRatio5m}})',
{
symbol: symbol,
price: (inputRatioPrice * rate).toFixed(6),
ratio: cacheCreationRatio5m,
total: cacheCreationUnitPrice5m.toFixed(6),
cacheCreationRatio5m: cacheCreationRatio5m,
},
)}
</p>
)}
{shouldShowCacheCreation1h && (
<p>
{i18next.t(
'1h缓存创建价格{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (1h缓存创建倍率: {{cacheCreationRatio1h}})',
{
symbol: symbol,
price: (inputRatioPrice * rate).toFixed(6),
ratio: cacheCreationRatio1h,
total: cacheCreationUnitPrice1h.toFixed(6),
cacheCreationRatio1h: cacheCreationRatio1h,
},
)}
</p>
)}
{shouldShowCacheCreation5m && shouldShowCacheCreation1h && (
<p>
{i18next.t(
'缓存创建价格合计5m {{symbol}}{{five}} + 1h {{symbol}}{{one}} = {{symbol}}{{total}} / 1M tokens',
{
symbol: symbol,
five: cacheCreationUnitPrice5m.toFixed(6),
one: cacheCreationUnitPrice1h.toFixed(6),
total: cacheCreationUnitPriceTotal.toFixed(6),
},
)}
</p>
)}
<p></p> <p></p>
<p> <p>
{i18next.t( {cacheTokens > 0 || cacheCreationTokens > 0
'{{breakdown}} * {{ratioType}} {{ratio}} = {{symbol}}{{total}}', ? i18next.t(
{ '提示 {{nonCacheInput}} tokens / 1M tokens * {{symbol}}{{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * {{symbol}}{{cachePrice}} + 缓存创建 {{cacheCreationInput}} tokens / 1M tokens * {{symbol}}{{cacheCreationPrice}} + 补全 {{completion}} tokens / 1M tokens * {{symbol}}{{compPrice}} * {{ratioType}} {{ratio}} = {{symbol}}{{total}}',
breakdown: breakdownText, {
ratioType: ratioLabel, nonCacheInput: nonCachedTokens,
ratio: groupRatio, cacheInput: cacheTokens,
symbol: symbol, cacheRatio: cacheRatio,
total: (price * rate).toFixed(6), cacheCreationInput: cacheCreationTokens,
}, cacheCreationRatio: cacheCreationRatio,
)} symbol: symbol,
cachePrice: (cacheRatioPrice * rate).toFixed(2),
cacheCreationPrice: (
cacheCreationRatioPrice * rate
).toFixed(6),
price: (inputRatioPrice * rate).toFixed(6),
completion: completionTokens,
compPrice: (completionRatioPrice * rate).toFixed(6),
ratio: groupRatio,
ratioType: ratioLabel,
total: (price * rate).toFixed(6),
},
)
: i18next.t(
'提示 {{input}} tokens / 1M tokens * {{symbol}}{{price}} + 补全 {{completion}} tokens / 1M tokens * {{symbol}}{{compPrice}} * {{ratioType}} {{ratio}} = {{symbol}}{{total}}',
{
input: inputTokens,
symbol: symbol,
price: (inputRatioPrice * rate).toFixed(6),
completion: completionTokens,
compPrice: (completionRatioPrice * rate).toFixed(6),
ratio: groupRatio,
ratioType: ratioLabel,
total: (price * rate).toFixed(6),
},
)}
</p> </p>
<p>{i18next.t('仅供参考,以实际扣费为准')}</p> <p>{i18next.t('仅供参考,以实际扣费为准')}</p>
</article> </article>
@@ -1983,10 +1825,6 @@ export function renderClaudeLogContent(
user_group_ratio, user_group_ratio,
cacheRatio = 1.0, cacheRatio = 1.0,
cacheCreationRatio = 1.0, cacheCreationRatio = 1.0,
cacheCreationTokens5m = 0,
cacheCreationRatio5m = 1.0,
cacheCreationTokens1h = 0,
cacheCreationRatio1h = 1.0,
) { ) {
const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio( const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(
groupRatio, groupRatio,
@@ -2005,58 +1843,17 @@ export function renderClaudeLogContent(
ratio: groupRatio, ratio: groupRatio,
}); });
} else { } else {
const hasSplitCacheCreation = return i18next.t(
cacheCreationTokens5m > 0 || cacheCreationTokens1h > 0; '模型倍率 {{modelRatio}},输出倍率 {{completionRatio}},缓存倍率 {{cacheRatio}},缓存创建倍率 {{cacheCreationRatio}}{{ratioType}} {{ratio}}',
const shouldShowCacheCreation5m = {
hasSplitCacheCreation && cacheCreationTokens5m > 0; modelRatio: modelRatio,
const shouldShowCacheCreation1h = completionRatio: completionRatio,
hasSplitCacheCreation && cacheCreationTokens1h > 0; cacheRatio: cacheRatio,
cacheCreationRatio: cacheCreationRatio,
let cacheCreationPart = null;
if (hasSplitCacheCreation) {
if (shouldShowCacheCreation5m && shouldShowCacheCreation1h) {
cacheCreationPart = i18next.t(
'缓存创建倍率 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}',
{
cacheCreationRatio5m,
cacheCreationRatio1h,
},
);
} else if (shouldShowCacheCreation5m) {
cacheCreationPart = i18next.t(
'缓存创建倍率 5m {{cacheCreationRatio5m}}',
{
cacheCreationRatio5m,
},
);
} else if (shouldShowCacheCreation1h) {
cacheCreationPart = i18next.t(
'缓存创建倍率 1h {{cacheCreationRatio1h}}',
{
cacheCreationRatio1h,
},
);
}
}
if (!cacheCreationPart) {
cacheCreationPart = i18next.t('缓存创建倍率 {{cacheCreationRatio}}', {
cacheCreationRatio,
});
}
const parts = [
i18next.t('模型倍率 {{modelRatio}}', { modelRatio }),
i18next.t('输出倍率 {{completionRatio}}', { completionRatio }),
i18next.t('缓存倍率 {{cacheRatio}}', { cacheRatio }),
cacheCreationPart,
i18next.t('{{ratioType}} {{ratio}}', {
ratioType: ratioLabel, ratioType: ratioLabel,
ratio: groupRatio, ratio: groupRatio,
}), },
]; );
return parts.join('');
} }
} }

View File

@@ -361,10 +361,6 @@ export const useLogsData = () => {
other?.user_group_ratio, other?.user_group_ratio,
other.cache_ratio || 1.0, other.cache_ratio || 1.0,
other.cache_creation_ratio || 1.0, other.cache_creation_ratio || 1.0,
other.cache_creation_tokens_5m || 0,
other.cache_creation_ratio_5m || other.cache_creation_ratio || 1.0,
other.cache_creation_tokens_1h || 0,
other.cache_creation_ratio_1h || other.cache_creation_ratio || 1.0,
) )
: renderLogContent( : renderLogContent(
other?.model_ratio, other?.model_ratio,
@@ -433,10 +429,6 @@ export const useLogsData = () => {
other.cache_ratio || 1.0, other.cache_ratio || 1.0,
other.cache_creation_tokens || 0, other.cache_creation_tokens || 0,
other.cache_creation_ratio || 1.0, other.cache_creation_ratio || 1.0,
other.cache_creation_tokens_5m || 0,
other.cache_creation_ratio_5m || other.cache_creation_ratio || 1.0,
other.cache_creation_tokens_1h || 0,
other.cache_creation_ratio_1h || other.cache_creation_ratio || 1.0,
); );
} else { } else {
content = renderModelPrice( content = renderModelPrice(

View File

@@ -1516,10 +1516,6 @@
"缓存倍率": "Cache ratio", "缓存倍率": "Cache ratio",
"缓存创建 Tokens": "Cache Creation Tokens", "缓存创建 Tokens": "Cache Creation Tokens",
"缓存创建: {{cacheCreationRatio}}": "Cache creation: {{cacheCreationRatio}}", "缓存创建: {{cacheCreationRatio}}": "Cache creation: {{cacheCreationRatio}}",
"缓存创建: 5m {{cacheCreationRatio5m}}": "Cache creation: 5m {{cacheCreationRatio5m}}",
"缓存创建: 1h {{cacheCreationRatio1h}}": "Cache creation: 1h {{cacheCreationRatio1h}}",
"缓存创建倍率 5m {{cacheCreationRatio5m}}": "Cache creation multiplier 5m {{cacheCreationRatio5m}}",
"缓存创建倍率 1h {{cacheCreationRatio1h}}": "Cache creation multiplier 1h {{cacheCreationRatio1h}}",
"缓存创建价格:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (缓存创建倍率: {{cacheCreationRatio}})": "Cache creation price: {{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (Cache creation ratio: {{cacheCreationRatio}})", "缓存创建价格:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (缓存创建倍率: {{cacheCreationRatio}})": "Cache creation price: {{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (Cache creation ratio: {{cacheCreationRatio}})",
"编辑": "Edit", "编辑": "Edit",
"编辑API": "Edit API", "编辑API": "Edit API",
@@ -2108,4 +2104,4 @@
"统一的": "The Unified", "统一的": "The Unified",
"大模型接口网关": "LLM API Gateway" "大模型接口网关": "LLM API Gateway"
} }
} }

View File

@@ -1525,10 +1525,6 @@
"缓存倍率": "Ratio de cache", "缓存倍率": "Ratio de cache",
"缓存创建 Tokens": "Jetons de création de cache", "缓存创建 Tokens": "Jetons de création de cache",
"缓存创建: {{cacheCreationRatio}}": "Création de cache : {{cacheCreationRatio}}", "缓存创建: {{cacheCreationRatio}}": "Création de cache : {{cacheCreationRatio}}",
"缓存创建: 5m {{cacheCreationRatio5m}}": "Création de cache : 5m {{cacheCreationRatio5m}}",
"缓存创建: 1h {{cacheCreationRatio1h}}": "Création de cache : 1h {{cacheCreationRatio1h}}",
"缓存创建倍率 5m {{cacheCreationRatio5m}}": "Multiplicateur de création de cache 5m {{cacheCreationRatio5m}}",
"缓存创建倍率 1h {{cacheCreationRatio1h}}": "Multiplicateur de création de cache 1h {{cacheCreationRatio1h}}",
"缓存创建价格:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (缓存创建倍率: {{cacheCreationRatio}})": "Prix de création du cache : {{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (taux de création de cache : {{cacheCreationRatio}})", "缓存创建价格:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (缓存创建倍率: {{cacheCreationRatio}})": "Prix de création du cache : {{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (taux de création de cache : {{cacheCreationRatio}})",
"编辑": "Modifier", "编辑": "Modifier",
"编辑API": "Modifier l'API", "编辑API": "Modifier l'API",

View File

@@ -1516,10 +1516,6 @@
"缓存倍率": "キャッシュ倍率", "缓存倍率": "キャッシュ倍率",
"缓存创建 Tokens": "キャッシュ作成トークン", "缓存创建 Tokens": "キャッシュ作成トークン",
"缓存创建: {{cacheCreationRatio}}": "キャッシュ作成:{{cacheCreationRatio}}", "缓存创建: {{cacheCreationRatio}}": "キャッシュ作成:{{cacheCreationRatio}}",
"缓存创建: 5m {{cacheCreationRatio5m}}": "キャッシュ作成5m {{cacheCreationRatio5m}}",
"缓存创建: 1h {{cacheCreationRatio1h}}": "キャッシュ作成1h {{cacheCreationRatio1h}}",
"缓存创建倍率 5m {{cacheCreationRatio5m}}": "キャッシュ作成倍率 5m {{cacheCreationRatio5m}}",
"缓存创建倍率 1h {{cacheCreationRatio1h}}": "キャッシュ作成倍率 1h {{cacheCreationRatio1h}}",
"缓存创建价格:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (缓存创建倍率: {{cacheCreationRatio}})": "キャッシュ作成料金:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1Mtokensキャッシュ作成倍率{{cacheCreationRatio}}", "缓存创建价格:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (缓存创建倍率: {{cacheCreationRatio}})": "キャッシュ作成料金:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1Mtokensキャッシュ作成倍率{{cacheCreationRatio}}",
"编辑": "編集", "编辑": "編集",
"编辑API": "API編集", "编辑API": "API編集",
@@ -2079,4 +2075,4 @@
"统一的": "統合型", "统一的": "統合型",
"大模型接口网关": "LLM APIゲートウェイ" "大模型接口网关": "LLM APIゲートウェイ"
} }
} }

View File

@@ -1534,10 +1534,6 @@
"缓存倍率": "Коэффициент кэширования", "缓存倍率": "Коэффициент кэширования",
"缓存创建 Tokens": "Создание кэша токенов", "缓存创建 Tokens": "Создание кэша токенов",
"缓存创建: {{cacheCreationRatio}}": "Создание кэша: {{cacheCreationRatio}}", "缓存创建: {{cacheCreationRatio}}": "Создание кэша: {{cacheCreationRatio}}",
"缓存创建: 5m {{cacheCreationRatio5m}}": "Создание кэша: 5m {{cacheCreationRatio5m}}",
"缓存创建: 1h {{cacheCreationRatio1h}}": "Создание кэша: 1h {{cacheCreationRatio1h}}",
"缓存创建倍率 5m {{cacheCreationRatio5m}}": "Множитель создания кэша 5m {{cacheCreationRatio5m}}",
"缓存创建倍率 1h {{cacheCreationRatio1h}}": "Множитель создания кэша 1h {{cacheCreationRatio1h}}",
"缓存创建价格:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (缓存创建倍率: {{cacheCreationRatio}})": "Цена создания кэша: {{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M токенов (коэффициент создания кэша: {{cacheCreationRatio}})", "缓存创建价格:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (缓存创建倍率: {{cacheCreationRatio}})": "Цена создания кэша: {{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M токенов (коэффициент создания кэша: {{cacheCreationRatio}})",
"编辑": "Редактировать", "编辑": "Редактировать",
"编辑API": "Редактировать API", "编辑API": "Редактировать API",

View File

@@ -1507,10 +1507,6 @@
"缓存倍率": "缓存倍率", "缓存倍率": "缓存倍率",
"缓存创建 Tokens": "缓存创建 Tokens", "缓存创建 Tokens": "缓存创建 Tokens",
"缓存创建: {{cacheCreationRatio}}": "缓存创建: {{cacheCreationRatio}}", "缓存创建: {{cacheCreationRatio}}": "缓存创建: {{cacheCreationRatio}}",
"缓存创建: 5m {{cacheCreationRatio5m}}": "缓存创建: 5m {{cacheCreationRatio5m}}",
"缓存创建: 1h {{cacheCreationRatio1h}}": "缓存创建: 1h {{cacheCreationRatio1h}}",
"缓存创建倍率 5m {{cacheCreationRatio5m}}": "缓存创建倍率 5m {{cacheCreationRatio5m}}",
"缓存创建倍率 1h {{cacheCreationRatio1h}}": "缓存创建倍率 1h {{cacheCreationRatio1h}}",
"缓存创建价格:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (缓存创建倍率: {{cacheCreationRatio}})": "缓存创建价格:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (缓存创建倍率: {{cacheCreationRatio}})", "缓存创建价格:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (缓存创建倍率: {{cacheCreationRatio}})": "缓存创建价格:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (缓存创建倍率: {{cacheCreationRatio}})",
"编辑": "编辑", "编辑": "编辑",
"编辑API": "编辑API", "编辑API": "编辑API",
@@ -2070,4 +2066,4 @@
"Creem 介绍": "Creem 是一个简单的支付处理平台,支持固定金额产品销售,以及订阅销售。", "Creem 介绍": "Creem 是一个简单的支付处理平台,支持固定金额产品销售,以及订阅销售。",
"Creem Setting Tips": "Creem 只支持预设的固定金额产品这产品以及价格需要提前在Creem网站内创建配置所以不支持自定义动态金额充值。在Creem端配置产品的名字以及价格获取Product Id 后填到下面的产品在new-api为该产品设置充值额度以及展示价格。" "Creem Setting Tips": "Creem 只支持预设的固定金额产品这产品以及价格需要提前在Creem网站内创建配置所以不支持自定义动态金额充值。在Creem端配置产品的名字以及价格获取Product Id 后填到下面的产品在new-api为该产品设置充值额度以及展示价格。"
} }
} }