From 2875dbba1026fb228e66f4de067c3204cbde64fa Mon Sep 17 00:00:00 2001 From: creamlike1024 Date: Sat, 30 Aug 2025 23:28:09 +0800 Subject: [PATCH 01/12] =?UTF-8?q?feat:=20=E5=9B=BE=E5=83=8F=E5=80=8D?= =?UTF-8?q?=E7=8E=87=EF=BC=8C=E9=9F=B3=E9=A2=91=E5=80=8D=E7=8E=87=E5=92=8C?= =?UTF-8?q?=E9=9F=B3=E9=A2=91=E8=A1=A5=E5=85=A8=E5=80=8D=E7=8E=87=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/option.go | 27 ++++ model/option.go | 9 ++ relay/helper/price.go | 6 + setting/ratio_setting/model_ratio.go | 133 +++++++++++++++--- types/price_data.go | 4 +- web/src/components/settings/RatioSetting.jsx | 8 +- web/src/i18n/locales/en.json | 12 +- .../Setting/Ratio/ModelRatioSettings.jsx | 69 +++++++++ 8 files changed, 244 insertions(+), 24 deletions(-) diff --git a/controller/option.go b/controller/option.go index decdb0d405..fb54d20a0d 100644 --- a/controller/option.go +++ b/controller/option.go @@ -112,6 +112,33 @@ func UpdateOption(c *gin.Context) { }) return } + case "ImageRatio": + err = ratio_setting.UpdateImageRatioByJSONString(option.Value) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": "图片倍率设置失败: " + err.Error(), + }) + return + } + case "AudioRatio": + err = ratio_setting.UpdateAudioRatioByJSONString(option.Value) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": "音频倍率设置失败: " + err.Error(), + }) + return + } + case "AudioCompletionRatio": + err = ratio_setting.UpdateAudioCompletionRatioByJSONString(option.Value) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": "音频补全倍率设置失败: " + err.Error(), + }) + return + } case "ModelRequestRateLimitGroup": err = setting.CheckModelRequestRateLimitGroup(option.Value) if err != nil { diff --git a/model/option.go b/model/option.go index 2121710cee..e589b46ed5 100644 --- a/model/option.go +++ b/model/option.go @@ -111,6 +111,9 @@ func InitOptionMap() { common.OptionMap["GroupGroupRatio"] = ratio_setting.GroupGroupRatio2JSONString() common.OptionMap["UserUsableGroups"] = setting.UserUsableGroups2JSONString() common.OptionMap["CompletionRatio"] = ratio_setting.CompletionRatio2JSONString() + common.OptionMap["ImageRatio"] = ratio_setting.ImageRatio2JSONString() + common.OptionMap["AudioRatio"] = ratio_setting.AudioRatio2JSONString() + common.OptionMap["AudioCompletionRatio"] = ratio_setting.AudioCompletionRatio2JSONString() common.OptionMap["TopUpLink"] = common.TopUpLink //common.OptionMap["ChatLink"] = common.ChatLink //common.OptionMap["ChatLink2"] = common.ChatLink2 @@ -396,6 +399,12 @@ func updateOptionMap(key string, value string) (err error) { err = ratio_setting.UpdateModelPriceByJSONString(value) case "CacheRatio": err = ratio_setting.UpdateCacheRatioByJSONString(value) + case "ImageRatio": + err = ratio_setting.UpdateImageRatioByJSONString(value) + case "AudioRatio": + err = ratio_setting.UpdateAudioRatioByJSONString(value) + case "AudioCompletionRatio": + err = ratio_setting.UpdateAudioCompletionRatioByJSONString(value) case "TopUpLink": common.TopUpLink = value //case "ChatLink": diff --git a/relay/helper/price.go b/relay/helper/price.go index fdc5b66d84..c23c068b3e 100644 --- a/relay/helper/price.go +++ b/relay/helper/price.go @@ -52,6 +52,8 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens var cacheRatio float64 var imageRatio float64 var cacheCreationRatio float64 + var audioRatio float64 + var audioCompletionRatio float64 if !usePrice { preConsumedTokens := common.Max(promptTokens, common.PreConsumedQuota) if meta.MaxTokens != 0 { @@ -73,6 +75,8 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens cacheRatio, _ = ratio_setting.GetCacheRatio(info.OriginModelName) cacheCreationRatio, _ = ratio_setting.GetCreateCacheRatio(info.OriginModelName) imageRatio, _ = ratio_setting.GetImageRatio(info.OriginModelName) + audioRatio = ratio_setting.GetAudioRatio(info.OriginModelName) + audioCompletionRatio = ratio_setting.GetAudioCompletionRatio(info.OriginModelName) ratio := modelRatio * groupRatioInfo.GroupRatio preConsumedQuota = int(float64(preConsumedTokens) * ratio) } else { @@ -90,6 +94,8 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens UsePrice: usePrice, CacheRatio: cacheRatio, ImageRatio: imageRatio, + AudioRatio: audioRatio, + AudioCompletionRatio: audioCompletionRatio, CacheCreationRatio: cacheCreationRatio, ShouldPreConsumedQuota: preConsumedQuota, } diff --git a/setting/ratio_setting/model_ratio.go b/setting/ratio_setting/model_ratio.go index f06cd71ef9..99952daf19 100644 --- a/setting/ratio_setting/model_ratio.go +++ b/setting/ratio_setting/model_ratio.go @@ -278,6 +278,18 @@ var defaultModelPrice = map[string]float64{ "mj_upload": 0.05, } +var defaultAudioRatio = map[string]float64{ + "gpt-4o-audio-preview": 16, + "gpt-4o-mini-audio-preview": 66.67, + "gpt-4o-realtime-preview": 8, + "gpt-4o-mini-realtime-preview": 16.67, +} + +var defaultAudioCompletionRatio = map[string]float64{ + "gpt-4o-realtime": 2, + "gpt-4o-mini-realtime": 2, +} + var ( modelPriceMap map[string]float64 = nil modelPriceMapMutex = sync.RWMutex{} @@ -326,6 +338,15 @@ func InitRatioSettings() { imageRatioMap = defaultImageRatio imageRatioMapMutex.Unlock() + // initialize audioRatioMap + audioRatioMapMutex.Lock() + audioRatioMap = defaultAudioRatio + audioRatioMapMutex.Unlock() + + // initialize audioCompletionRatioMap + audioCompletionRatioMapMutex.Lock() + audioCompletionRatioMap = defaultAudioCompletionRatio + audioCompletionRatioMapMutex.Unlock() } func GetModelPriceMap() map[string]float64 { @@ -417,6 +438,18 @@ func GetDefaultModelRatioMap() map[string]float64 { return defaultModelRatio } +func GetDefaultImageRatioMap() map[string]float64 { + return defaultImageRatio +} + +func GetDefaultAudioRatioMap() map[string]float64 { + return defaultAudioRatio +} + +func GetDefaultAudioCompletionRatioMap() map[string]float64 { + return defaultAudioCompletionRatio +} + func GetCompletionRatioMap() map[string]float64 { CompletionRatioMutex.RLock() defer CompletionRatioMutex.RUnlock() @@ -584,32 +617,20 @@ func getHardcodedCompletionModelRatio(name string) (float64, bool) { } func GetAudioRatio(name string) float64 { - if strings.Contains(name, "-realtime") { - if strings.HasSuffix(name, "gpt-4o-realtime-preview") { - return 8 - } else if strings.Contains(name, "gpt-4o-mini-realtime-preview") { - return 10 / 0.6 - } else { - return 20 - } - } - if strings.Contains(name, "-audio") { - if strings.HasPrefix(name, "gpt-4o-audio-preview") { - return 40 / 2.5 - } else if strings.HasPrefix(name, "gpt-4o-mini-audio-preview") { - return 10 / 0.15 - } else { - return 40 - } + audioRatioMapMutex.RLock() + defer audioRatioMapMutex.RUnlock() + if ratio, ok := audioRatioMap[name]; ok { + return ratio } return 20 } func GetAudioCompletionRatio(name string) float64 { - if strings.HasPrefix(name, "gpt-4o-realtime") { - return 2 - } else if strings.HasPrefix(name, "gpt-4o-mini-realtime") { - return 2 + audioCompletionRatioMapMutex.RLock() + defer audioCompletionRatioMapMutex.RUnlock() + if ratio, ok := audioCompletionRatioMap[name]; ok { + + return ratio } return 2 } @@ -630,6 +651,14 @@ var defaultImageRatio = map[string]float64{ } var imageRatioMap map[string]float64 var imageRatioMapMutex sync.RWMutex +var ( + audioRatioMap map[string]float64 = nil + audioRatioMapMutex = sync.RWMutex{} +) +var ( + audioCompletionRatioMap map[string]float64 = nil + audioCompletionRatioMapMutex = sync.RWMutex{} +) func ImageRatio2JSONString() string { imageRatioMapMutex.RLock() @@ -658,6 +687,68 @@ func GetImageRatio(name string) (float64, bool) { return ratio, true } +func AudioRatio2JSONString() string { + audioRatioMapMutex.RLock() + defer audioRatioMapMutex.RUnlock() + jsonBytes, err := common.Marshal(audioRatioMap) + if err != nil { + common.SysError("error marshalling audio ratio: " + err.Error()) + } + return string(jsonBytes) +} + +func UpdateAudioRatioByJSONString(jsonStr string) error { + audioRatioMapMutex.Lock() + defer audioRatioMapMutex.Unlock() + audioRatioMap = make(map[string]float64) + err := common.Unmarshal([]byte(jsonStr), &audioRatioMap) + if err == nil { + InvalidateExposedDataCache() + } + return err +} + +func GetAudioRatioCopy() map[string]float64 { + audioRatioMapMutex.RLock() + defer audioRatioMapMutex.RUnlock() + copyMap := make(map[string]float64, len(audioRatioMap)) + for k, v := range audioRatioMap { + copyMap[k] = v + } + return copyMap +} + +func AudioCompletionRatio2JSONString() string { + audioCompletionRatioMapMutex.RLock() + defer audioCompletionRatioMapMutex.RUnlock() + jsonBytes, err := common.Marshal(audioCompletionRatioMap) + if err != nil { + common.SysError("error marshalling audio completion ratio: " + err.Error()) + } + return string(jsonBytes) +} + +func UpdateAudioCompletionRatioByJSONString(jsonStr string) error { + audioCompletionRatioMapMutex.Lock() + defer audioCompletionRatioMapMutex.Unlock() + audioCompletionRatioMap = make(map[string]float64) + err := common.Unmarshal([]byte(jsonStr), &audioCompletionRatioMap) + if err == nil { + InvalidateExposedDataCache() + } + return err +} + +func GetAudioCompletionRatioCopy() map[string]float64 { + audioCompletionRatioMapMutex.RLock() + defer audioCompletionRatioMapMutex.RUnlock() + copyMap := make(map[string]float64, len(audioCompletionRatioMap)) + for k, v := range audioCompletionRatioMap { + copyMap[k] = v + } + return copyMap +} + func GetModelRatioCopy() map[string]float64 { modelRatioMapMutex.RLock() defer modelRatioMapMutex.RUnlock() diff --git a/types/price_data.go b/types/price_data.go index f6a92d7e31..ec7fcdfe9c 100644 --- a/types/price_data.go +++ b/types/price_data.go @@ -15,6 +15,8 @@ type PriceData struct { CacheRatio float64 CacheCreationRatio float64 ImageRatio float64 + AudioRatio float64 + AudioCompletionRatio float64 UsePrice bool ShouldPreConsumedQuota int GroupRatioInfo GroupRatioInfo @@ -27,5 +29,5 @@ type PerCallPriceData struct { } func (p PriceData) ToSetting() string { - return fmt.Sprintf("ModelPrice: %f, ModelRatio: %f, CompletionRatio: %f, CacheRatio: %f, GroupRatio: %f, UsePrice: %t, CacheCreationRatio: %f, ShouldPreConsumedQuota: %d, ImageRatio: %f", p.ModelPrice, p.ModelRatio, p.CompletionRatio, p.CacheRatio, p.GroupRatioInfo.GroupRatio, p.UsePrice, p.CacheCreationRatio, p.ShouldPreConsumedQuota, p.ImageRatio) + return fmt.Sprintf("ModelPrice: %f, ModelRatio: %f, CompletionRatio: %f, CacheRatio: %f, GroupRatio: %f, UsePrice: %t, CacheCreationRatio: %f, ShouldPreConsumedQuota: %d, ImageRatio: %f, AudioRatio: %f, AudioCompletionRatio: %f", p.ModelPrice, p.ModelRatio, p.CompletionRatio, p.CacheRatio, p.GroupRatioInfo.GroupRatio, p.UsePrice, p.CacheCreationRatio, p.ShouldPreConsumedQuota, p.ImageRatio, p.AudioRatio, p.AudioCompletionRatio) } diff --git a/web/src/components/settings/RatioSetting.jsx b/web/src/components/settings/RatioSetting.jsx index 096722bba4..f5d8ef99d4 100644 --- a/web/src/components/settings/RatioSetting.jsx +++ b/web/src/components/settings/RatioSetting.jsx @@ -39,6 +39,9 @@ const RatioSetting = () => { CompletionRatio: '', GroupRatio: '', GroupGroupRatio: '', + ImageRatio: '', + AudioRatio: '', + AudioCompletionRatio: '', AutoGroups: '', DefaultUseAutoGroup: false, ExposeRatioEnabled: false, @@ -61,7 +64,10 @@ const RatioSetting = () => { item.key === 'UserUsableGroups' || item.key === 'CompletionRatio' || item.key === 'ModelPrice' || - item.key === 'CacheRatio' + item.key === 'CacheRatio' || + item.key === 'ImageRatio' || + item.key === 'AudioRatio' || + item.key === 'AudioCompletionRatio' ) { try { item.value = JSON.stringify(JSON.parse(item.value), null, 2); diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 877fa44fe3..62c8ebb79d 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -2017,5 +2017,15 @@ "查看密钥": "View key", "查看渠道密钥": "View channel key", "渠道密钥信息": "Channel key information", - "密钥获取成功": "Key acquisition successful" + "密钥获取成功": "Key acquisition successful", + "模型补全倍率(仅对自定义模型有效)": "Model completion ratio (only effective for custom models)", + "图片倍率": "Image ratio", + "音频倍率": "Audio ratio", + "音频补全倍率": "Audio completion ratio", + "图片输入相关的倍率设置,键为模型名称,值为倍率": "Image input related ratio settings, key is model name, value is ratio", + "音频输入相关的倍率设置,键为模型名称,值为倍率": "Audio input related ratio settings, key is model name, value is ratio", + "音频输出补全相关的倍率设置,键为模型名称,值为倍率": "Audio output completion related ratio settings, key is model name, value is ratio", + "为一个 JSON 文本,键为模型名称,值为倍率,例如:{\"gpt-image-1\": 2}": "A JSON text with model name as key and ratio as value, e.g.: {\"gpt-image-1\": 2}", + "为一个 JSON 文本,键为模型名称,值为倍率,例如:{\"gpt-4o-audio-preview\": 16}": "A JSON text with model name as key and ratio as value, e.g.: {\"gpt-4o-audio-preview\": 16}", + "为一个 JSON 文本,键为模型名称,值为倍率,例如:{\"gpt-4o-realtime\": 2}": "A JSON text with model name as key and ratio as value, e.g.: {\"gpt-4o-realtime\": 2}" } diff --git a/web/src/pages/Setting/Ratio/ModelRatioSettings.jsx b/web/src/pages/Setting/Ratio/ModelRatioSettings.jsx index 2462a35adb..b409512617 100644 --- a/web/src/pages/Setting/Ratio/ModelRatioSettings.jsx +++ b/web/src/pages/Setting/Ratio/ModelRatioSettings.jsx @@ -44,6 +44,9 @@ export default function ModelRatioSettings(props) { ModelRatio: '', CacheRatio: '', CompletionRatio: '', + ImageRatio: '', + AudioRatio: '', + AudioCompletionRatio: '', ExposeRatioEnabled: false, }); const refForm = useRef(); @@ -219,6 +222,72 @@ export default function ModelRatioSettings(props) { /> + + + verifyJSON(value), + message: '不是合法的 JSON 字符串', + }, + ]} + onChange={(value) => + setInputs({ ...inputs, ImageRatio: value }) + } + /> + + + + + verifyJSON(value), + message: '不是合法的 JSON 字符串', + }, + ]} + onChange={(value) => + setInputs({ ...inputs, AudioRatio: value }) + } + /> + + + + + verifyJSON(value), + message: '不是合法的 JSON 字符串', + }, + ]} + onChange={(value) => + setInputs({ ...inputs, AudioCompletionRatio: value }) + } + /> + + Date: Sat, 30 Aug 2025 23:53:46 +0800 Subject: [PATCH 02/12] feat: improve ratio update --- setting/ratio_setting/model_ratio.go | 33 ++++++++++++++++------------ 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/setting/ratio_setting/model_ratio.go b/setting/ratio_setting/model_ratio.go index 99952daf19..5b47c875ff 100644 --- a/setting/ratio_setting/model_ratio.go +++ b/setting/ratio_setting/model_ratio.go @@ -619,6 +619,7 @@ func getHardcodedCompletionModelRatio(name string) (float64, bool) { func GetAudioRatio(name string) float64 { audioRatioMapMutex.RLock() defer audioRatioMapMutex.RUnlock() + name = FormatMatchingModelName(name) if ratio, ok := audioRatioMap[name]; ok { return ratio } @@ -628,6 +629,7 @@ func GetAudioRatio(name string) float64 { func GetAudioCompletionRatio(name string) float64 { audioCompletionRatioMapMutex.RLock() defer audioCompletionRatioMapMutex.RUnlock() + name = FormatMatchingModelName(name) if ratio, ok := audioCompletionRatioMap[name]; ok { return ratio @@ -698,14 +700,16 @@ func AudioRatio2JSONString() string { } func UpdateAudioRatioByJSONString(jsonStr string) error { - audioRatioMapMutex.Lock() - defer audioRatioMapMutex.Unlock() - audioRatioMap = make(map[string]float64) - err := common.Unmarshal([]byte(jsonStr), &audioRatioMap) - if err == nil { - InvalidateExposedDataCache() + + tmp := make(map[string]float64) + if err := common.Unmarshal([]byte(jsonStr), &tmp); err != nil { + return err } - return err + audioRatioMapMutex.Lock() + audioRatioMap = tmp + audioRatioMapMutex.Unlock() + InvalidateExposedDataCache() + return nil } func GetAudioRatioCopy() map[string]float64 { @@ -729,14 +733,15 @@ func AudioCompletionRatio2JSONString() string { } func UpdateAudioCompletionRatioByJSONString(jsonStr string) error { - audioCompletionRatioMapMutex.Lock() - defer audioCompletionRatioMapMutex.Unlock() - audioCompletionRatioMap = make(map[string]float64) - err := common.Unmarshal([]byte(jsonStr), &audioCompletionRatioMap) - if err == nil { - InvalidateExposedDataCache() + tmp := make(map[string]float64) + if err := common.Unmarshal([]byte(jsonStr), &tmp); err != nil { + return err } - return err + audioCompletionRatioMapMutex.Lock() + audioCompletionRatioMap = tmp + audioCompletionRatioMapMutex.Unlock() + InvalidateExposedDataCache() + return nil } func GetAudioCompletionRatioCopy() map[string]float64 { From b0bb2fe5a97b91a4f7e6a46f56b7e6fd1dca329e Mon Sep 17 00:00:00 2001 From: Xyfacai Date: Mon, 15 Sep 2025 01:01:48 +0800 Subject: [PATCH 03/12] fix: gemini support webp file --- relay/channel/gemini/relay-gemini.go | 2 ++ service/pre_consume_quota.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/relay/channel/gemini/relay-gemini.go b/relay/channel/gemini/relay-gemini.go index eb4afbae15..199c84664b 100644 --- a/relay/channel/gemini/relay-gemini.go +++ b/relay/channel/gemini/relay-gemini.go @@ -23,6 +23,7 @@ import ( "github.com/gin-gonic/gin" ) +// https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/inference?hl=zh-cn#blob var geminiSupportedMimeTypes = map[string]bool{ "application/pdf": true, "audio/mpeg": true, @@ -30,6 +31,7 @@ var geminiSupportedMimeTypes = map[string]bool{ "audio/wav": true, "image/png": true, "image/jpeg": true, + "image/webp": true, "text/plain": true, "video/mov": true, "video/mpeg": true, diff --git a/service/pre_consume_quota.go b/service/pre_consume_quota.go index 3cfabc1a47..0cf53513bd 100644 --- a/service/pre_consume_quota.go +++ b/service/pre_consume_quota.go @@ -19,7 +19,7 @@ func ReturnPreConsumedQuota(c *gin.Context, relayInfo *relaycommon.RelayInfo) { gopool.Go(func() { relayInfoCopy := *relayInfo - err := PostConsumeQuota(&relayInfoCopy, -relayInfo.FinalPreConsumedQuota, 0, false) + err := PostConsumeQuota(&relayInfoCopy, -relayInfoCopy.FinalPreConsumedQuota, 0, false) if err != nil { common.SysLog("error return pre-consumed quota: " + err.Error()) } From c176e713f71118223d66d1eabf5822271e95241e Mon Sep 17 00:00:00 2001 From: Xyfacai Date: Mon, 15 Sep 2025 19:38:31 +0800 Subject: [PATCH 04/12] =?UTF-8?q?fix:=20=E9=9D=9Eopenai=20=E6=B8=A0?= =?UTF-8?q?=E9=81=93=E4=BD=BF=E7=94=A8=20SystemPrompt=20=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E4=BC=9Apanic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- relay/compatible_handler.go | 64 +++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/relay/compatible_handler.go b/relay/compatible_handler.go index 01ab1fff4a..c2d6b6fa17 100644 --- a/relay/compatible_handler.go +++ b/relay/compatible_handler.go @@ -90,41 +90,43 @@ func TextHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *types if info.ChannelSetting.SystemPrompt != "" { // 如果有系统提示,则将其添加到请求中 - request := convertedRequest.(*dto.GeneralOpenAIRequest) - containSystemPrompt := false - for _, message := range request.Messages { - if message.Role == request.GetSystemRoleName() { - containSystemPrompt = true - break - } - } - if !containSystemPrompt { - // 如果没有系统提示,则添加系统提示 - systemMessage := dto.Message{ - Role: request.GetSystemRoleName(), - Content: info.ChannelSetting.SystemPrompt, - } - request.Messages = append([]dto.Message{systemMessage}, request.Messages...) - } else if info.ChannelSetting.SystemPromptOverride { - common.SetContextKey(c, constant.ContextKeySystemPromptOverride, true) - // 如果有系统提示,且允许覆盖,则拼接到前面 - for i, message := range request.Messages { + request, ok := convertedRequest.(*dto.GeneralOpenAIRequest) + if ok { + containSystemPrompt := false + for _, message := range request.Messages { if message.Role == request.GetSystemRoleName() { - if message.IsStringContent() { - request.Messages[i].SetStringContent(info.ChannelSetting.SystemPrompt + "\n" + message.StringContent()) - } else { - contents := message.ParseContent() - contents = append([]dto.MediaContent{ - { - Type: dto.ContentTypeText, - Text: info.ChannelSetting.SystemPrompt, - }, - }, contents...) - request.Messages[i].Content = contents - } + containSystemPrompt = true break } } + if !containSystemPrompt { + // 如果没有系统提示,则添加系统提示 + systemMessage := dto.Message{ + Role: request.GetSystemRoleName(), + Content: info.ChannelSetting.SystemPrompt, + } + request.Messages = append([]dto.Message{systemMessage}, request.Messages...) + } else if info.ChannelSetting.SystemPromptOverride { + common.SetContextKey(c, constant.ContextKeySystemPromptOverride, true) + // 如果有系统提示,且允许覆盖,则拼接到前面 + for i, message := range request.Messages { + if message.Role == request.GetSystemRoleName() { + if message.IsStringContent() { + request.Messages[i].SetStringContent(info.ChannelSetting.SystemPrompt + "\n" + message.StringContent()) + } else { + contents := message.ParseContent() + contents = append([]dto.MediaContent{ + { + Type: dto.ContentTypeText, + Text: info.ChannelSetting.SystemPrompt, + }, + }, contents...) + request.Messages[i].Content = contents + } + break + } + } + } } } From 9fa9cf38c28bdc12a3ec408ba120c57c8f874cd6 Mon Sep 17 00:00:00 2001 From: Xyfacai Date: Tue, 16 Sep 2025 16:28:27 +0800 Subject: [PATCH 05/12] =?UTF-8?q?feat:=20jimeng=20kling=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81new=20api=20=E5=B5=8C=E5=A5=97=E4=B8=AD=E8=BD=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/channel.go | 14 +++++++--- relay/channel/task/jimeng/adaptor.go | 38 ++++++++++++++++++++-------- relay/channel/task/kling/adaptor.go | 19 +++++++++++++- 3 files changed, 57 insertions(+), 14 deletions(-) diff --git a/controller/channel.go b/controller/channel.go index 403eb04cc3..17154ab0ff 100644 --- a/controller/channel.go +++ b/controller/channel.go @@ -501,9 +501,10 @@ func validateChannel(channel *model.Channel, isAdd bool) error { } type AddChannelRequest struct { - Mode string `json:"mode"` - MultiKeyMode constant.MultiKeyMode `json:"multi_key_mode"` - Channel *model.Channel `json:"channel"` + Mode string `json:"mode"` + MultiKeyMode constant.MultiKeyMode `json:"multi_key_mode"` + BatchAddSetKeyPrefix2Name bool `json:"batch_add_set_key_prefix_2_name"` + Channel *model.Channel `json:"channel"` } func getVertexArrayKeys(keys string) ([]string, error) { @@ -616,6 +617,13 @@ func AddChannel(c *gin.Context) { } localChannel := addChannelRequest.Channel localChannel.Key = key + if addChannelRequest.BatchAddSetKeyPrefix2Name && len(keys) > 1 { + keyPrefix := localChannel.Key + if len(localChannel.Key) > 8 { + keyPrefix = localChannel.Key[:8] + } + localChannel.Name = fmt.Sprintf("%s %s", localChannel.Name, keyPrefix) + } channels = append(channels, *localChannel) } err = model.BatchInsertChannels(channels) diff --git a/relay/channel/task/jimeng/adaptor.go b/relay/channel/task/jimeng/adaptor.go index 2bc45c547f..95f3cb2696 100644 --- a/relay/channel/task/jimeng/adaptor.go +++ b/relay/channel/task/jimeng/adaptor.go @@ -93,6 +93,9 @@ func (a *TaskAdaptor) ValidateRequestAndSetAction(c *gin.Context, info *relaycom // BuildRequestURL constructs the upstream URL. func (a *TaskAdaptor) BuildRequestURL(info *relaycommon.RelayInfo) (string, error) { + if isNewAPIRelay(info.ApiKey) { + return fmt.Sprintf("%s/jimeng/?Action=CVSync2AsyncSubmitTask&Version=2022-08-31", a.baseURL), nil + } return fmt.Sprintf("%s/?Action=CVSync2AsyncSubmitTask&Version=2022-08-31", a.baseURL), nil } @@ -100,7 +103,12 @@ func (a *TaskAdaptor) BuildRequestURL(info *relaycommon.RelayInfo) (string, erro func (a *TaskAdaptor) BuildRequestHeader(c *gin.Context, req *http.Request, info *relaycommon.RelayInfo) error { req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") - return a.signRequest(req, a.accessKey, a.secretKey) + if isNewAPIRelay(info.ApiKey) { + req.Header.Set("Authorization", "Bearer "+info.ApiKey) + } else { + return a.signRequest(req, a.accessKey, a.secretKey) + } + return nil } // BuildRequestBody converts request into Jimeng specific format. @@ -160,6 +168,9 @@ func (a *TaskAdaptor) FetchTask(baseUrl, key string, body map[string]any) (*http } uri := fmt.Sprintf("%s/?Action=CVSync2AsyncGetResult&Version=2022-08-31", baseUrl) + if isNewAPIRelay(key) { + uri = fmt.Sprintf("%s/jimeng/?Action=CVSync2AsyncGetResult&Version=2022-08-31", a.baseURL) + } payload := map[string]string{ "req_key": "jimeng_vgfm_t2v_l20", // This is fixed value from doc: https://www.volcengine.com/docs/85621/1544774 "task_id": taskID, @@ -177,17 +188,20 @@ func (a *TaskAdaptor) FetchTask(baseUrl, key string, body map[string]any) (*http req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") - keyParts := strings.Split(key, "|") - if len(keyParts) != 2 { - return nil, fmt.Errorf("invalid api key format for jimeng: expected 'ak|sk'") - } - accessKey := strings.TrimSpace(keyParts[0]) - secretKey := strings.TrimSpace(keyParts[1]) + if isNewAPIRelay(key) { + req.Header.Set("Authorization", "Bearer "+key) + } else { + keyParts := strings.Split(key, "|") + if len(keyParts) != 2 { + return nil, fmt.Errorf("invalid api key format for jimeng: expected 'ak|sk'") + } + accessKey := strings.TrimSpace(keyParts[0]) + secretKey := strings.TrimSpace(keyParts[1]) - if err := a.signRequest(req, accessKey, secretKey); err != nil { - return nil, errors.Wrap(err, "sign request failed") + if err := a.signRequest(req, accessKey, secretKey); err != nil { + return nil, errors.Wrap(err, "sign request failed") + } } - return service.GetHttpClient().Do(req) } @@ -362,3 +376,7 @@ func (a *TaskAdaptor) ParseTaskResult(respBody []byte) (*relaycommon.TaskInfo, e taskResult.Url = resTask.Data.VideoUrl return &taskResult, nil } + +func isNewAPIRelay(apiKey string) bool { + return strings.HasPrefix(apiKey, "sk-") +} diff --git a/relay/channel/task/kling/adaptor.go b/relay/channel/task/kling/adaptor.go index 13f2af9729..fec3396aec 100644 --- a/relay/channel/task/kling/adaptor.go +++ b/relay/channel/task/kling/adaptor.go @@ -117,6 +117,11 @@ func (a *TaskAdaptor) ValidateRequestAndSetAction(c *gin.Context, info *relaycom // BuildRequestURL constructs the upstream URL. func (a *TaskAdaptor) BuildRequestURL(info *relaycommon.RelayInfo) (string, error) { path := lo.Ternary(info.Action == constant.TaskActionGenerate, "/v1/videos/image2video", "/v1/videos/text2video") + + if isNewAPIRelay(info.ApiKey) { + return fmt.Sprintf("%s/kling%s", a.baseURL, path), nil + } + return fmt.Sprintf("%s%s", a.baseURL, path), nil } @@ -199,6 +204,9 @@ func (a *TaskAdaptor) FetchTask(baseUrl, key string, body map[string]any) (*http } path := lo.Ternary(action == constant.TaskActionGenerate, "/v1/videos/image2video", "/v1/videos/text2video") url := fmt.Sprintf("%s%s/%s", baseUrl, path, taskID) + if isNewAPIRelay(key) { + url = fmt.Sprintf("%s/kling%s/%s", baseUrl, path, taskID) + } req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { @@ -304,8 +312,13 @@ func (a *TaskAdaptor) createJWTToken() (string, error) { //} func (a *TaskAdaptor) createJWTTokenWithKey(apiKey string) (string, error) { - + if isNewAPIRelay(apiKey) { + return apiKey, nil // new api relay + } keyParts := strings.Split(apiKey, "|") + if len(keyParts) != 2 { + return "", errors.New("invalid api_key, required format is accessKey|secretKey") + } accessKey := strings.TrimSpace(keyParts[0]) if len(keyParts) == 1 { return accessKey, nil @@ -352,3 +365,7 @@ func (a *TaskAdaptor) ParseTaskResult(respBody []byte) (*relaycommon.TaskInfo, e } return taskInfo, nil } + +func isNewAPIRelay(apiKey string) bool { + return strings.HasPrefix(apiKey, "sk-") +} From a2b2f1ce272f0eaed2017546248ea788f0dcae5c Mon Sep 17 00:00:00 2001 From: t0ng7u Date: Tue, 16 Sep 2025 16:55:35 +0800 Subject: [PATCH 06/12] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20fix:=20Align=20se?= =?UTF-8?q?tup=20API=20errors=20to=20HTTP=20200=20with=20{success:false,?= =?UTF-8?q?=20message}?= 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(), }) From fdc3030e4795a574733a7ba24466fbc1f0a4d357 Mon Sep 17 00:00:00 2001 From: CaIon Date: Fri, 19 Sep 2025 14:20:35 +0800 Subject: [PATCH 07/12] CI --- .github/workflows/docker-image-arm64.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/docker-image-arm64.yml b/.github/workflows/docker-image-arm64.yml index cabf3cec4b..8e4656aa75 100644 --- a/.github/workflows/docker-image-arm64.yml +++ b/.github/workflows/docker-image-arm64.yml @@ -53,5 +53,4 @@ jobs: platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - provenance: false \ No newline at end of file + labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file From abfb35b53b6391ff92b9e79c1b2d7e65a2dc5a83 Mon Sep 17 00:00:00 2001 From: CaIon Date: Fri, 19 Sep 2025 14:21:32 +0800 Subject: [PATCH 08/12] fix: cast option.Value to string for ratio updates --- controller/option.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/controller/option.go b/controller/option.go index 3e59c68e04..7d1c676f54 100644 --- a/controller/option.go +++ b/controller/option.go @@ -129,7 +129,7 @@ func UpdateOption(c *gin.Context) { return } case "ImageRatio": - err = ratio_setting.UpdateImageRatioByJSONString(option.Value) + err = ratio_setting.UpdateImageRatioByJSONString(option.Value.(string)) if err != nil { c.JSON(http.StatusOK, gin.H{ "success": false, @@ -138,7 +138,7 @@ func UpdateOption(c *gin.Context) { return } case "AudioRatio": - err = ratio_setting.UpdateAudioRatioByJSONString(option.Value) + err = ratio_setting.UpdateAudioRatioByJSONString(option.Value.(string)) if err != nil { c.JSON(http.StatusOK, gin.H{ "success": false, @@ -147,7 +147,7 @@ func UpdateOption(c *gin.Context) { return } case "AudioCompletionRatio": - err = ratio_setting.UpdateAudioCompletionRatioByJSONString(option.Value) + err = ratio_setting.UpdateAudioCompletionRatioByJSONString(option.Value.(string)) if err != nil { c.JSON(http.StatusOK, gin.H{ "success": false, From d77aa81f4f6145b565da24a8d87dc1851a148b7d Mon Sep 17 00:00:00 2001 From: CaIon Date: Fri, 19 Sep 2025 14:23:08 +0800 Subject: [PATCH 09/12] feat: update labels for ratio settings to clarify model support --- web/src/pages/Setting/Ratio/ModelRatioSettings.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/src/pages/Setting/Ratio/ModelRatioSettings.jsx b/web/src/pages/Setting/Ratio/ModelRatioSettings.jsx index b409512617..ed982edcfb 100644 --- a/web/src/pages/Setting/Ratio/ModelRatioSettings.jsx +++ b/web/src/pages/Setting/Ratio/ModelRatioSettings.jsx @@ -225,8 +225,8 @@ export default function ModelRatioSettings(props) { Date: Tue, 16 Sep 2025 12:30:22 +0800 Subject: [PATCH 10/12] feat: vidu video support multi images --- relay/channel/task/vidu/adaptor.go | 10 ++-------- relay/common/relay_utils.go | 28 ++-------------------------- 2 files changed, 4 insertions(+), 34 deletions(-) diff --git a/relay/channel/task/vidu/adaptor.go b/relay/channel/task/vidu/adaptor.go index a1140d1e7f..8974c6149a 100644 --- a/relay/channel/task/vidu/adaptor.go +++ b/relay/channel/task/vidu/adaptor.go @@ -80,8 +80,7 @@ func (a *TaskAdaptor) Init(info *relaycommon.RelayInfo) { } func (a *TaskAdaptor) ValidateRequestAndSetAction(c *gin.Context, info *relaycommon.RelayInfo) *dto.TaskError { - // Use the unified validation method for TaskSubmitReq with image-based action determination - return relaycommon.ValidateTaskRequestWithImageBinding(c, info) + return relaycommon.ValidateBasicTaskRequest(c, info, constant.TaskActionGenerate) } func (a *TaskAdaptor) BuildRequestBody(c *gin.Context, _ *relaycommon.RelayInfo) (io.Reader, error) { @@ -187,14 +186,9 @@ func (a *TaskAdaptor) GetChannelName() string { // ============================ func (a *TaskAdaptor) convertToRequestPayload(req *relaycommon.TaskSubmitReq) (*requestPayload, error) { - var images []string - if req.Image != "" { - images = []string{req.Image} - } - r := requestPayload{ Model: defaultString(req.Model, "viduq1"), - Images: images, + Images: req.Images, Prompt: req.Prompt, Duration: defaultInt(req.Duration, 5), Resolution: defaultString(req.Size, "1080p"), diff --git a/relay/common/relay_utils.go b/relay/common/relay_utils.go index cf6d08dda0..96d1370bdd 100644 --- a/relay/common/relay_utils.go +++ b/relay/common/relay_utils.go @@ -79,34 +79,10 @@ func ValidateBasicTaskRequest(c *gin.Context, info *RelayInfo, action string) *d req.Images = []string{req.Image} } - storeTaskRequest(c, info, action, req) - return nil -} - -func ValidateTaskRequestWithImage(c *gin.Context, info *RelayInfo, requestObj interface{}) *dto.TaskError { - hasPrompt, ok := requestObj.(HasPrompt) - if !ok { - return createTaskError(fmt.Errorf("request must have prompt"), "invalid_request", http.StatusBadRequest, true) - } - - if taskErr := validatePrompt(hasPrompt.GetPrompt()); taskErr != nil { - return taskErr - } - - action := constant.TaskActionTextGenerate - if hasImage, ok := requestObj.(HasImage); ok && hasImage.HasImage() { + if req.HasImage() { action = constant.TaskActionGenerate } - storeTaskRequest(c, info, action, requestObj) + storeTaskRequest(c, info, action, req) return nil } - -func ValidateTaskRequestWithImageBinding(c *gin.Context, info *RelayInfo) *dto.TaskError { - var req TaskSubmitReq - if err := c.ShouldBindJSON(&req); err != nil { - return createTaskError(err, "invalid_request_body", http.StatusBadRequest, false) - } - - return ValidateTaskRequestWithImage(c, info, req) -} From b183f2f6633d5544382d2d6df4ca882f737ac0fb Mon Sep 17 00:00:00 2001 From: feitianbubu Date: Fri, 19 Sep 2025 17:44:58 +0800 Subject: [PATCH 11/12] feat: vidu video add starEnd and reference gen video --- constant/task.go | 6 ++++-- relay/channel/task/vidu/adaptor.go | 4 ++++ relay/common/relay_utils.go | 8 ++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/constant/task.go b/constant/task.go index 21790145bf..e174fd60e0 100644 --- a/constant/task.go +++ b/constant/task.go @@ -11,8 +11,10 @@ const ( SunoActionMusic = "MUSIC" SunoActionLyrics = "LYRICS" - TaskActionGenerate = "generate" - TaskActionTextGenerate = "textGenerate" + TaskActionGenerate = "generate" + TaskActionTextGenerate = "textGenerate" + TaskActionFirstTailGenerate = "firstTailGenerate" + TaskActionReferenceGenerate = "referenceGenerate" ) var SunoModel2Action = map[string]string{ diff --git a/relay/channel/task/vidu/adaptor.go b/relay/channel/task/vidu/adaptor.go index 8974c6149a..358aef5835 100644 --- a/relay/channel/task/vidu/adaptor.go +++ b/relay/channel/task/vidu/adaptor.go @@ -111,6 +111,10 @@ func (a *TaskAdaptor) BuildRequestURL(info *relaycommon.RelayInfo) (string, erro switch info.Action { case constant.TaskActionGenerate: path = "/img2video" + case constant.TaskActionFirstTailGenerate: + path = "/start-end2video" + case constant.TaskActionReferenceGenerate: + path = "/reference2video" default: path = "/text2video" } diff --git a/relay/common/relay_utils.go b/relay/common/relay_utils.go index 96d1370bdd..3a721b4793 100644 --- a/relay/common/relay_utils.go +++ b/relay/common/relay_utils.go @@ -81,6 +81,14 @@ func ValidateBasicTaskRequest(c *gin.Context, info *RelayInfo, action string) *d if req.HasImage() { action = constant.TaskActionGenerate + if info.ChannelType == constant.ChannelTypeVidu { + // vidu 增加 首尾帧生视频和参考图生视频 + if len(req.Images) == 2 { + action = constant.TaskActionFirstTailGenerate + } else if len(req.Images) > 2 { + action = constant.TaskActionReferenceGenerate + } + } } storeTaskRequest(c, info, action, req) From d29fbd378d387ebaf604905cafe4305415b3415e Mon Sep 17 00:00:00 2001 From: feitianbubu Date: Fri, 19 Sep 2025 18:36:44 +0800 Subject: [PATCH 12/12] feat: vidu video add starEnd and reference gen video show type --- .../table/task-logs/TaskLogsColumnDefs.jsx | 21 ++++++++++++++++--- web/src/constants/common.constant.js | 2 ++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/web/src/components/table/task-logs/TaskLogsColumnDefs.jsx b/web/src/components/table/task-logs/TaskLogsColumnDefs.jsx index 766c17158c..b63c7dd4fb 100644 --- a/web/src/components/table/task-logs/TaskLogsColumnDefs.jsx +++ b/web/src/components/table/task-logs/TaskLogsColumnDefs.jsx @@ -35,8 +35,9 @@ import { Sparkles, } from 'lucide-react'; import { - TASK_ACTION_GENERATE, - TASK_ACTION_TEXT_GENERATE, + TASK_ACTION_FIRST_TAIL_GENERATE, + TASK_ACTION_GENERATE, TASK_ACTION_REFERENCE_GENERATE, + TASK_ACTION_TEXT_GENERATE } from '../../../constants/common.constant'; import { CHANNEL_OPTIONS } from '../../../constants/channel.constants'; @@ -111,6 +112,18 @@ const renderType = (type, t) => { {t('文生视频')} ); + case TASK_ACTION_FIRST_TAIL_GENERATE: + return ( + }> + {t('首尾生视频')} + + ); + case TASK_ACTION_REFERENCE_GENERATE: + return ( + }> + {t('参照生视频')} + + ); default: return ( }> @@ -343,7 +356,9 @@ export const getTaskLogsColumns = ({ // 仅当为视频生成任务且成功,且 fail_reason 是 URL 时显示可点击链接 const isVideoTask = record.action === TASK_ACTION_GENERATE || - record.action === TASK_ACTION_TEXT_GENERATE; + record.action === TASK_ACTION_TEXT_GENERATE || + record.action === TASK_ACTION_FIRST_TAIL_GENERATE || + record.action === TASK_ACTION_REFERENCE_GENERATE; const isSuccess = record.status === 'SUCCESS'; const isUrl = typeof text === 'string' && /^https?:\/\//.test(text); if (isSuccess && isVideoTask && isUrl) { diff --git a/web/src/constants/common.constant.js b/web/src/constants/common.constant.js index 277bb9a543..57fbbbde59 100644 --- a/web/src/constants/common.constant.js +++ b/web/src/constants/common.constant.js @@ -40,3 +40,5 @@ export const API_ENDPOINTS = [ export const TASK_ACTION_GENERATE = 'generate'; export const TASK_ACTION_TEXT_GENERATE = 'textGenerate'; +export const TASK_ACTION_FIRST_TAIL_GENERATE = 'firstTailGenerate'; +export const TASK_ACTION_REFERENCE_GENERATE = 'referenceGenerate';