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
+25
View File
@@ -0,0 +1,25 @@
package logic
import (
"img_infinite_canvas/internal/domain/brandkit"
"img_infinite_canvas/internal/types"
)
func toAPIBrandKit(kit brandkit.Kit) types.BrandKit {
return types.BrandKit{
Id: kit.ID,
Name: kit.Name,
Document: string(kit.Document),
IsDefault: kit.IsDefault,
CreatedAt: formatTime(kit.CreatedAt),
UpdatedAt: formatTime(kit.UpdatedAt),
}
}
func toAPIBrandKits(kits []brandkit.Kit) []types.BrandKit {
items := make([]types.BrandKit, 0, len(kits))
for _, kit := range kits {
items = append(items, toAPIBrandKit(kit))
}
return items
}
@@ -31,7 +31,7 @@ func (l *CreateProjectAsyncLogic) CreateProjectAsync(req *types.CreateProjectReq
return nil, err
}
project, err := l.svcCtx.DesignService.CreateProjectAsync(l.ctx, req.Title, req.Prompt, req.ImageModel, req.ImageSize, req.ImageQuality, int(req.ImageCount), req.EnableWebSearch)
project, err := l.svcCtx.DesignService.CreateProjectAsyncWithBrandKit(l.ctx, req.Title, req.Prompt, req.ImageModel, req.ImageSize, req.ImageQuality, int(req.ImageCount), req.EnableWebSearch, req.BrandKitId)
if err != nil {
return nil, err
}
@@ -31,7 +31,7 @@ func (l *CreateProjectLogic) CreateProject(req *types.CreateProjectRequest) (res
return nil, err
}
project, err := l.svcCtx.DesignService.CreateProject(l.ctx, req.Title, req.Prompt, req.ImageModel, req.ImageSize, req.ImageQuality, int(req.ImageCount), req.EnableWebSearch)
project, err := l.svcCtx.DesignService.CreateProjectWithBrandKit(l.ctx, req.Title, req.Prompt, req.ImageModel, req.ImageSize, req.ImageQuality, int(req.ImageCount), req.EnableWebSearch, req.BrandKitId)
if err != nil {
return nil, err
}
@@ -0,0 +1,40 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package logic
import (
"context"
"img_infinite_canvas/internal/svc"
"img_infinite_canvas/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type DeleteBrandKitLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewDeleteBrandKitLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteBrandKitLogic {
return &DeleteBrandKitLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *DeleteBrandKitLogic) DeleteBrandKit(req *types.BrandKitIdRequest) (resp *types.DeleteBrandKitResponse, err error) {
if _, err := l.svcCtx.BrandKitService.Get(l.ctx, req.Id); err != nil {
return nil, err
}
if err := l.svcCtx.DesignService.ClearBrandKitBindings(l.ctx, req.Id); err != nil {
return nil, err
}
if err := l.svcCtx.BrandKitService.Delete(l.ctx, req.Id); err != nil {
return nil, err
}
return &types.DeleteBrandKitResponse{Id: req.Id, Deleted: true}, nil
}
@@ -0,0 +1,35 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package logic
import (
"context"
"img_infinite_canvas/internal/svc"
"img_infinite_canvas/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type ListBrandKitsLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewListBrandKitsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListBrandKitsLogic {
return &ListBrandKitsLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ListBrandKitsLogic) ListBrandKits() (resp *types.BrandKitListResponse, err error) {
kits, err := l.svcCtx.BrandKitService.List(l.ctx)
if err != nil {
return nil, err
}
return &types.BrandKitListResponse{BrandKits: toAPIBrandKits(kits)}, nil
}
+9 -7
View File
@@ -195,6 +195,7 @@ func toAPIProject(project design.Project) types.Project {
SnapshotId: project.SnapshotID,
Canvas: project.Canvas,
LastThreadId: project.LastThreadID,
BrandKitId: project.BrandKitID,
Viewport: toAPIView(project.Viewport),
Nodes: toAPINodes(project.Nodes),
Connections: toAPIConnections(project.Connections),
@@ -206,13 +207,14 @@ func toAPISummaries(items []design.ProjectSummary) []types.ProjectSummary {
resp := make([]types.ProjectSummary, 0, len(items))
for _, item := range items {
resp = append(resp, types.ProjectSummary{
Id: item.ID,
UserId: item.UserID,
Title: item.Title,
Brief: item.Brief,
Status: string(item.Status),
Thumbnail: item.Thumbnail,
UpdatedAt: formatTime(item.UpdatedAt),
Id: item.ID,
UserId: item.UserID,
Title: item.Title,
Brief: item.Brief,
Status: string(item.Status),
Thumbnail: item.Thumbnail,
BrandKitId: item.BrandKitID,
UpdatedAt: formatTime(item.UpdatedAt),
})
}
return resp
@@ -86,6 +86,7 @@ func toQueryProjectResponse(project design.Project, permission string, visibilit
Version: project.Version,
SnapshotId: nil,
ItemId: nil,
BrandKitId: project.BrandKitID,
Permission: permission,
},
}
@@ -0,0 +1,39 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package logic
import (
"context"
sharingmodule "img_infinite_canvas/internal/modules/sharing"
"img_infinite_canvas/internal/svc"
"img_infinite_canvas/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type UpdateProjectBrandKitLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewUpdateProjectBrandKitLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateProjectBrandKitLogic {
return &UpdateProjectBrandKitLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *UpdateProjectBrandKitLogic) UpdateProjectBrandKit(req *types.UpdateProjectBrandKitRequest) (resp *types.ProjectResponse, err error) {
if access, ok := sharingmodule.AccessFromContext(l.ctx); ok && !access.IsOwner {
return nil, sharingmodule.ErrForbidden
}
project, err := l.svcCtx.DesignService.UpdateProjectBrandKit(l.ctx, req.Id, req.BrandKitId)
if err != nil {
return nil, err
}
return toProjectResponse(project), nil
}
@@ -0,0 +1,35 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package logic
import (
"context"
"img_infinite_canvas/internal/svc"
"img_infinite_canvas/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type UpsertBrandKitLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewUpsertBrandKitLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpsertBrandKitLogic {
return &UpsertBrandKitLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *UpsertBrandKitLogic) UpsertBrandKit(req *types.UpsertBrandKitRequest) (resp *types.BrandKitResponse, err error) {
kit, err := l.svcCtx.BrandKitService.Upsert(l.ctx, req.Id, req.Document, req.IsDefault)
if err != nil {
return nil, err
}
return &types.BrandKitResponse{BrandKit: toAPIBrandKit(kit)}, nil
}