feat(server): add brand kit management and project binding

Add a brand kit domain with memory and postgres stores, a BrandKitService
for CRUD and resolution, and REST endpoints to list, upsert, and delete
brand kits. Projects can bind a brand kit whose resolved context and
reference images feed into the agent's long-term memory and design prompts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-10 20:58:55 +08:00
parent 3653474355
commit 789f51b29e
38 changed files with 2165 additions and 135 deletions
@@ -210,7 +210,7 @@ func directImagePlan(req design.AgentRequest) design.AgentPlan {
func directImagePrompts(req design.AgentRequest) []design.AgentImagePrompt {
count := plannedGenerationCount(req)
prompt := strings.TrimSpace(req.Prompt)
prompt := directImagePrompt(req)
if prompt == "" {
prompt = fallbackDirectImagePrompt(req)
}
@@ -257,7 +257,7 @@ func directImageTasksSharePrompt(req design.AgentRequest) bool {
}
func directImageBatchPrompt(req design.AgentRequest) design.AgentImagePrompt {
prompt := strings.TrimSpace(req.Prompt)
prompt := directImagePrompt(req)
if prompt == "" {
prompt = fallbackDirectImagePrompt(req)
}
@@ -614,6 +614,12 @@ func referenceImageURLs(req design.AgentRequest) []string {
}
}
}
for _, image := range req.Project.BrandImages {
add(image)
if len(urls) >= 16 {
return urls[:16]
}
}
if len(urls) > 16 {
return urls[:16]
}
@@ -662,9 +668,9 @@ func fallbackImagePrompt(req design.AgentRequest, index int) string {
task := req.TaskPlan.ImageTasks[index]
if brief := strings.TrimSpace(task.Brief); brief != "" {
if title := strings.TrimSpace(task.Title); title != "" && !strings.Contains(brief, title) {
return title + ": " + brief
return imagePromptWithBrandKit(title+": "+brief, req)
}
return brief
return imagePromptWithBrandKit(brief, req)
}
}
return fallbackDirectImagePrompt(req)
@@ -1064,6 +1070,9 @@ func fallbackDirectImagePrompt(req design.AgentRequest) string {
if brief := strings.TrimSpace(req.Project.Brief); brief != "" {
lines = append(lines, "Project brief: "+truncateRunes(brief, 180))
}
if brandContext := strings.TrimSpace(req.Project.BrandContext); brandContext != "" {
lines = append(lines, "Binding Brand Kit: "+truncateRunes(brandContext, 520))
}
if memory := compactFallbackMemory(req.Memory); memory != "" {
lines = append(lines, "Relevant memory: "+memory)
}
@@ -1080,6 +1089,19 @@ func fallbackDirectImagePrompt(req design.AgentRequest) string {
return compactImageGenerationPrompt(strings.Join(lines, "\n"), imageGenerationFallbackMaxRunes)
}
func directImagePrompt(req design.AgentRequest) string {
return imagePromptWithBrandKit(req.Prompt, req)
}
func imagePromptWithBrandKit(prompt string, req design.AgentRequest) string {
prompt = strings.TrimSpace(prompt)
brandContext := strings.TrimSpace(req.Project.BrandContext)
if brandContext == "" {
return prompt
}
return strings.TrimSpace(prompt + "\n\nBinding Brand Kit requirements:\n" + brandContext)
}
func compactFallbackMemory(memory design.AgentMemory) string {
if memory.IsZero() {
return ""