feat: 支持视频模型首帧参数及顺序执行逻辑
添加 FirstFrame 字段到模型信息结构体,重构视频节点数据转换处理,支持从上游节点获取视频配置并区分输出字段。实现视频结果重新上传至 OSS,修正字段匹配逻辑为精确匹配,并基于 return_last_frame 参数优化顺序执行中的首帧与内容传递。
This commit is contained in:
@@ -49,6 +49,7 @@ type GetModelInfoReq struct {
|
||||
|
||||
type GetModelInfoRes struct {
|
||||
Model struct {
|
||||
FirstFrame string `json:"firstFrame"`
|
||||
LastFrame string `json:"lastFrame"`
|
||||
ResponseTokenField string `json:"responseTokenField"`
|
||||
ResponseMapping map[string]any `json:"responseMapping"`
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
flowDto "ai-agent/workflow/model/dto/flow"
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -292,7 +293,23 @@ func VideoModelLambda(ctx context.Context, input any) (any, error) {
|
||||
}
|
||||
videoUrl = newS + msg.FileURL
|
||||
} else {
|
||||
videoUrl = videoURL[0]
|
||||
var bytes []byte
|
||||
bytes, err = GetFileBytesFromURL(ctx, videoURL[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("下载图片失败: %w", err)
|
||||
}
|
||||
// 构造文件名
|
||||
fileName := fmt.Sprintf("ai_video_%d%s", time.Now().UnixMilli(), strings.ToLower(filepath.Ext(videoURL[0])))
|
||||
// 上传到你的OSS(你项目已有的Upload方法)
|
||||
var upResp *dto.UploadFileBytesRes
|
||||
upResp, err = Upload(ctx, &dto.UploadFileBytesReq{
|
||||
FileName: fileName,
|
||||
FileBytes: bytes,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("上传OSS失败: %w", err)
|
||||
}
|
||||
videoUrl = upResp.FileURL
|
||||
}
|
||||
|
||||
outputRes := make([]node.NodeFormField, 0)
|
||||
|
||||
@@ -788,18 +788,41 @@ func VideoOptimizeNode(ctx context.Context, nodeInput *flowDto.NodeExecutionInpu
|
||||
}
|
||||
|
||||
func DataConversionNode(ctx context.Context, nodeInput *flowDto.NodeExecutionInput, skillName string, form []map[string]any, userForm []map[string]any) ([]node.NodeFormField, error) {
|
||||
if strings.Contains(nodeInput.Config.Name, "字幕") {
|
||||
if strings.Contains(nodeInput.Config.Name, "字幕") || strings.Contains(nodeInput.Config.Name, "视频") {
|
||||
jsonStr := ``
|
||||
for _, field := range nodeInput.Config.OutputConfig {
|
||||
jsonStr, _ = sjson.Set(jsonStr, field.Field, field.Value)
|
||||
}
|
||||
outputRes := make([]node.NodeFormField, 0)
|
||||
outputRes = append(outputRes, node.NodeFormField{
|
||||
Field: fmt.Sprintf("data_conversion"),
|
||||
Value: gconv.Map(jsonStr),
|
||||
Label: fmt.Sprintf("data_conversion"),
|
||||
Type: "string",
|
||||
})
|
||||
for _, field := range nodeInput.Config.OutputConfig {
|
||||
if strings.Contains(nodeInput.Config.Name, "视频") {
|
||||
for _, item := range nodeInput.Global.ExecutedNodes {
|
||||
refNode, ok := nodeInput.Global.ConfigMap[item.NodeId]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, v := range refNode.FormConfig {
|
||||
if v.Field == field.Value {
|
||||
jsonStr, _ = sjson.Set(jsonStr, field.Field, v.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
jsonStr, _ = sjson.Set(jsonStr, field.Field, field.Value)
|
||||
}
|
||||
}
|
||||
if strings.Contains(nodeInput.Config.Name, "视频") {
|
||||
outputRes = append(outputRes, node.NodeFormField{
|
||||
Field: fmt.Sprintf("data_content"),
|
||||
Value: gconv.Map(jsonStr),
|
||||
Label: fmt.Sprintf("data_content"),
|
||||
Type: "string",
|
||||
})
|
||||
} else {
|
||||
outputRes = append(outputRes, node.NodeFormField{
|
||||
Field: fmt.Sprintf("data_conversion"),
|
||||
Value: gconv.Map(jsonStr),
|
||||
Label: fmt.Sprintf("data_conversion"),
|
||||
Type: "string",
|
||||
})
|
||||
}
|
||||
return outputRes, nil
|
||||
}
|
||||
|
||||
@@ -1035,7 +1058,7 @@ func GetNodeContextContent(execInput *flowDto.FlowExecutionInput, nodeEntity *en
|
||||
// 取指定字段
|
||||
for _, f := range source.Field {
|
||||
for _, v := range refNode.FormConfig {
|
||||
if strings.Contains(v.Label, f) {
|
||||
if v.Label == f {
|
||||
input = append(input, v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,7 +299,22 @@ func GetModelResult(ctx context.Context, sessionId string, nodeInput *flowDto.No
|
||||
if !nodeInput.Global.IsDialogue {
|
||||
sessionId = ""
|
||||
}
|
||||
|
||||
needSequential := false
|
||||
for _, item := range userForm {
|
||||
if g.NewVar(item).IsMap() {
|
||||
valMap := gconv.Map(item)
|
||||
for _, v := range valMap {
|
||||
if g.NewVar(v).IsMap() {
|
||||
vv := gconv.Map(v)
|
||||
for kk, vvv := range vv {
|
||||
if kk == "return_last_frame" {
|
||||
needSequential = vvv.(bool)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
composeResult, err := GetComposeResult(ctx, buildType, nodeInput.Config.ModelConfig.ModelName, nodeInput.Config.PromptContent, skillName, form, userForm, nodeInput.Global.FileUrl, sessionId, nodeInput.Config.Id, nodeInput.Config.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -316,13 +331,12 @@ func GetModelResult(ctx context.Context, sessionId string, nodeInput *flowDto.No
|
||||
mapTaskResult = make([]map[string]any, len(composeResult.Messages.Rounds))
|
||||
var taskResultMap map[string]any
|
||||
|
||||
needSequential := false
|
||||
if buildType == 1 {
|
||||
if needSequential {
|
||||
for idx, item := range composeResult.Messages.Rounds {
|
||||
if !g.IsEmpty(taskResultMap) {
|
||||
var set string
|
||||
set, err = sjson.Set(gconv.String(item), modelInfo.Model.LastFrame, gconv.String(taskResultMap[modelInfo.Model.ResponseBody]))
|
||||
set, err = sjson.Set(gconv.String(item), modelInfo.Model.FirstFrame, gconv.String(taskResultMap["content"]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -339,7 +353,7 @@ func GetModelResult(ctx context.Context, sessionId string, nodeInput *flowDto.No
|
||||
}
|
||||
|
||||
if nodeInput.Config.NodeCode == node.NodeTypeVideoModel {
|
||||
ext := GetFileTypeByPath(gconv.String(taskResult[modelInfo.Model.ResponseBody]))
|
||||
ext := GetFileTypeByPath(gconv.String(taskResult["content"]))
|
||||
if ext == "image" {
|
||||
taskResultMap = taskResult
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user