feat: 支持模板子流程递归拷贝与段级续跑优化

* 模板拷贝用户流程时递归复制子流程,并重写 sub_flow 节点引用
* 段级续跑改用列表位置作为段身份,移除对 segment_index 的依赖
* 段结果保存移到每段生成完成时立即落库,降低崩溃丢失风险
* 移除视频分段续跑设计与对应测试
This commit is contained in:
2026-08-25 13:56:55 +08:00
parent 855d0cca72
commit b3f4b94b21
8 changed files with 123 additions and 426 deletions
@@ -1,124 +0,0 @@
package flow
import (
"testing"
"ai-agent/workflow/model/entity"
)
// hasDistinctSegmentIndex:各段都带 segment_index 且互不重复 → 允许段级续跑
func TestHasDistinctSegmentIndex(t *testing.T) {
distinct := []map[string]any{{"segment_index": 1}, {"segment_index": 2}, {"segment_index": 3}}
if !hasDistinctSegmentIndex(distinct) {
t.Fatal("互不重复的 segment_index 应返回 true")
}
missing := []map[string]any{{"segment_index": 1}, {"other": 2}}
if hasDistinctSegmentIndex(missing) {
t.Fatal("缺失 segment_index 应返回 false")
}
duplicate := []map[string]any{{"segment_index": 1}, {"segment_index": 1}}
if hasDistinctSegmentIndex(duplicate) {
t.Fatal("重复 segment_index 应返回 false")
}
if hasDistinctSegmentIndex(nil) {
t.Fatal("空列表应返回 false")
}
if hasDistinctSegmentIndex([]map[string]any{}) {
t.Fatal("空列表(非 nil)应返回 false")
}
}
// planSegmentResume:段序号从 params 的 segment_index 读出;已成功段复用,缺失/空地址需生成
func TestPlanSegmentResume(t *testing.T) {
paramsList := []map[string]any{
{"segment_index": 1}, {"segment_index": 2}, {"segment_index": 3}, {"segment_index": 4}, {"segment_index": 5},
}
saved := map[int]entity.SegmentRef{
1: {Key: "video_url", URL: "u1"}, 2: {Key: "video_url", URL: "u2"},
3: {Key: "video_url", URL: "u3"}, 5: {Key: "video_url", URL: "u5"},
}
idxList, needGen := planSegmentResume(paramsList, saved)
if len(idxList) != 5 || len(needGen) != 5 {
t.Fatalf("长度不符: idxList=%v needGen=%v", idxList, needGen)
}
want := []bool{false, false, false, true, false} // 仅段4需重新生成
for i := range want {
if needGen[i] != want[i] {
t.Fatalf("needGen[%d]=%v 期望 %v", i, needGen[i], want[i])
}
if idxList[i] != i+1 {
t.Fatalf("idxList[%d]=%d 期望 %d", i, idxList[i], i+1)
}
}
}
// planSegmentResume:无已成功段(全新执行)→ 全部需生成
func TestPlanSegmentResumeEmptySaved(t *testing.T) {
paramsList := []map[string]any{{"segment_index": 0}, {"segment_index": 1}}
_, needGen := planSegmentResume(paramsList, nil)
for i := range needGen {
if !needGen[i] {
t.Fatalf("needGen[%d] 期望 true(无复用)", i)
}
}
}
// mergeSegmentOutputs:复用段 + 新生段按段序号升序,顺序即拼接顺序
func TestMergeSegmentOutputs(t *testing.T) {
idxList := []int{1, 2, 3, 4, 5}
needGen := []bool{false, false, false, true, false}
newRes := make([][]map[string]any, 5)
newRes[3] = []map[string]any{{"video_url": "new4"}}
saved := map[int]entity.SegmentRef{
1: {Key: "video_url", URL: "u1"}, 2: {Key: "video_url", URL: "u2"},
3: {Key: "video_url", URL: "u3"}, 5: {Key: "video_url", URL: "u5"},
}
got := mergeSegmentOutputs(idxList, needGen, newRes, saved)
if len(got) != 5 {
t.Fatalf("合并后应有 5 段,实际 %d: %v", len(got), got)
}
wantURLs := []string{"u1", "u2", "u3", "new4", "u5"}
for i, rec := range got {
if rec["video_url"] != wantURLs[i] {
t.Fatalf("第 %d 段=%v 期望 %s", i, rec, wantURLs[i])
}
}
}
// mergeSegmentOutputs:复用段 key 保持模型原字段,重建记录与新生记录 key 不一致也不影响顺序
func TestMergeSegmentOutputsMixedKeys(t *testing.T) {
idxList := []int{1, 2, 3}
needGen := []bool{false, true, false}
newRes := make([][]map[string]any, 3)
newRes[1] = []map[string]any{{"video_oss_url": "new2"}}
saved := map[int]entity.SegmentRef{
1: {Key: "video_url", URL: "u1"}, 3: {Key: "file_url", URL: "u3"},
}
got := mergeSegmentOutputs(idxList, needGen, newRes, saved)
if len(got) != 3 {
t.Fatalf("合并后应有 3 段,实际 %d: %v", len(got), got)
}
if got[0]["video_url"] != "u1" || got[1]["video_oss_url"] != "new2" || got[2]["file_url"] != "u3" {
t.Fatalf("合并结果不符: %v", got)
}
}
// mergeSegmentOutputsparamsList 乱序时仍按段序号升序输出(顺序保证)
func TestMergeSegmentOutputsUnorderedParams(t *testing.T) {
idxList := []int{3, 1, 2}
needGen := []bool{true, false, true}
newRes := make([][]map[string]any, 3)
newRes[0] = []map[string]any{{"video_url": "new3"}}
newRes[2] = []map[string]any{{"video_url": "new2"}}
saved := map[int]entity.SegmentRef{1: {Key: "video_url", URL: "u1"}}
got := mergeSegmentOutputs(idxList, needGen, newRes, saved)
if len(got) != 3 {
t.Fatalf("合并后应有 3 段,实际 %d: %v", len(got), got)
}
wantURLs := []string{"u1", "new2", "new3"}
for i, rec := range got {
if rec["video_url"] != wantURLs[i] {
t.Fatalf("第 %d 段=%v 期望 %s(应段序升序)", i, rec, wantURLs[i])
}
}
}