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
@@ -280,10 +280,7 @@ func normalizeLayerSeparationInput(input design.GeneratorTaskInputArgs) design.G
input.ImageURL = strings.TrimSpace(input.Image[0])
}
input.ImageURL = strings.TrimSpace(input.ImageURL)
input.OutputFormat = strings.TrimSpace(input.OutputFormat)
if input.OutputFormat == "" {
input.OutputFormat = "psd"
}
input.OutputFormat = ""
input.Prompt = strings.TrimSpace(input.Prompt)
input.Resolution = strings.TrimSpace(input.Resolution)
input.AspectRatio = strings.TrimSpace(input.AspectRatio)
@@ -792,8 +789,8 @@ func generatorTaskInputArgsForNodeAction(target design.Node, req design.NodeActi
if normalizeNodeAction(req.Action) == "move-object" {
input.SrcBox, input.DstBox = moveObjectBoxesFromSelection(req.Selection)
}
if normalizeNodeAction(req.Action) == "edit-elements" && input.OutputFormat == "" {
input.OutputFormat = "psd"
if normalizeNodeAction(req.Action) == "edit-elements" {
input.OutputFormat = ""
}
return input
}