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
+47 -16
View File
@@ -12,6 +12,7 @@ import (
"img_infinite_canvas/internal/application"
"img_infinite_canvas/internal/config"
"img_infinite_canvas/internal/domain/brandkit"
"img_infinite_canvas/internal/domain/design"
"img_infinite_canvas/internal/infrastructure/backgroundremoval"
cacheinfra "img_infinite_canvas/internal/infrastructure/cache"
@@ -31,14 +32,15 @@ import (
)
type ServiceContext struct {
Config config.Config
DesignService *application.DesignService
AuthService *authmodule.Service
ShareService *sharingmodule.Service
RealtimeBus realtimemodule.Bus
jobQueue design.JobQueue
jobWorker backgroundWorker
outboxWorker backgroundWorker
Config config.Config
DesignService *application.DesignService
BrandKitService *application.BrandKitService
AuthService *authmodule.Service
ShareService *sharingmodule.Service
RealtimeBus realtimemodule.Bus
jobQueue design.JobQueue
jobWorker backgroundWorker
outboxWorker backgroundWorker
}
type backgroundWorker interface {
@@ -49,6 +51,7 @@ type backgroundWorker interface {
func NewServiceContext(c config.Config) *ServiceContext {
repo := newProjectRepository(c)
brandKitService := newBrandKitService(c)
cacheStore := newCacheStore(c)
authService := newAuthService(c, cacheStore)
shareService := newShareService(c)
@@ -61,6 +64,7 @@ func NewServiceContext(c config.Config) *ServiceContext {
researcher := newResearcher(c)
textExtractor := newTextExtractor(c)
service := application.NewDesignService(repo, agentRunner, assets, researcher, textExtractor, creativeAgent)
service.SetBrandKitService(brandKitService)
service.SetBackgroundRemover(newBackgroundRemover(c))
service.SetObjectRecognizer(newObjectRecognizer(c))
if mockupAnalyzer, ok := creativeAgent.(design.MockupModelAnalyzer); ok {
@@ -75,14 +79,15 @@ func NewServiceContext(c config.Config) *ServiceContext {
}
return &ServiceContext{
Config: c,
DesignService: service,
AuthService: authService,
ShareService: shareService,
RealtimeBus: realtimeBus,
jobQueue: queue,
jobWorker: worker,
outboxWorker: outboxWorker,
Config: c,
DesignService: service,
BrandKitService: brandKitService,
AuthService: authService,
ShareService: shareService,
RealtimeBus: realtimeBus,
jobQueue: queue,
jobWorker: worker,
outboxWorker: outboxWorker,
}
}
@@ -159,6 +164,32 @@ func (s *ServiceContext) Close() {
logx.Errorf("close sharing service failed: %v", err)
}
}
if s.BrandKitService != nil {
if err := s.BrandKitService.Close(); err != nil {
logx.Errorf("close brand kit service failed: %v", err)
}
}
}
func newBrandKitService(c config.Config) *application.BrandKitService {
var store brandkit.Store
switch c.Storage.Driver {
case "", "memory":
store = memory.NewBrandKitStore()
case "postgres":
if strings.TrimSpace(c.Storage.DataSource) == "" {
logx.Must(application.ErrMissingDataSource)
}
postgresStore, err := postgres.NewBrandKitStore(context.Background(), c.Storage.DataSource)
if err != nil {
logx.Must(err)
}
store = postgresStore
default:
logx.Must(application.ErrUnsupportedStorage)
store = memory.NewBrandKitStore()
}
return application.NewBrandKitService(store)
}
func newShareService(c config.Config) *sharingmodule.Service {