feat(canvas): build PSD export in the browser from canvas layers

Move PSD generation off the server. The image-generation API only
produces raster formats, so drop the PSD request path, the oov/psd
decoder, and the LayeredDocumentGenerator wiring. Layer separation now
returns clean background, foreground, and text_render_data artifacts
into a transparent frame.

Export that frame (or any image node) as PSD from the canvas context
menu: ag-psd assembles layers, rotation/flip, opacity, editable text,
and stroke effects from the current canvas state at download time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-10 11:01:46 +08:00
parent 4ff246a294
commit 60130b3d9b
17 changed files with 424 additions and 614 deletions
-17
View File
@@ -59,7 +59,6 @@ func NewServiceContext(c config.Config) *ServiceContext {
textExtractor := newTextExtractor(c)
service := application.NewDesignService(repo, agentRunner, assets, researcher, textExtractor, creativeAgent)
service.SetBackgroundRemover(newBackgroundRemover(c))
service.SetLayeredDocumentGenerator(newLayeredDocumentGenerator(c))
service.SetObjectRecognizer(newObjectRecognizer(c))
if mockupAnalyzer, ok := creativeAgent.(design.MockupModelAnalyzer); ok {
service.SetMockupModelAnalyzer(mockupAnalyzer)
@@ -318,22 +317,6 @@ func newImageGenerator(c config.Config) piagent.ImageGenerator {
return client
}
func newLayeredDocumentGenerator(c config.Config) design.LayeredDocumentGenerator {
opts := c.Agent.Image
apiKey := resolveImageAPIKey(opts)
client := piagent.NewImageClient(piagent.ImageClientOptions{
BaseURL: opts.BaseURL,
APIKey: apiKey,
InputImageTransport: opts.InputImageTransport,
TimeoutSeconds: opts.TimeoutSeconds,
ResponseHeaderTimeoutSeconds: opts.ResponseHeaderTimeoutSeconds,
})
if client == nil {
return nil
}
return client
}
func newTextExtractor(c config.Config) design.TextExtractor {
opts := c.Agent.Text
switch opts.Driver {