From cb0d3d60d310405f9653218dc894846f3f1bcbed Mon Sep 17 00:00:00 2001 From: Seefs Date: Sun, 14 Sep 2025 12:59:44 +0800 Subject: [PATCH 1/8] fix: settings --- service/cf_worker.go | 12 ++++++------ service/user_notify.go | 6 +++--- service/webhook.go | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/service/cf_worker.go b/service/cf_worker.go index 4a7b437603..d60b6fad5f 100644 --- a/service/cf_worker.go +++ b/service/cf_worker.go @@ -6,7 +6,7 @@ import ( "fmt" "net/http" "one-api/common" - "one-api/setting" + "one-api/setting/system_setting" "strings" ) @@ -21,14 +21,14 @@ type WorkerRequest struct { // DoWorkerRequest 通过Worker发送请求 func DoWorkerRequest(req *WorkerRequest) (*http.Response, error) { - if !setting.EnableWorker() { + if !system_setting.EnableWorker() { return nil, fmt.Errorf("worker not enabled") } - if !setting.WorkerAllowHttpImageRequestEnabled && !strings.HasPrefix(req.URL, "https") { + if !system_setting.WorkerAllowHttpImageRequestEnabled && !strings.HasPrefix(req.URL, "https") { return nil, fmt.Errorf("only support https url") } - workerUrl := setting.WorkerUrl + workerUrl := system_setting.WorkerUrl if !strings.HasSuffix(workerUrl, "/") { workerUrl += "/" } @@ -43,11 +43,11 @@ func DoWorkerRequest(req *WorkerRequest) (*http.Response, error) { } func DoDownloadRequest(originUrl string, reason ...string) (resp *http.Response, err error) { - if setting.EnableWorker() { + if system_setting.EnableWorker() { common.SysLog(fmt.Sprintf("downloading file from worker: %s, reason: %s", originUrl, strings.Join(reason, ", "))) req := &WorkerRequest{ URL: originUrl, - Key: setting.WorkerValidKey, + Key: system_setting.WorkerValidKey, } return DoWorkerRequest(req) } else { diff --git a/service/user_notify.go b/service/user_notify.go index c4a3ea91f9..972ca655c5 100644 --- a/service/user_notify.go +++ b/service/user_notify.go @@ -7,7 +7,7 @@ import ( "one-api/common" "one-api/dto" "one-api/model" - "one-api/setting" + "one-api/setting/system_setting" "strings" ) @@ -91,11 +91,11 @@ func sendBarkNotify(barkURL string, data dto.Notify) error { var resp *http.Response var err error - if setting.EnableWorker() { + if system_setting.EnableWorker() { // 使用worker发送请求 workerReq := &WorkerRequest{ URL: finalURL, - Key: setting.WorkerValidKey, + Key: system_setting.WorkerValidKey, Method: http.MethodGet, Headers: map[string]string{ "User-Agent": "OneAPI-Bark-Notify/1.0", diff --git a/service/webhook.go b/service/webhook.go index 8faccda301..9c6ec81027 100644 --- a/service/webhook.go +++ b/service/webhook.go @@ -9,7 +9,7 @@ import ( "fmt" "net/http" "one-api/dto" - "one-api/setting" + "one-api/setting/system_setting" "time" ) @@ -56,11 +56,11 @@ func SendWebhookNotify(webhookURL string, secret string, data dto.Notify) error var req *http.Request var resp *http.Response - if setting.EnableWorker() { + if system_setting.EnableWorker() { // 构建worker请求数据 workerReq := &WorkerRequest{ URL: webhookURL, - Key: setting.WorkerValidKey, + Key: system_setting.WorkerValidKey, Method: http.MethodPost, Headers: map[string]string{ "Content-Type": "application/json", From 6b028bfedb1513e83bc17e6bd88c199b0af3e7f7 Mon Sep 17 00:00:00 2001 From: feitianbubu Date: Mon, 15 Sep 2025 14:31:55 +0800 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E5=8D=B3?= =?UTF-8?q?=E6=A2=A6=E8=A7=86=E9=A2=913.0,=E6=96=B0=E5=A2=9E10s(frames=3D2?= =?UTF-8?q?41)=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- relay/channel/task/jimeng/adaptor.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/relay/channel/task/jimeng/adaptor.go b/relay/channel/task/jimeng/adaptor.go index 2bc45c547f..e870a6590a 100644 --- a/relay/channel/task/jimeng/adaptor.go +++ b/relay/channel/task/jimeng/adaptor.go @@ -36,6 +36,7 @@ type requestPayload struct { Prompt string `json:"prompt,omitempty"` Seed int64 `json:"seed"` AspectRatio string `json:"aspect_ratio"` + Frames int `json:"frames,omitempty"` } type responsePayload struct { @@ -311,10 +312,15 @@ func hmacSHA256(key []byte, data []byte) []byte { func (a *TaskAdaptor) convertToRequestPayload(req *relaycommon.TaskSubmitReq) (*requestPayload, error) { r := requestPayload{ - ReqKey: "jimeng_vgfm_i2v_l20", - Prompt: req.Prompt, - AspectRatio: "16:9", // Default aspect ratio - Seed: -1, // Default to random + ReqKey: req.Model, + Prompt: req.Prompt, + } + + switch req.Duration { + case 10: + r.Frames = 241 // 24*10+1 = 241 + default: + r.Frames = 121 // 24*5+1 = 121 } // Handle one-of image_urls or binary_data_base64 From cf446438ed4ab41b19e97e65dc8cc7f29e5eac90 Mon Sep 17 00:00:00 2001 From: feitianbubu Date: Mon, 15 Sep 2025 15:53:41 +0800 Subject: [PATCH 3/8] feat: jimeng video 3.0 req_key convert --- relay/channel/task/jimeng/adaptor.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/relay/channel/task/jimeng/adaptor.go b/relay/channel/task/jimeng/adaptor.go index e870a6590a..b954d7b88d 100644 --- a/relay/channel/task/jimeng/adaptor.go +++ b/relay/channel/task/jimeng/adaptor.go @@ -340,6 +340,22 @@ func (a *TaskAdaptor) convertToRequestPayload(req *relaycommon.TaskSubmitReq) (* if err != nil { return nil, errors.Wrap(err, "unmarshal metadata failed") } + + // 即梦视频3.0 ReqKey转换 + // https://www.volcengine.com/docs/85621/1792707 + if strings.Contains(r.ReqKey, "jimeng_v30") { + if len(r.ImageUrls) > 1 { + // 多张图片:首尾帧生成 + r.ReqKey = strings.Replace(r.ReqKey, "jimeng_v30", "jimeng_i2v_first_tail_v30", 1) + } else if len(r.ImageUrls) == 1 { + // 单张图片:图生视频 + r.ReqKey = strings.Replace(r.ReqKey, "jimeng_v30", "jimeng_i2v_first_v30", 1) + } else { + // 无图片:文生视频 + r.ReqKey = strings.Replace(r.ReqKey, "jimeng_v30", "jimeng_t2v_v30", 1) + } + } + return &r, nil } From 8f5c29342dba75e470b359deee7404b056b46f9a Mon Sep 17 00:00:00 2001 From: creamlike1024 Date: Mon, 15 Sep 2025 16:22:37 +0800 Subject: [PATCH 4/8] =?UTF-8?q?fix:=20stripe=E6=94=AF=E4=BB=98=E6=88=90?= =?UTF-8?q?=E5=8A=9F=E6=9C=AA=E6=AD=A3=E7=A1=AE=E8=B7=B3=E8=BD=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/topup_stripe.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/topup_stripe.go b/controller/topup_stripe.go index d462acb4b8..ccde91dbec 100644 --- a/controller/topup_stripe.go +++ b/controller/topup_stripe.go @@ -217,7 +217,7 @@ func genStripeLink(referenceId string, customerId string, email string, amount i params := &stripe.CheckoutSessionParams{ ClientReferenceID: stripe.String(referenceId), - SuccessURL: stripe.String(system_setting.ServerAddress + "/log"), + SuccessURL: stripe.String(system_setting.ServerAddress + "/console/log"), CancelURL: stripe.String(system_setting.ServerAddress + "/topup"), LineItems: []*stripe.CheckoutSessionLineItemParams{ { From f6984272bf5dff6be6d59970d9125058be171296 Mon Sep 17 00:00:00 2001 From: creamlike1024 Date: Tue, 16 Sep 2025 12:47:59 +0800 Subject: [PATCH 5/8] =?UTF-8?q?fix:=20openai=20responses=20api=20=E6=9C=AA?= =?UTF-8?q?=E7=BB=9F=E8=AE=A1=E5=9B=BE=E5=83=8F=E7=94=9F=E6=88=90=E8=B0=83?= =?UTF-8?q?=E7=94=A8=E8=AE=A1=E8=B4=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dto/openai_response.go | 42 +++++++++++++++++++ relay/channel/openai/relay_responses.go | 35 +++++++++++----- relay/compatible_handler.go | 13 ++++++ setting/operation_setting/tools.go | 40 ++++++++++++++++++ web/src/helpers/render.jsx | 23 +++++++++- web/src/hooks/usage-logs/useUsageLogsData.jsx | 2 + 6 files changed, 142 insertions(+), 13 deletions(-) diff --git a/dto/openai_response.go b/dto/openai_response.go index 966748cb58..6353c15ffe 100644 --- a/dto/openai_response.go +++ b/dto/openai_response.go @@ -6,6 +6,10 @@ import ( "one-api/types" ) +const ( + ResponsesOutputTypeImageGenerationCall = "image_generation_call" +) + type SimpleResponse struct { Usage `json:"usage"` Error any `json:"error"` @@ -273,6 +277,42 @@ func (o *OpenAIResponsesResponse) GetOpenAIError() *types.OpenAIError { return GetOpenAIError(o.Error) } +func (o *OpenAIResponsesResponse) HasImageGenerationCall() bool { + if len(o.Output) == 0 { + return false + } + for _, output := range o.Output { + if output.Type == ResponsesOutputTypeImageGenerationCall { + return true + } + } + return false +} + +func (o *OpenAIResponsesResponse) GetQuality() string { + if len(o.Output) == 0 { + return "" + } + for _, output := range o.Output { + if output.Type == ResponsesOutputTypeImageGenerationCall { + return output.Quality + } + } + return "" +} + +func (o *OpenAIResponsesResponse) GetSize() string { + if len(o.Output) == 0 { + return "" + } + for _, output := range o.Output { + if output.Type == ResponsesOutputTypeImageGenerationCall { + return output.Size + } + } + return "" +} + type IncompleteDetails struct { Reasoning string `json:"reasoning"` } @@ -283,6 +323,8 @@ type ResponsesOutput struct { Status string `json:"status"` Role string `json:"role"` Content []ResponsesOutputContent `json:"content"` + Quality string `json:"quality"` + Size string `json:"size"` } type ResponsesOutputContent struct { diff --git a/relay/channel/openai/relay_responses.go b/relay/channel/openai/relay_responses.go index e188889e43..85938a7715 100644 --- a/relay/channel/openai/relay_responses.go +++ b/relay/channel/openai/relay_responses.go @@ -33,6 +33,12 @@ func OaiResponsesHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http return nil, types.WithOpenAIError(*oaiError, resp.StatusCode) } + if responsesResponse.HasImageGenerationCall() { + c.Set("image_generation_call", true) + c.Set("image_generation_call_quality", responsesResponse.GetQuality()) + c.Set("image_generation_call_size", responsesResponse.GetSize()) + } + // 写入新的 response body service.IOCopyBytesGracefully(c, resp, responseBody) @@ -80,18 +86,25 @@ func OaiResponsesStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, resp sendResponsesStreamData(c, streamResponse, data) switch streamResponse.Type { case "response.completed": - if streamResponse.Response != nil && streamResponse.Response.Usage != nil { - if streamResponse.Response.Usage.InputTokens != 0 { - usage.PromptTokens = streamResponse.Response.Usage.InputTokens + if streamResponse.Response != nil { + if streamResponse.Response.Usage != nil { + if streamResponse.Response.Usage.InputTokens != 0 { + usage.PromptTokens = streamResponse.Response.Usage.InputTokens + } + if streamResponse.Response.Usage.OutputTokens != 0 { + usage.CompletionTokens = streamResponse.Response.Usage.OutputTokens + } + if streamResponse.Response.Usage.TotalTokens != 0 { + usage.TotalTokens = streamResponse.Response.Usage.TotalTokens + } + if streamResponse.Response.Usage.InputTokensDetails != nil { + usage.PromptTokensDetails.CachedTokens = streamResponse.Response.Usage.InputTokensDetails.CachedTokens + } } - if streamResponse.Response.Usage.OutputTokens != 0 { - usage.CompletionTokens = streamResponse.Response.Usage.OutputTokens - } - if streamResponse.Response.Usage.TotalTokens != 0 { - usage.TotalTokens = streamResponse.Response.Usage.TotalTokens - } - if streamResponse.Response.Usage.InputTokensDetails != nil { - usage.PromptTokensDetails.CachedTokens = streamResponse.Response.Usage.InputTokensDetails.CachedTokens + if streamResponse.Response.HasImageGenerationCall() { + c.Set("image_generation_call", true) + c.Set("image_generation_call_quality", streamResponse.Response.GetQuality()) + c.Set("image_generation_call_size", streamResponse.Response.GetSize()) } } case "response.output_text.delta": diff --git a/relay/compatible_handler.go b/relay/compatible_handler.go index 01ab1fff4a..c931fe2a06 100644 --- a/relay/compatible_handler.go +++ b/relay/compatible_handler.go @@ -276,6 +276,13 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage fileSearchTool.CallCount, dFileSearchQuota.String()) } } + var dImageGenerationCallQuota decimal.Decimal + var imageGenerationCallPrice float64 + if ctx.GetBool("image_generation_call") { + imageGenerationCallPrice = operation_setting.GetGPTImage1PriceOnceCall(ctx.GetString("image_generation_call_quality"), ctx.GetString("image_generation_call_size")) + dImageGenerationCallQuota = decimal.NewFromFloat(imageGenerationCallPrice).Mul(dGroupRatio).Mul(dQuotaPerUnit) + extraContent += fmt.Sprintf("Image Generation Call 花费 %s", dImageGenerationCallQuota.String()) + } var quotaCalculateDecimal decimal.Decimal @@ -331,6 +338,8 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage quotaCalculateDecimal = quotaCalculateDecimal.Add(dFileSearchQuota) // 添加 audio input 独立计费 quotaCalculateDecimal = quotaCalculateDecimal.Add(audioInputQuota) + // 添加 image generation call 计费 + quotaCalculateDecimal = quotaCalculateDecimal.Add(dImageGenerationCallQuota) quota := int(quotaCalculateDecimal.Round(0).IntPart()) totalTokens := promptTokens + completionTokens @@ -429,6 +438,10 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage other["audio_input_token_count"] = audioTokens other["audio_input_price"] = audioInputPrice } + if !dImageGenerationCallQuota.IsZero() { + other["image_generation_call"] = true + other["image_generation_call_price"] = imageGenerationCallPrice + } model.RecordConsumeLog(ctx, relayInfo.UserId, model.RecordConsumeLogParams{ ChannelId: relayInfo.ChannelId, PromptTokens: promptTokens, diff --git a/setting/operation_setting/tools.go b/setting/operation_setting/tools.go index 549a1862e1..5b89d6fec0 100644 --- a/setting/operation_setting/tools.go +++ b/setting/operation_setting/tools.go @@ -10,6 +10,18 @@ const ( FileSearchPrice = 2.5 ) +const ( + GPTImage1Low1024x1024 = 0.011 + GPTImage1Low1024x1536 = 0.016 + GPTImage1Low1536x1024 = 0.016 + GPTImage1Medium1024x1024 = 0.042 + GPTImage1Medium1024x1536 = 0.063 + GPTImage1Medium1536x1024 = 0.063 + GPTImage1High1024x1024 = 0.167 + GPTImage1High1024x1536 = 0.25 + GPTImage1High1536x1024 = 0.25 +) + const ( // Gemini Audio Input Price Gemini25FlashPreviewInputAudioPrice = 1.00 @@ -65,3 +77,31 @@ func GetGeminiInputAudioPricePerMillionTokens(modelName string) float64 { } return 0 } + +func GetGPTImage1PriceOnceCall(quality string, size string) float64 { + prices := map[string]map[string]float64{ + "low": { + "1024x1024": GPTImage1Low1024x1024, + "1024x1536": GPTImage1Low1024x1536, + "1536x1024": GPTImage1Low1536x1024, + }, + "medium": { + "1024x1024": GPTImage1Medium1024x1024, + "1024x1536": GPTImage1Medium1024x1536, + "1536x1024": GPTImage1Medium1536x1024, + }, + "high": { + "1024x1024": GPTImage1High1024x1024, + "1024x1536": GPTImage1High1024x1536, + "1536x1024": GPTImage1High1536x1024, + }, + } + + if qualityMap, exists := prices[quality]; exists { + if price, exists := qualityMap[size]; exists { + return price + } + } + + return GPTImage1High1024x1024 +} diff --git a/web/src/helpers/render.jsx b/web/src/helpers/render.jsx index 65332701bb..c331d7fe85 100644 --- a/web/src/helpers/render.jsx +++ b/web/src/helpers/render.jsx @@ -1027,6 +1027,8 @@ export function renderModelPrice( audioInputSeperatePrice = false, audioInputTokens = 0, audioInputPrice = 0, + imageGenerationCall = false, + imageGenerationCallPrice = 0, ) { const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio( groupRatio, @@ -1069,7 +1071,8 @@ export function renderModelPrice( (audioInputTokens / 1000000) * audioInputPrice * groupRatio + (completionTokens / 1000000) * completionRatioPrice * groupRatio + (webSearchCallCount / 1000) * webSearchPrice * groupRatio + - (fileSearchCallCount / 1000) * fileSearchPrice * groupRatio; + (fileSearchCallCount / 1000) * fileSearchPrice * groupRatio + + (imageGenerationCall * imageGenerationCallPrice * groupRatio); return ( <> @@ -1131,7 +1134,13 @@ export function renderModelPrice( })}

)} -

+ {imageGenerationCall && imageGenerationCallPrice > 0 && ( +

+ {i18next.t('图片生成调用:${{price}} / 1次', { + price: imageGenerationCallPrice, + })} +

+ )}

{(() => { // 构建输入部分描述 @@ -1211,6 +1220,16 @@ export function renderModelPrice( }, ) : '', + imageGenerationCall && imageGenerationCallPrice > 0 + ? i18next.t( + ' + 图片生成调用 ${{price}} / 1次 * {{ratioType}} {{ratio}}', + { + price: imageGenerationCallPrice, + ratio: groupRatio, + ratioType: ratioLabel, + }, + ) + : '', ].join(''); return i18next.t( diff --git a/web/src/hooks/usage-logs/useUsageLogsData.jsx b/web/src/hooks/usage-logs/useUsageLogsData.jsx index 81f3f539a7..d434e7333b 100644 --- a/web/src/hooks/usage-logs/useUsageLogsData.jsx +++ b/web/src/hooks/usage-logs/useUsageLogsData.jsx @@ -447,6 +447,8 @@ export const useLogsData = () => { other?.audio_input_seperate_price || false, other?.audio_input_token_count || 0, other?.audio_input_price || 0, + other?.image_generation_call || false, + other?.image_generation_call_price || 0, ); } expandDataLocal.push({ From b9befccd9e6ff41ecb47a4a77a8b59ba9a7b4556 Mon Sep 17 00:00:00 2001 From: creamlike1024 Date: Tue, 16 Sep 2025 13:02:15 +0800 Subject: [PATCH 6/8] fix: imageGenerationCall involves billing --- web/src/helpers/render.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/helpers/render.jsx b/web/src/helpers/render.jsx index c331d7fe85..c19e2849d9 100644 --- a/web/src/helpers/render.jsx +++ b/web/src/helpers/render.jsx @@ -1072,7 +1072,7 @@ export function renderModelPrice( (completionTokens / 1000000) * completionRatioPrice * groupRatio + (webSearchCallCount / 1000) * webSearchPrice * groupRatio + (fileSearchCallCount / 1000) * fileSearchPrice * groupRatio + - (imageGenerationCall * imageGenerationCallPrice * groupRatio); + (imageGenerationCallPrice * groupRatio); return ( <> From 97755c2e94c3907237f7c331ce421b9a6857b242 Mon Sep 17 00:00:00 2001 From: huanghejian Date: Tue, 16 Sep 2025 14:30:12 +0800 Subject: [PATCH 7/8] fix: VolcEngine doubao-seedream-4-0-250828 --- controller/channel-test.go | 39 +++++++++++++++++++++++++++ relay/channel/volcengine/adaptor.go | 2 ++ relay/channel/volcengine/constants.go | 1 + 3 files changed, 42 insertions(+) diff --git a/controller/channel-test.go b/controller/channel-test.go index 5a668c4884..9ea6eed75c 100644 --- a/controller/channel-test.go +++ b/controller/channel-test.go @@ -90,6 +90,11 @@ func testChannel(channel *model.Channel, testModel string) testResult { requestPath = "/v1/embeddings" // 修改请求路径 } + // VolcEngine 图像生成模型 + if channel.Type == constant.ChannelTypeVolcEngine && strings.Contains(testModel, "seedream") { + requestPath = "/v1/images/generations" + } + c.Request = &http.Request{ Method: "POST", URL: &url.URL{Path: requestPath}, // 使用动态路径 @@ -109,6 +114,21 @@ func testChannel(channel *model.Channel, testModel string) testResult { } } + // 重新检查模型类型并更新请求路径 + if strings.Contains(strings.ToLower(testModel), "embedding") || + strings.HasPrefix(testModel, "m3e") || + strings.Contains(testModel, "bge-") || + strings.Contains(testModel, "embed") || + channel.Type == constant.ChannelTypeMokaAI { + requestPath = "/v1/embeddings" + c.Request.URL.Path = requestPath + } + + if channel.Type == constant.ChannelTypeVolcEngine && strings.Contains(testModel, "seedream") { + requestPath = "/v1/images/generations" + c.Request.URL.Path = requestPath + } + cache, err := model.GetUserCache(1) if err != nil { return testResult{ @@ -140,6 +160,9 @@ func testChannel(channel *model.Channel, testModel string) testResult { if c.Request.URL.Path == "/v1/embeddings" { relayFormat = types.RelayFormatEmbedding } + if c.Request.URL.Path == "/v1/images/generations" { + relayFormat = types.RelayFormatOpenAIImage + } info, err := relaycommon.GenRelayInfo(c, relayFormat, request, nil) @@ -201,6 +224,22 @@ func testChannel(channel *model.Channel, testModel string) testResult { } // 调用专门用于 Embedding 的转换函数 convertedRequest, err = adaptor.ConvertEmbeddingRequest(c, info, embeddingRequest) + } else if info.RelayMode == relayconstant.RelayModeImagesGenerations { + // 创建一个 ImageRequest + prompt := "cat" + if request.Prompt != nil { + if promptStr, ok := request.Prompt.(string); ok && promptStr != "" { + prompt = promptStr + } + } + imageRequest := dto.ImageRequest{ + Prompt: prompt, + Model: request.Model, + N: uint(request.N), + Size: request.Size, + } + // 调用专门用于图像生成的转换函数 + convertedRequest, err = adaptor.ConvertImageRequest(c, info, imageRequest) } else { // 对其他所有请求类型(如 Chat),保持原有逻辑 convertedRequest, err = adaptor.ConvertOpenAIRequest(c, info, request) diff --git a/relay/channel/volcengine/adaptor.go b/relay/channel/volcengine/adaptor.go index 0af019da48..eb88412af7 100644 --- a/relay/channel/volcengine/adaptor.go +++ b/relay/channel/volcengine/adaptor.go @@ -41,6 +41,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) { switch info.RelayMode { + case constant.RelayModeImagesGenerations: + return request, nil case constant.RelayModeImagesEdits: var requestBody bytes.Buffer diff --git a/relay/channel/volcengine/constants.go b/relay/channel/volcengine/constants.go index 30cc902e77..fca10e7c12 100644 --- a/relay/channel/volcengine/constants.go +++ b/relay/channel/volcengine/constants.go @@ -8,6 +8,7 @@ var ModelList = []string{ "Doubao-lite-32k", "Doubao-lite-4k", "Doubao-embedding", + "doubao-seedream-4-0-250828", } var ChannelName = "volcengine" From 6c7d28af0b692a6a2c5e9c3b34dd3f45170451b0 Mon Sep 17 00:00:00 2001 From: t0ng7u Date: Tue, 16 Sep 2025 17:21:22 +0800 Subject: [PATCH 8/8] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20fix:=20Align=20setu?= =?UTF-8?q?p=20API=20errors=20to=20HTTP=20200=20with=20{success:false,=20m?= =?UTF-8?q?essage}?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unify the setup initialization endpoint’s error contract to match the rest of the project and keep the frontend unchanged. Changes - controller/setup.go: Return HTTP 200 with {success:false, message} for all predictable errors in POST /api/setup, including: - already initialized - invalid payload - username too long - password mismatch - password too short - password hashing failure - root user creation failure - option persistence failures (SelfUseModeEnabled, DemoSiteEnabled) - setup record creation failure - web/src/components/setup/SetupWizard.jsx: Restore catch handler to the previous generic toast (frontend logic unchanged). - web/src/helpers/utils.jsx: Restore the original showError implementation (no Axios response.data parsing required). Why - Keep API behavior consistent across endpoints so the UI can rely on the success flag and message in the normal .then() flow instead of falling into Axios 4xx errors that only show a generic "400". Impact - UI now displays specific server messages during initialization without frontend adaptations. - Note: clients relying solely on HTTP status codes for error handling should inspect the JSON body (success/message) instead. No changes to the happy path; initialization success responses are unchanged. --- controller/setup.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/controller/setup.go b/controller/setup.go index 44a7b3a732..3ae255e94b 100644 --- a/controller/setup.go +++ b/controller/setup.go @@ -53,7 +53,7 @@ func GetSetup(c *gin.Context) { func PostSetup(c *gin.Context) { // Check if setup is already completed if constant.Setup { - c.JSON(400, gin.H{ + c.JSON(200, gin.H{ "success": false, "message": "系统已经初始化完成", }) @@ -66,7 +66,7 @@ func PostSetup(c *gin.Context) { var req SetupRequest err := c.ShouldBindJSON(&req) if err != nil { - c.JSON(400, gin.H{ + c.JSON(200, gin.H{ "success": false, "message": "请求参数有误", }) @@ -77,7 +77,7 @@ func PostSetup(c *gin.Context) { if !rootExists { // Validate username length: max 12 characters to align with model.User validation if len(req.Username) > 12 { - c.JSON(400, gin.H{ + c.JSON(200, gin.H{ "success": false, "message": "用户名长度不能超过12个字符", }) @@ -85,7 +85,7 @@ func PostSetup(c *gin.Context) { } // Validate password if req.Password != req.ConfirmPassword { - c.JSON(400, gin.H{ + c.JSON(200, gin.H{ "success": false, "message": "两次输入的密码不一致", }) @@ -93,7 +93,7 @@ func PostSetup(c *gin.Context) { } if len(req.Password) < 8 { - c.JSON(400, gin.H{ + c.JSON(200, gin.H{ "success": false, "message": "密码长度至少为8个字符", }) @@ -103,7 +103,7 @@ func PostSetup(c *gin.Context) { // Create root user hashedPassword, err := common.Password2Hash(req.Password) if err != nil { - c.JSON(500, gin.H{ + c.JSON(200, gin.H{ "success": false, "message": "系统错误: " + err.Error(), }) @@ -120,7 +120,7 @@ func PostSetup(c *gin.Context) { } err = model.DB.Create(&rootUser).Error if err != nil { - c.JSON(500, gin.H{ + c.JSON(200, gin.H{ "success": false, "message": "创建管理员账号失败: " + err.Error(), }) @@ -135,7 +135,7 @@ func PostSetup(c *gin.Context) { // Save operation modes to database for persistence err = model.UpdateOption("SelfUseModeEnabled", boolToString(req.SelfUseModeEnabled)) if err != nil { - c.JSON(500, gin.H{ + c.JSON(200, gin.H{ "success": false, "message": "保存自用模式设置失败: " + err.Error(), }) @@ -144,7 +144,7 @@ func PostSetup(c *gin.Context) { err = model.UpdateOption("DemoSiteEnabled", boolToString(req.DemoSiteEnabled)) if err != nil { - c.JSON(500, gin.H{ + c.JSON(200, gin.H{ "success": false, "message": "保存演示站点模式设置失败: " + err.Error(), }) @@ -160,7 +160,7 @@ func PostSetup(c *gin.Context) { } err = model.DB.Create(&setup).Error if err != nil { - c.JSON(500, gin.H{ + c.JSON(200, gin.H{ "success": false, "message": "系统初始化失败: " + err.Error(), })