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
@@ -50,13 +50,14 @@ func (r *ProjectRepository) List(ctx context.Context) ([]design.ProjectSummary,
items := make([]design.ProjectSummary, 0, len(rows))
for _, row := range rows {
items = append(items, design.ProjectSummary{
ID: row.ID,
UserID: fromPGUUID(row.UserID),
Title: row.Title,
Brief: row.Brief,
Status: design.ProjectStatus(row.Status),
Thumbnail: row.Thumbnail,
UpdatedAt: row.UpdatedAt.Time,
ID: row.ID,
UserID: fromPGUUID(row.UserID),
Title: row.Title,
Brief: row.Brief,
Status: design.ProjectStatus(row.Status),
Thumbnail: row.Thumbnail,
BrandKitID: fromPGText(row.BrandKitID),
UpdatedAt: row.UpdatedAt.Time,
})
}
return items, nil
@@ -81,13 +82,14 @@ func (r *ProjectRepository) ListPage(ctx context.Context, limit int64, offset in
items := make([]design.ProjectSummary, 0, len(rows))
for _, row := range rows {
items = append(items, design.ProjectSummary{
ID: row.ID,
UserID: fromPGUUID(row.UserID),
Title: row.Title,
Brief: row.Brief,
Status: design.ProjectStatus(row.Status),
Thumbnail: row.Thumbnail,
UpdatedAt: row.UpdatedAt.Time,
ID: row.ID,
UserID: fromPGUUID(row.UserID),
Title: row.Title,
Brief: row.Brief,
Status: design.ProjectStatus(row.Status),
Thumbnail: row.Thumbnail,
BrandKitID: fromPGText(row.BrandKitID),
UpdatedAt: row.UpdatedAt.Time,
})
}
return items, nil
@@ -129,6 +131,7 @@ func (r *ProjectRepository) Get(ctx context.Context, id string) (design.Project,
SnapshotID: row.SnapshotID,
Canvas: row.Canvas,
LastThreadID: row.LastThreadID,
BrandKitID: fromPGText(row.BrandKitID),
UpdatedAt: row.UpdatedAt.Time,
Viewport: design.Viewport{
X: row.ViewportX,
@@ -165,6 +168,7 @@ func (r *ProjectRepository) Save(ctx context.Context, project design.Project) er
Version: project.Version,
SnapshotID: project.SnapshotID,
LastThreadID: project.LastThreadID,
BrandKitID: toPGText(project.BrandKitID),
ViewportX: project.Viewport.X,
ViewportY: project.Viewport.Y,
ViewportK: project.Viewport.K,
@@ -463,6 +467,18 @@ func fromPGUUID(value pgtype.UUID) string {
return uuid.UUID(value.Bytes).String()
}
func toPGText(value string) pgtype.Text {
value = strings.TrimSpace(value)
return pgtype.Text{String: value, Valid: value != ""}
}
func fromPGText(value pgtype.Text) string {
if !value.Valid {
return ""
}
return value.String
}
func insertProjectOutboxEvent(ctx context.Context, tx pgx.Tx, project design.Project, eventType string) error {
eventID := uuid.NewString()
createdAt := project.UpdatedAt