feat: support image edit model mapping
(cherry picked from commit 1a869d8ad77f262ee27675ec2deaf451b1743eb7)
This commit is contained in:
@@ -22,9 +22,11 @@ import (
|
|||||||
"one-api/relay/common_handler"
|
"one-api/relay/common_handler"
|
||||||
"one-api/relay/constant"
|
"one-api/relay/constant"
|
||||||
"one-api/service"
|
"one-api/service"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"net/textproto"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Adaptor struct {
|
type Adaptor struct {
|
||||||
@@ -238,13 +240,8 @@ func (a *Adaptor) ConvertAudioRequest(c *gin.Context, info *relaycommon.RelayInf
|
|||||||
func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.ImageRequest) (any, error) {
|
func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.ImageRequest) (any, error) {
|
||||||
switch info.RelayMode {
|
switch info.RelayMode {
|
||||||
case constant.RelayModeImagesEdits:
|
case constant.RelayModeImagesEdits:
|
||||||
body, err := common.GetRequestBody(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("get request body fail")
|
|
||||||
}
|
|
||||||
return bytes.NewReader(body), nil
|
|
||||||
|
|
||||||
/*var requestBody bytes.Buffer
|
var requestBody bytes.Buffer
|
||||||
writer := multipart.NewWriter(&requestBody)
|
writer := multipart.NewWriter(&requestBody)
|
||||||
|
|
||||||
writer.WriteField("model", request.Model)
|
writer.WriteField("model", request.Model)
|
||||||
@@ -260,36 +257,129 @@ func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加文件字段
|
// Parse the multipart form to handle both single image and multiple images
|
||||||
imageFiles := c.Request.MultipartForm.File["image[]"]
|
if err := c.Request.ParseMultipartForm(32 << 20); err != nil { // 32MB max memory
|
||||||
for _, file := range imageFiles {
|
return nil, errors.New("failed to parse multipart form")
|
||||||
part, err := writer.CreateFormFile("image[]", file.Filename)
|
}
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("create form file failed")
|
if c.Request.MultipartForm != nil && c.Request.MultipartForm.File != nil {
|
||||||
|
// Check if "image" field exists in any form, including array notation
|
||||||
|
var imageFiles []*multipart.FileHeader
|
||||||
|
var exists bool
|
||||||
|
|
||||||
|
// First check for standard "image" field
|
||||||
|
if imageFiles, exists = c.Request.MultipartForm.File["image"]; !exists || len(imageFiles) == 0 {
|
||||||
|
// If not found, check for "image[]" field
|
||||||
|
if imageFiles, exists = c.Request.MultipartForm.File["image[]"]; !exists || len(imageFiles) == 0 {
|
||||||
|
// If still not found, iterate through all fields to find any that start with "image["
|
||||||
|
foundArrayImages := false
|
||||||
|
for fieldName, files := range c.Request.MultipartForm.File {
|
||||||
|
if strings.HasPrefix(fieldName, "image[") && len(files) > 0 {
|
||||||
|
foundArrayImages = true
|
||||||
|
for _, file := range files {
|
||||||
|
imageFiles = append(imageFiles, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no image fields found at all
|
||||||
|
if !foundArrayImages && (len(imageFiles) == 0) {
|
||||||
|
return nil, errors.New("image is required")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 打开文件
|
|
||||||
src, err := file.Open()
|
// Process all image files
|
||||||
if err != nil {
|
for i, fileHeader := range imageFiles {
|
||||||
return nil, errors.New("open file failed")
|
file, err := fileHeader.Open()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to open image file %d: %w", i, err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// If multiple images, use image[] as the field name
|
||||||
|
fieldName := "image"
|
||||||
|
if len(imageFiles) > 1 {
|
||||||
|
fieldName = "image[]"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine MIME type based on file extension
|
||||||
|
mimeType := detectImageMimeType(fileHeader.Filename)
|
||||||
|
|
||||||
|
// Create a form file with the appropriate content type
|
||||||
|
h := make(textproto.MIMEHeader)
|
||||||
|
h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, fieldName, fileHeader.Filename))
|
||||||
|
h.Set("Content-Type", mimeType)
|
||||||
|
|
||||||
|
part, err := writer.CreatePart(h)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create form part failed for image %d: %w", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.Copy(part, file); err != nil {
|
||||||
|
return nil, fmt.Errorf("copy file failed for image %d: %w", i, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 将文件数据写入 form part
|
|
||||||
_, err = io.Copy(part, src)
|
// Handle mask file if present
|
||||||
if err != nil {
|
if maskFiles, exists := c.Request.MultipartForm.File["mask"]; exists && len(maskFiles) > 0 {
|
||||||
return nil, errors.New("copy file failed")
|
maskFile, err := maskFiles[0].Open()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("failed to open mask file")
|
||||||
|
}
|
||||||
|
defer maskFile.Close()
|
||||||
|
|
||||||
|
// Determine MIME type for mask file
|
||||||
|
mimeType := detectImageMimeType(maskFiles[0].Filename)
|
||||||
|
|
||||||
|
// Create a form file with the appropriate content type
|
||||||
|
h := make(textproto.MIMEHeader)
|
||||||
|
h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="mask"; filename="%s"`, maskFiles[0].Filename))
|
||||||
|
h.Set("Content-Type", mimeType)
|
||||||
|
|
||||||
|
maskPart, err := writer.CreatePart(h)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("create form file failed for mask")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.Copy(maskPart, maskFile); err != nil {
|
||||||
|
return nil, errors.New("copy mask file failed")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
src.Close()
|
} else {
|
||||||
|
return nil, errors.New("no multipart form data found")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关闭 multipart 编写器以设置分界线
|
// 关闭 multipart 编写器以设置分界线
|
||||||
writer.Close()
|
writer.Close()
|
||||||
c.Request.Header.Set("Content-Type", writer.FormDataContentType())
|
c.Request.Header.Set("Content-Type", writer.FormDataContentType())
|
||||||
return bytes.NewReader(requestBody.Bytes()), nil*/
|
return bytes.NewReader(requestBody.Bytes()), nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return request, nil
|
return request, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// detectImageMimeType determines the MIME type based on the file extension
|
||||||
|
func detectImageMimeType(filename string) string {
|
||||||
|
ext := strings.ToLower(filepath.Ext(filename))
|
||||||
|
switch ext {
|
||||||
|
case ".jpg", ".jpeg":
|
||||||
|
return "image/jpeg"
|
||||||
|
case ".png":
|
||||||
|
return "image/png"
|
||||||
|
case ".webp":
|
||||||
|
return "image/webp"
|
||||||
|
default:
|
||||||
|
// Try to detect from extension if possible
|
||||||
|
if strings.HasPrefix(ext, ".jp") {
|
||||||
|
return "image/jpeg"
|
||||||
|
}
|
||||||
|
// Default to png as a fallback
|
||||||
|
return "image/png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (a *Adaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, requestBody io.Reader) (any, error) {
|
func (a *Adaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, requestBody io.Reader) (any, error) {
|
||||||
if info.RelayMode == constant.RelayModeAudioTranscription ||
|
if info.RelayMode == constant.RelayModeAudioTranscription ||
|
||||||
info.RelayMode == constant.RelayModeAudioTranslation ||
|
info.RelayMode == constant.RelayModeAudioTranslation ||
|
||||||
|
|||||||
@@ -425,6 +425,11 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
|
|||||||
logContent += ", " + 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)
|
||||||
|
if imageTokens != 0 {
|
||||||
|
other["image"] = true
|
||||||
|
other["image_ratio"] = imageRatio
|
||||||
|
other["image_output"] = imageTokens
|
||||||
|
}
|
||||||
model.RecordConsumeLog(ctx, relayInfo.UserId, relayInfo.ChannelId, promptTokens, completionTokens, logModel,
|
model.RecordConsumeLog(ctx, relayInfo.UserId, relayInfo.ChannelId, promptTokens, completionTokens, logModel,
|
||||||
tokenName, quota, logContent, relayInfo.TokenId, userQuota, int(useTimeSeconds), relayInfo.IsStream, relayInfo.Group, other)
|
tokenName, quota, logContent, relayInfo.TokenId, userQuota, int(useTimeSeconds), relayInfo.IsStream, relayInfo.Group, other)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -987,7 +987,9 @@ const LogsTable = () => {
|
|||||||
other?.group_ratio,
|
other?.group_ratio,
|
||||||
other?.cache_tokens || 0,
|
other?.cache_tokens || 0,
|
||||||
other?.cache_ratio || 1.0,
|
other?.cache_ratio || 1.0,
|
||||||
);
|
other?.image || false,
|
||||||
|
other?.image_ratio || 0,
|
||||||
|
other?.image_output || 0,);
|
||||||
}
|
}
|
||||||
expandDataLocal.push({
|
expandDataLocal.push({
|
||||||
key: t('计费过程'),
|
key: t('计费过程'),
|
||||||
|
|||||||
@@ -314,6 +314,9 @@ export function renderModelPrice(
|
|||||||
groupRatio,
|
groupRatio,
|
||||||
cacheTokens = 0,
|
cacheTokens = 0,
|
||||||
cacheRatio = 1.0,
|
cacheRatio = 1.0,
|
||||||
|
image = false,
|
||||||
|
imageRatio = 1.0,
|
||||||
|
imageOutputTokens = 0,
|
||||||
) {
|
) {
|
||||||
if (modelPrice !== -1) {
|
if (modelPrice !== -1) {
|
||||||
return i18next.t(
|
return i18next.t(
|
||||||
@@ -331,10 +334,15 @@ export function renderModelPrice(
|
|||||||
let inputRatioPrice = modelRatio * 2.0;
|
let inputRatioPrice = modelRatio * 2.0;
|
||||||
let completionRatioPrice = modelRatio * 2.0 * completionRatio;
|
let completionRatioPrice = modelRatio * 2.0 * completionRatio;
|
||||||
let cacheRatioPrice = modelRatio * 2.0 * cacheRatio;
|
let cacheRatioPrice = modelRatio * 2.0 * cacheRatio;
|
||||||
|
let imageRatioPrice = modelRatio * 2.0 * imageRatio;
|
||||||
|
|
||||||
// Calculate effective input tokens (non-cached + cached with ratio applied)
|
// Calculate effective input tokens (non-cached + cached with ratio applied)
|
||||||
const effectiveInputTokens =
|
let effectiveInputTokens =
|
||||||
inputTokens - cacheTokens + cacheTokens * cacheRatio;
|
inputTokens - cacheTokens + cacheTokens * cacheRatio;
|
||||||
|
// Handle image tokens if present
|
||||||
|
if (image && imageOutputTokens > 0) {
|
||||||
|
effectiveInputTokens = inputTokens - imageOutputTokens + imageOutputTokens * imageRatio;
|
||||||
|
}
|
||||||
|
|
||||||
let price =
|
let price =
|
||||||
(effectiveInputTokens / 1000000) * inputRatioPrice * groupRatio +
|
(effectiveInputTokens / 1000000) * inputRatioPrice * groupRatio +
|
||||||
@@ -344,13 +352,13 @@ export function renderModelPrice(
|
|||||||
<>
|
<>
|
||||||
<article>
|
<article>
|
||||||
<p>
|
<p>
|
||||||
{i18next.t('提示价格:${{price}} / 1M tokens', {
|
{i18next.t('输入价格:${{price}} / 1M tokens', {
|
||||||
price: inputRatioPrice,
|
price: inputRatioPrice,
|
||||||
})}
|
})}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{i18next.t(
|
{i18next.t(
|
||||||
'补全价格:${{price}} * {{completionRatio}} = ${{total}} / 1M tokens (补全倍率: {{completionRatio}})',
|
'输出价格:${{price}} * {{completionRatio}} = ${{total}} / 1M tokens (补全倍率: {{completionRatio}})',
|
||||||
{
|
{
|
||||||
price: inputRatioPrice,
|
price: inputRatioPrice,
|
||||||
total: completionRatioPrice,
|
total: completionRatioPrice,
|
||||||
@@ -370,11 +378,24 @@ export function renderModelPrice(
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
{image && imageOutputTokens > 0 && (
|
||||||
|
<p>
|
||||||
|
{i18next.t(
|
||||||
|
'图片输入价格:${{price}} * {{ratio}} = ${{total}} / 1M tokens (图片倍率: {{imageRatio}})',
|
||||||
|
{
|
||||||
|
price: imageRatioPrice,
|
||||||
|
ratio: groupRatio,
|
||||||
|
total: imageRatioPrice * groupRatio,
|
||||||
|
imageRatio: imageRatio,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
<p></p>
|
<p></p>
|
||||||
<p>
|
<p>
|
||||||
{cacheTokens > 0
|
{cacheTokens > 0 && !image
|
||||||
? i18next.t(
|
? i18next.t(
|
||||||
'提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}',
|
'输入 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}',
|
||||||
{
|
{
|
||||||
nonCacheInput: inputTokens - cacheTokens,
|
nonCacheInput: inputTokens - cacheTokens,
|
||||||
cacheInput: cacheTokens,
|
cacheInput: cacheTokens,
|
||||||
@@ -386,8 +407,22 @@ export function renderModelPrice(
|
|||||||
total: price.toFixed(6),
|
total: price.toFixed(6),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
: image && imageOutputTokens > 0
|
||||||
|
? i18next.t(
|
||||||
|
'输入 {{nonImageInput}} tokens + 图片输入 {{imageInput}} tokens * {{imageRatio}} / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}',
|
||||||
|
{
|
||||||
|
nonImageInput: inputTokens - imageOutputTokens,
|
||||||
|
imageInput: imageOutputTokens,
|
||||||
|
imageRatio: imageRatio,
|
||||||
|
price: inputRatioPrice,
|
||||||
|
completion: completionTokens,
|
||||||
|
compPrice: completionRatioPrice,
|
||||||
|
ratio: groupRatio,
|
||||||
|
total: price.toFixed(6),
|
||||||
|
},
|
||||||
|
)
|
||||||
: i18next.t(
|
: i18next.t(
|
||||||
'提示 {{input}} tokens / 1M tokens * ${{price}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}',
|
'输入 {{input}} tokens / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}',
|
||||||
{
|
{
|
||||||
input: inputTokens,
|
input: inputTokens,
|
||||||
price: inputRatioPrice,
|
price: inputRatioPrice,
|
||||||
@@ -405,12 +440,53 @@ export function renderModelPrice(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function renderLogContent(
|
||||||
|
modelRatio,
|
||||||
|
completionRatio,
|
||||||
|
modelPrice = -1,
|
||||||
|
groupRatio,
|
||||||
|
user_group_ratio,
|
||||||
|
image = false,
|
||||||
|
imageRatio = 1.0,
|
||||||
|
) {
|
||||||
|
const useUserGroupRatio = isValidGroupRatio(user_group_ratio);
|
||||||
|
const ratioLabel = useUserGroupRatio ? i18next.t('专属倍率') : i18next.t('分组倍率');
|
||||||
|
const ratio = useUserGroupRatio ? user_group_ratio : groupRatio;
|
||||||
|
|
||||||
|
if (modelPrice !== -1) {
|
||||||
|
return i18next.t('模型价格 ${{price}},{{ratioType}} {{ratio}}', {
|
||||||
|
price: modelPrice,
|
||||||
|
ratioType: ratioLabel,
|
||||||
|
ratio
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (image) {
|
||||||
|
return i18next.t('模型倍率 {{modelRatio}},输出倍率 {{completionRatio}},图片输入倍率 {{imageRatio}},{{ratioType}} {{ratio}}', {
|
||||||
|
modelRatio: modelRatio,
|
||||||
|
completionRatio: completionRatio,
|
||||||
|
imageRatio: imageRatio,
|
||||||
|
ratioType: ratioLabel,
|
||||||
|
ratio
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return i18next.t('模型倍率 {{modelRatio}},输出倍率 {{completionRatio}},{{ratioType}} {{ratio}}', {
|
||||||
|
modelRatio: modelRatio,
|
||||||
|
completionRatio: completionRatio,
|
||||||
|
ratioType: ratioLabel,
|
||||||
|
ratio
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function renderModelPriceSimple(
|
export function renderModelPriceSimple(
|
||||||
modelRatio,
|
modelRatio,
|
||||||
modelPrice = -1,
|
modelPrice = -1,
|
||||||
groupRatio,
|
groupRatio,
|
||||||
cacheTokens = 0,
|
cacheTokens = 0,
|
||||||
cacheRatio = 1.0,
|
cacheRatio = 1.0,
|
||||||
|
image = false,
|
||||||
|
imageRatio = 1.0,
|
||||||
) {
|
) {
|
||||||
if (modelPrice !== -1) {
|
if (modelPrice !== -1) {
|
||||||
return i18next.t('价格:${{price}} * 分组:{{ratio}}', {
|
return i18next.t('价格:${{price}} * 分组:{{ratio}}', {
|
||||||
@@ -418,7 +494,28 @@ export function renderModelPriceSimple(
|
|||||||
ratio: groupRatio,
|
ratio: groupRatio,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (cacheTokens !== 0) {
|
if (image && cacheTokens !== 0) {
|
||||||
|
return i18next.t(
|
||||||
|
'模型: {{ratio}} * {{ratioType}}: {{groupRatio}} * 缓存倍率: {{cacheRatio}} * 图片输入倍率: {{imageRatio}}',
|
||||||
|
{
|
||||||
|
ratio: modelRatio,
|
||||||
|
ratioType: ratioLabel,
|
||||||
|
groupRatio: groupRatio,
|
||||||
|
cacheRatio: cacheRatio,
|
||||||
|
imageRatio: imageRatio,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if (image) {
|
||||||
|
return i18next.t(
|
||||||
|
'模型: {{ratio}} * {{ratioType}}: {{groupRatio}} * 图片输入倍率: {{imageRatio}}',
|
||||||
|
{
|
||||||
|
ratio: modelRatio,
|
||||||
|
ratioType: ratioLabel,
|
||||||
|
groupRatio: groupRatio,
|
||||||
|
imageRatio: imageRatio,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if (cacheTokens !== 0) {
|
||||||
return i18next.t(
|
return i18next.t(
|
||||||
'模型: {{ratio}} * 分组: {{groupRatio}} * 缓存: {{cacheRatio}}',
|
'模型: {{ratio}} * 分组: {{groupRatio}} * 缓存: {{cacheRatio}}',
|
||||||
{
|
{
|
||||||
@@ -882,7 +979,7 @@ export function renderClaudeLogContent(
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return i18next.t(
|
return i18next.t(
|
||||||
'模型倍率 {{modelRatio}},补全倍率 {{completionRatio}},缓存倍率 {{cacheRatio}},缓存创建倍率 {{cacheCreationRatio}},{{ratioType}} {{ratio}}',
|
'模型倍率 {{modelRatio}},输出倍率 {{completionRatio}},缓存倍率 {{cacheRatio}},缓存创建倍率 {{cacheCreationRatio}},{{ratioType}} {{ratio}}',
|
||||||
{
|
{
|
||||||
modelRatio: modelRatio,
|
modelRatio: modelRatio,
|
||||||
completionRatio: completionRatio,
|
completionRatio: completionRatio,
|
||||||
@@ -933,30 +1030,3 @@ export function renderClaudeModelPriceSimple(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderLogContent(
|
|
||||||
modelRatio,
|
|
||||||
completionRatio,
|
|
||||||
modelPrice = -1,
|
|
||||||
groupRatio,
|
|
||||||
) {
|
|
||||||
const ratioLabel = false ? i18next.t('专属倍率') : i18next.t('分组倍率');
|
|
||||||
|
|
||||||
if (modelPrice !== -1) {
|
|
||||||
return i18next.t('模型价格 ${{price}},{{ratioType}} {{ratio}}', {
|
|
||||||
price: modelPrice,
|
|
||||||
ratioType: ratioLabel,
|
|
||||||
ratio: groupRatio,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return i18next.t(
|
|
||||||
'模型倍率 {{modelRatio}},补全倍率 {{completionRatio}},{{ratioType}} {{ratio}}',
|
|
||||||
{
|
|
||||||
modelRatio: modelRatio,
|
|
||||||
completionRatio: completionRatio,
|
|
||||||
ratioType: ratioLabel,
|
|
||||||
ratio: groupRatio,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -679,7 +679,10 @@
|
|||||||
"当前分组可用": "Available in current group",
|
"当前分组可用": "Available in current group",
|
||||||
"当前分组不可用": "The current group is unavailable",
|
"当前分组不可用": "The current group is unavailable",
|
||||||
"提示:": "input:",
|
"提示:": "input:",
|
||||||
|
"输入:": "input:",
|
||||||
"补全:": "output:",
|
"补全:": "output:",
|
||||||
|
"输出:": "output:",
|
||||||
|
"图片输出:": "Image output:",
|
||||||
"模型价格:": "Model price:",
|
"模型价格:": "Model price:",
|
||||||
"模型:": "Model:",
|
"模型:": "Model:",
|
||||||
"分组:": "Grouping:",
|
"分组:": "Grouping:",
|
||||||
@@ -1054,14 +1057,16 @@
|
|||||||
"等级": "grade",
|
"等级": "grade",
|
||||||
"钉钉": "DingTalk",
|
"钉钉": "DingTalk",
|
||||||
"模型价格:${{price}} * 分组倍率:{{ratio}} = ${{total}}": "Model price: ${{price}} * Group ratio: {{ratio}} = ${{total}}",
|
"模型价格:${{price}} * 分组倍率:{{ratio}} = ${{total}}": "Model price: ${{price}} * Group ratio: {{ratio}} = ${{total}}",
|
||||||
"提示:${{price}} * {{ratio}} = ${{total}} / 1M tokens": "Prompt: ${{price}} * {{ratio}} = ${{total}} / 1M tokens",
|
"输入:${{price}} * {{ratio}} = ${{total}} / 1M tokens": "Prompt: ${{price}} * {{ratio}} = ${{total}} / 1M tokens",
|
||||||
"补全:${{price}} * {{ratio}} = ${{total}} / 1M tokens": "Completion: ${{price}} * {{ratio}} = ${{total}} / 1M tokens",
|
"输出:${{price}} * {{ratio}} = ${{total}} / 1M tokens": "Completion: ${{price}} * {{ratio}} = ${{total}} / 1M tokens",
|
||||||
"音频提示:${{price}} * {{ratio}} * {{audioRatio}} = ${{total}} / 1M tokens": "Audio prompt: ${{price}} * {{ratio}} * {{audioRatio}} = ${{total}} / 1M tokens",
|
"图片输入:${{price}} * {{ratio}} = ${{total}} / 1M tokens (图片倍率: {{imageRatio}})": "Image input: ${{price}} * {{ratio}} = ${{total}} / 1M tokens (Image ratio: {{imageRatio}})",
|
||||||
|
"音频输入:${{price}} * {{ratio}} * {{audioRatio}} = ${{total}} / 1M tokens": "Audio prompt: ${{price}} * {{ratio}} * {{audioRatio}} = ${{total}} / 1M tokens",
|
||||||
"音频提示 {{input}} tokens / 1M tokens * ${{price}} * {{audioRatio}} + 音频补全 {{completion}} tokens / 1M tokens * ${{price}} * {{audioRatio}} * {{audioCompRatio}}": "Audio prompt {{input}} tokens / 1M tokens * ${{price}} * {{audioRatio}} + Audio completion {{completion}} tokens / 1M tokens * ${{price}} * {{audioRatio}} * {{audioCompRatio}}",
|
"音频提示 {{input}} tokens / 1M tokens * ${{price}} * {{audioRatio}} + 音频补全 {{completion}} tokens / 1M tokens * ${{price}} * {{audioRatio}} * {{audioCompRatio}}": "Audio prompt {{input}} tokens / 1M tokens * ${{price}} * {{audioRatio}} + Audio completion {{completion}} tokens / 1M tokens * ${{price}} * {{audioRatio}} * {{audioCompRatio}}",
|
||||||
"音频补全:${{price}} * {{ratio}} * {{audioRatio}} * {{audioCompRatio}} = ${{total}} / 1M tokens": "Audio completion: ${{price}} * {{ratio}} * {{audioRatio}} * {{audioCompRatio}} = ${{total}} / 1M tokens",
|
"音频输出:${{price}} * {{ratio}} * {{audioRatio}} * {{audioCompRatio}} = ${{total}} / 1M tokens": "Audio completion: ${{price}} * {{ratio}} * {{audioRatio}} * {{audioCompRatio}} = ${{total}} / 1M tokens",
|
||||||
|
"输入 {{nonImageInput}} tokens + 图片输入 {{imageInput}} tokens * {{imageRatio}} / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}": "Input {{nonImageInput}} tokens + Image input {{imageInput}} tokens * {{imageRatio}} / 1M tokens * ${{price}} + Output {{completion}} tokens / 1M tokens * ${{compPrice}} * Group {{ratio}} = ${{total}}",
|
||||||
"(文字 + 音频)* 分组倍率 {{ratio}} = ${{total}}": "(Text + Audio) * Group ratio {{ratio}} = ${{total}}",
|
"(文字 + 音频)* 分组倍率 {{ratio}} = ${{total}}": "(Text + Audio) * Group ratio {{ratio}} = ${{total}}",
|
||||||
"文字提示 {{input}} tokens / 1M tokens * ${{price}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} +": "Text prompt {{input}} tokens / 1M tokens * ${{price}} + Text completion {{completion}} tokens / 1M tokens * ${{compPrice}} +",
|
"文字提示 {{input}} tokens / 1M tokens * ${{price}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} +": "Text prompt {{input}} tokens / 1M tokens * ${{price}} + Text completion {{completion}} tokens / 1M tokens * ${{compPrice}} +",
|
||||||
"提示 {{input}} tokens / 1M tokens * ${{price}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}": "Prompt {{input}} tokens / 1M tokens * ${{price}} + Completion {{completion}} tokens / 1M tokens * ${{compPrice}} * Group {{ratio}} = ${{total}}",
|
"输入 {{input}} tokens / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}": "Prompt {{input}} tokens / 1M tokens * ${{price}} + Completion {{completion}} tokens / 1M tokens * ${{compPrice}} * Group {{ratio}} = ${{total}}",
|
||||||
"价格:${{price}} * 分组:{{ratio}}": "Price: ${{price}} * Group: {{ratio}}",
|
"价格:${{price}} * 分组:{{ratio}}": "Price: ${{price}} * Group: {{ratio}}",
|
||||||
"模型: {{ratio}} * 分组: {{groupRatio}}": "Model: {{ratio}} * Group: {{groupRatio}}",
|
"模型: {{ratio}} * 分组: {{groupRatio}}": "Model: {{ratio}} * Group: {{groupRatio}}",
|
||||||
"统计额度": "Statistical quota",
|
"统计额度": "Statistical quota",
|
||||||
|
|||||||
Reference in New Issue
Block a user