44406b72db
Moteva-style AI design workbench replica. Home prompt creates a project; the project page provides an infinite canvas with node drag, zoom/pan, a design chat panel, history replay, image asset upload, and project save/regenerate. - Backend: go-zero, DDD layering, sqlc/pgx, optional PostgreSQL; memory or Redis cache; asynq job queue; MinIO/S3/R2/OSS object storage; sky-valley/pi agent runtime adapter. - Frontend: Next.js App Router + Vite artifact build, TypeScript, i18n, shadcn/ui components, auth (OTP/Turnstile/Google/WeChat). - Deploy: Docker Compose and k3s manifests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
83 lines
2.9 KiB
Go
83 lines
2.9 KiB
Go
package application
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
"unicode/utf8"
|
|
|
|
"img_infinite_canvas/internal/domain/design"
|
|
)
|
|
|
|
func TestAgentShortMemoryUsesSlidingWindow(t *testing.T) {
|
|
now := time.Now()
|
|
project := design.Project{Title: "Memory project"}
|
|
for index := 0; index < 24; index++ {
|
|
project.Messages = append(project.Messages, design.Message{
|
|
ID: fmt.Sprintf("m-%d", index),
|
|
Role: "user",
|
|
Type: "user",
|
|
Content: fmt.Sprintf("历史消息 %02d 需要保留高级品牌风格", index),
|
|
CreatedAt: now.Add(time.Duration(index) * time.Second),
|
|
})
|
|
}
|
|
|
|
memory := buildAgentMemory(project, "继续优化当前画面", "design", nil)
|
|
if !strings.Contains(memory.ShortTerm, "历史消息 23") {
|
|
t.Fatalf("expected latest message in short memory, got %s", memory.ShortTerm)
|
|
}
|
|
if strings.Contains(memory.ShortTerm, "历史消息 00") {
|
|
t.Fatalf("expected oldest message to be outside sliding window, got %s", memory.ShortTerm)
|
|
}
|
|
if utf8.RuneCountInString(memory.ShortTerm) > shortMemoryMaxRunes+80 {
|
|
t.Fatalf("short memory exceeds budget: %d", utf8.RuneCountInString(memory.ShortTerm))
|
|
}
|
|
}
|
|
|
|
func TestAgentMemoryDoesNotLeakRawAssetURLs(t *testing.T) {
|
|
project := design.Project{
|
|
Title: "Asset memory",
|
|
Nodes: []design.Node{
|
|
{ID: "image-1", Type: design.NodeTypeImage, Title: "参考图", Content: "http://localhost:19000/canvas-assets/raw.png", Status: "success"},
|
|
},
|
|
}
|
|
memory := buildAgentMemory(project, "参考这张图继续", "image", []design.AgentChatMessage{
|
|
{
|
|
Contents: []design.AgentContent{
|
|
{Type: "image", ImageURL: "https://example.com/private/upload.png", ImageWidth: 1024, ImageHeight: 1024},
|
|
},
|
|
},
|
|
})
|
|
joined := memory.ShortTerm + "\n" + memory.LongTerm
|
|
if strings.Contains(joined, "localhost:19000") || strings.Contains(joined, "example.com/private") {
|
|
t.Fatalf("expected raw asset urls to be omitted from memory, got %s", joined)
|
|
}
|
|
if !strings.Contains(joined, "reference image") || !strings.Contains(joined, "asset reference") {
|
|
t.Fatalf("expected compact asset references, got %s", joined)
|
|
}
|
|
}
|
|
|
|
func TestAgentLongMemoryKeepsRecentDurableDecisions(t *testing.T) {
|
|
project := design.Project{Title: "Long memory"}
|
|
for index := 0; index < 20; index++ {
|
|
project.Messages = append(project.Messages, design.Message{
|
|
ID: fmt.Sprintf("d-%d", index),
|
|
Role: "user",
|
|
Type: "user",
|
|
Content: fmt.Sprintf("必须保留品牌风格决定 %02d", index),
|
|
})
|
|
}
|
|
|
|
memory := buildAgentMemory(project, "继续", "design", nil)
|
|
if !strings.Contains(memory.LongTerm, "品牌风格决定 19") {
|
|
t.Fatalf("expected recent durable decision in long memory, got %s", memory.LongTerm)
|
|
}
|
|
if strings.Contains(memory.LongTerm, "品牌风格决定 00") {
|
|
t.Fatalf("expected oldest durable decision to be omitted, got %s", memory.LongTerm)
|
|
}
|
|
if utf8.RuneCountInString(memory.LongTerm) > longMemoryMaxRunes+80 {
|
|
t.Fatalf("long memory exceeds budget: %d", utf8.RuneCountInString(memory.LongTerm))
|
|
}
|
|
}
|