feat(server): persist node semantic context and retain CAD agent context
Add a semantic_context column to canvas_nodes (schema, sqlc queries, generated models, repository upserts) and thread SemanticContext through the domain node, API type, and mappers so CAD-derived context survives canvas saves. Treat agent text parts with textSource "cad-context" like settings: keep them for planning, memory, and image-generation prompts but exclude them from the persisted user-visible message copy. Add coverage for the CAD context visibility and mapper behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"img_infinite_canvas/internal/domain/design"
|
||||
)
|
||||
|
||||
func TestCADContextIsModelInputButNotVisibleUserCopy(t *testing.T) {
|
||||
messages := []design.AgentChatMessage{
|
||||
{
|
||||
Role: "user",
|
||||
Contents: []design.AgentContent{
|
||||
{Type: "text", Text: "Focus on the living room", TextSource: "input"},
|
||||
{Type: "image", ImageURL: "/canvas-assets/floor-plan.webp", ImageName: "floor-plan crop.webp"},
|
||||
{Type: "text", Text: "CAD semantic context: layer ROOMS, block ROOM_A", TextSource: "cad-context"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
modelPrompt := promptFromAgentMessages(messages)
|
||||
visiblePrompt := visiblePromptFromAgentMessages(messages)
|
||||
if !strings.Contains(modelPrompt, "layer ROOMS") {
|
||||
t.Fatalf("expected model prompt to retain CAD semantics, got %q", modelPrompt)
|
||||
}
|
||||
if strings.Contains(visiblePrompt, "layer ROOMS") {
|
||||
t.Fatalf("expected visible user copy to hide CAD semantics, got %q", visiblePrompt)
|
||||
}
|
||||
if !strings.Contains(visiblePrompt, "Focus on the living room") || !strings.Contains(visiblePrompt, "floor-plan crop.webp") {
|
||||
t.Fatalf("expected visible prompt and reference capsule directive, got %q", visiblePrompt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCADContextAloneDoesNotSatisfyPromptValidation(t *testing.T) {
|
||||
messages := []design.AgentChatMessage{{
|
||||
Role: "user",
|
||||
Contents: []design.AgentContent{{Type: "text", Text: "CAD semantic context", TextSource: "cad-context"}},
|
||||
}}
|
||||
if hasUserPromptTextInAgentMessages(messages) {
|
||||
t.Fatal("expected hidden CAD context not to count as user prompt text")
|
||||
}
|
||||
}
|
||||
@@ -669,6 +669,10 @@ func (s *DesignService) AgentChat(ctx context.Context, projectID string, req des
|
||||
if prompt == "" {
|
||||
return design.AgentThread{}, fmt.Errorf("%w: prompt is required", design.ErrInvalidInput)
|
||||
}
|
||||
visiblePrompt := design.NormalizePrompt(visiblePromptFromAgentMessages(req.Messages))
|
||||
if visiblePrompt == "" {
|
||||
visiblePrompt = prompt
|
||||
}
|
||||
|
||||
mode := strings.TrimSpace(req.Mode)
|
||||
if mode == "" {
|
||||
@@ -703,7 +707,7 @@ func (s *DesignService) AgentChat(ctx context.Context, projectID string, req des
|
||||
Role: "user",
|
||||
Type: "user",
|
||||
Title: "新的设计需求",
|
||||
Content: prompt,
|
||||
Content: visiblePrompt,
|
||||
ThreadID: threadID,
|
||||
ActionID: actionID,
|
||||
StepID: newID(),
|
||||
@@ -5153,6 +5157,14 @@ func isGeneratedImageContent(value string) bool {
|
||||
}
|
||||
|
||||
func promptFromAgentMessages(messages []design.AgentChatMessage) string {
|
||||
return promptFromAgentMessagesWithVisibility(messages, false)
|
||||
}
|
||||
|
||||
func visiblePromptFromAgentMessages(messages []design.AgentChatMessage) string {
|
||||
return promptFromAgentMessagesWithVisibility(messages, true)
|
||||
}
|
||||
|
||||
func promptFromAgentMessagesWithVisibility(messages []design.AgentChatMessage, visibleOnly bool) string {
|
||||
var builder strings.Builder
|
||||
for _, message := range messages {
|
||||
if strings.TrimSpace(message.Role) != "" && strings.ToLower(strings.TrimSpace(message.Role)) != "user" {
|
||||
@@ -5161,7 +5173,8 @@ func promptFromAgentMessages(messages []design.AgentChatMessage) string {
|
||||
for _, content := range message.Contents {
|
||||
switch strings.ToLower(strings.TrimSpace(content.Type)) {
|
||||
case "text":
|
||||
if strings.EqualFold(strings.TrimSpace(content.TextSource), "settings") {
|
||||
textSource := strings.TrimSpace(content.TextSource)
|
||||
if strings.EqualFold(textSource, "settings") || (visibleOnly && strings.EqualFold(textSource, "cad-context")) {
|
||||
continue
|
||||
}
|
||||
if text := strings.TrimSpace(content.Text); text != "" {
|
||||
@@ -5207,7 +5220,8 @@ func hasUserPromptTextInAgentMessages(messages []design.AgentChatMessage) bool {
|
||||
if strings.ToLower(strings.TrimSpace(content.Type)) != "text" {
|
||||
continue
|
||||
}
|
||||
if strings.EqualFold(strings.TrimSpace(content.TextSource), "settings") {
|
||||
textSource := strings.TrimSpace(content.TextSource)
|
||||
if strings.EqualFold(textSource, "settings") || strings.EqualFold(textSource, "cad-context") {
|
||||
continue
|
||||
}
|
||||
text := stripPromptReferenceDirectives(stripImageGeneratorDirectiveLines(content.Text))
|
||||
|
||||
Reference in New Issue
Block a user