feat(canvas): 批量创作表 - 支持全局提示词、参考图编排与连线同步

This commit is contained in:
ddCat
2026-09-18 13:03:35 +08:00
parent a8770c89b0
commit c994566ffe
13 changed files with 714 additions and 116 deletions

View File

@@ -48,7 +48,7 @@ func TestCloudAgentBatchTableCapabilityMatchesCanvasComponent(t *testing.T) {
if !ok {
t.Fatal("batch-table capability missing")
}
if descriptor.Label != "批量创作表" || descriptor.DefaultWidth != 900 || descriptor.DefaultHeight != 520 || descriptor.InputKind != "text" || descriptor.ProjectionKind != "batch_table" || descriptor.ProjectionField != "batchTable" {
if descriptor.Label != "批量创作表" || descriptor.DefaultWidth != 1280 || descriptor.DefaultHeight != 560 || descriptor.InputKind != "text" || descriptor.ProjectionKind != "batch_table" || descriptor.ProjectionField != "batchTable" {
t.Fatalf("batch-table capability differs from component contract: %+v", descriptor)
}
if err := descriptor.ValidateConnection("image"); err != nil {
@@ -97,6 +97,20 @@ func TestCloudAgentBatchTableToolsAreScopedAndStructured(t *testing.T) {
t.Fatalf("batch row patch schema missing %s", field)
}
}
if properties["globalPrompt"] == nil {
t.Fatal("batch table edit schema missing globalPrompt")
}
actionEnum, _ := properties["action"].(map[string]any)["enum"].([]string)
foundGlobalPrompt := false
for _, action := range actionEnum {
if action == "set_global_prompt" {
foundGlobalPrompt = true
break
}
}
if !foundGlobalPrompt {
t.Fatalf("batch table edit schema missing set_global_prompt: %+v", actionEnum)
}
for _, forbidden := range []string{"outputNodeId", "taskId", "url", "storageKey", "metadata"} {
if patch[forbidden] != nil {
t.Fatalf("batch row patch schema exposes forbidden field %s", forbidden)
@@ -187,6 +201,85 @@ func TestCloudAgentCanReadAndEditBatchTableRows(t *testing.T) {
}
}
func TestCloudAgentCanSetBatchTableGlobalPrompt(t *testing.T) {
s, canvas := cloudAgentBatchTableFixture(t)
doc, err := creationDocument(canvas.PayloadJSON)
if err != nil {
t.Fatal(err)
}
policy, err := s.RuntimePolicy()
if err != nil {
t.Fatal(err)
}
appendCall := cloudAgentBatchTableCall(t, "canvas_edit_batch_table", "append-empty-prompt", map[string]any{
"snapshotHash": cloudAgentCanvasHash(doc), "nodeId": "batch-1", "action": "append",
"patch": map[string]any{"inputNodeIds": []string{"image-1", "image-2"}, "prompt": " "},
})
appendResult, err := applyCloudAgentBatchTableMutation(s.repo, "user", canvas.ID, appendCall, policy)
if err != nil {
t.Fatal(err)
}
state := &cloudAgentRuntime{Request: CloudAgentRequest{CanvasID: canvas.ID}}
readCall := cloudAgentBatchTableCall(t, "canvas_read_batch_table", "read-before-global", map[string]any{"nodeId": "batch-1", "offset": 0})
before, err := cloudAgentReadTool(s.repo, "user", state, readCall)
if err != nil {
t.Fatal(err)
}
beforePreview := before.(map[string]any)["batchTable"].(map[string]any)["generationPreview"].(map[string]any)
if beforePreview["readyRows"] != 0 || beforePreview["missingPromptRows"] != 1 {
t.Fatalf("empty row prompt should not be ready before global prompt: %+v", beforePreview)
}
setCall := cloudAgentBatchTableCall(t, "canvas_edit_batch_table", "set-global", map[string]any{
"snapshotHash": appendResult.(map[string]any)["snapshotHash"], "nodeId": "batch-1", "action": "set_global_prompt",
"globalPrompt": " 全局换装提示词 ",
})
setResult, err := applyCloudAgentBatchTableMutation(s.repo, "user", canvas.ID, setCall, policy)
if err != nil {
t.Fatal(err)
}
after, err := cloudAgentReadTool(s.repo, "user", state, readCall)
if err != nil {
t.Fatal(err)
}
table := after.(map[string]any)["batchTable"].(map[string]any)
if table["globalPrompt"] != "全局换装提示词" && table["globalPrompt"] != " 全局换装提示词 " {
t.Fatalf("global prompt was not projected: %+v", table)
}
preview := table["generationPreview"].(map[string]any)
if preview["readyRows"] != 1 || preview["missingPromptRows"] != 0 {
t.Fatalf("global prompt should make the row ready: %+v", preview)
}
clearCall := cloudAgentBatchTableCall(t, "canvas_edit_batch_table", "clear-global", map[string]any{
"snapshotHash": setResult.(map[string]any)["snapshotHash"], "nodeId": "batch-1", "action": "set_global_prompt",
"globalPrompt": " ",
})
if _, err := applyCloudAgentBatchTableMutation(s.repo, "user", canvas.ID, clearCall, policy); err != nil {
t.Fatal(err)
}
cleared, err := cloudAgentReadTool(s.repo, "user", state, readCall)
if err != nil {
t.Fatal(err)
}
clearedTable := cleared.(map[string]any)["batchTable"].(map[string]any)
if _, exists := clearedTable["globalPrompt"]; exists {
t.Fatalf("empty global prompt should be cleared: %+v", clearedTable)
}
clearedPreview := clearedTable["generationPreview"].(map[string]any)
if clearedPreview["readyRows"] != 0 {
t.Fatalf("cleared global prompt should restore missing prompt: %+v", clearedPreview)
}
reject := cloudAgentBatchTableCall(t, "canvas_edit_batch_table", "reject-global-row", map[string]any{
"snapshotHash": cleared.(map[string]any)["snapshotHash"], "nodeId": "batch-1", "action": "set_global_prompt",
"rowId": "row-should-not-be-accepted", "globalPrompt": "x",
})
if _, err := prepareCloudAgentBatchTableEdit(s.repo, "user", canvas.ID, reject); err == nil || !strings.Contains(err.Error(), "不接受其他修改参数") {
t.Fatalf("set_global_prompt should reject row-level parameters: %v", err)
}
}
func TestCloudAgentBatchTableRejectsUnsafeAndStaleEdits(t *testing.T) {
s, canvas := cloudAgentBatchTableFixture(t)
doc, err := creationDocument(canvas.PayloadJSON)