feat(piagent): bypass planner for direct image generation modes

For image / pure-image / image-generator / image-action modes, skip the
task-planning round trip and hand the prompt straight to the image model,
deriving the requested image count from the composer settings. Also
normalize completed image-generator nodes back into regular image nodes
(clearing the image-generator layer role and defaulting Tone to "visual")
when merging generated results and when restoring finished nodes, so a
finished generator node is not re-appended as a duplicate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-09 01:09:59 +08:00
parent a39e18950c
commit ffd8d78df7
5 changed files with 573 additions and 60 deletions
@@ -50,6 +50,44 @@ func TestMergeGeneratedNodesIntoImageGeneratorTargetUsesGeneratedSizeAndKeepsCen
}
}
func TestMergeGeneratedNodesIntoCompletedImageGeneratorTargetDoesNotReappend(t *testing.T) {
nodes := []design.Node{
{
ID: "generator-1",
Type: design.NodeTypeImage,
Title: "旧图",
Content: "https://example.com/old.png",
Tone: "visual",
Status: "success",
LayerRole: "",
Width: 360,
Height: 240,
},
}
generated := []design.Node{
{
ID: "generator-1",
Type: design.NodeTypeImage,
Title: "新图",
Content: "https://example.com/new.png",
Tone: "image-generator",
Status: "success",
LayerRole: "image-generator",
Width: 1024,
Height: 1024,
},
}
merged := mergeGeneratedNodesIntoTarget(nodes, generated, "generator-1")
if len(merged) != 1 {
t.Fatalf("expected existing target to be updated in place, got %#v", merged)
}
node := merged[0]
if node.Content != "https://example.com/new.png" || node.LayerRole != "" || node.Tone != "visual" {
t.Fatalf("expected completed image generator to become regular image, got %#v", node)
}
}
func TestImageGeneratorTargetNodeIDParsesDirectiveCaseInsensitively(t *testing.T) {
targetID := imageGeneratorTargetNodeID([]design.AgentChatMessage{
{
@@ -64,6 +102,36 @@ func TestImageGeneratorTargetNodeIDParsesDirectiveCaseInsensitively(t *testing.T
}
}
func TestRestoreFinishedGeneratedImageNodesConvertsCompletedImageGeneratorNode(t *testing.T) {
now := time.Now()
project := design.Project{
ID: "project-completed-generator",
Status: design.StatusReady,
Nodes: []design.Node{
{
ID: "generator-1",
Type: design.NodeTypeImage,
Title: "生成图片",
Content: "http://localhost:19000/canvas-assets/cat.png",
Tone: "image-generator",
Status: "success",
LayerRole: "image-generator",
Width: 1024,
Height: 1024,
},
},
}
restored, changed := (*DesignService)(nil).restoreFinishedGeneratedImageNodes(project, now.Add(time.Second))
if !changed {
t.Fatal("expected completed image generator node to be normalized")
}
node := restored.Nodes[0]
if node.LayerRole != "" || node.Tone != "visual" || node.Status != "success" || node.Content != "http://localhost:19000/canvas-assets/cat.png" {
t.Fatalf("expected regular completed image node, got %#v", node)
}
}
func TestRestoreFinishedGeneratedImageNodesRepairsStaleCanvasSnapshot(t *testing.T) {
now := time.Now()
project := design.Project{