feat(api): add Lovart-style project and agent thread endpoints

Add canva/canda-compatible routes for lightweight project open/save and
agent thread history:
- POST /canva/project/queryProject, saveProject
- POST /canva/agent/queryAgentLastThread, agentThreadList
- GET /canda/chat-history, /canda/thread/status

Canvas save now accepts a compressed SHAKKERDATA:// snapshot payload,
preserves connections, and returns a SaveProject response; blank
successful image nodes are backfilled from generated artifacts. Frontend
designGateway adopts the new save/query flow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-08 19:10:08 +08:00
parent ada29cee12
commit 263748af4a
27 changed files with 2045 additions and 114 deletions
@@ -119,6 +119,48 @@ func TestRestoreFinishedGeneratedImageNodesRepairsStaleCanvasSnapshot(t *testing
}
}
func TestRestoreFinishedGeneratedImageNodesRepairsBlankSuccessfulImageNode(t *testing.T) {
now := time.Now()
project := design.Project{
ID: "project-blank-generated-node",
Status: design.StatusReady,
Nodes: []design.Node{
{
ID: "blank-node",
Type: design.NodeTypeImage,
Status: "success",
Width: 320,
Height: 240,
},
},
Messages: []design.Message{
toolArtifactsMessage("thread-image", "pants", []design.Node{
{
ID: "artifact-node",
Type: design.NodeTypeImage,
Title: "浅麻灰长裤抠图",
Content: "http://localhost:19000/canvas-assets/pants.png",
Status: "success",
Width: 1024,
Height: 1024,
},
}, now),
},
}
restored, changed := (*DesignService)(nil).restoreFinishedGeneratedImageNodes(project, now.Add(time.Second))
if !changed {
t.Fatal("expected blank generated node to be repaired")
}
if len(restored.Nodes) != 1 {
t.Fatalf("expected one repaired node, got %#v", restored.Nodes)
}
node := restored.Nodes[0]
if node.ID != "blank-node" || node.Content != "http://localhost:19000/canvas-assets/pants.png" || node.Title != "浅麻灰长裤抠图" || node.Width != 1024 || node.Height != 1024 {
t.Fatalf("expected blank node repaired from artifact, got %#v", node)
}
}
func TestMergeSavedCanvasWorkStateDoesNotReapplyStaleGeneratingNodes(t *testing.T) {
existing := []design.Node{
{ID: "generated", Type: design.NodeTypeImage, Title: "Generated", Content: "http://localhost:19000/canvas-assets/generated.png", Status: "success"},