feat(canvas): AI 拆图 - 完善规划、图层编辑与素材复用
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLayerPlanningDeliversOwnedPixelsToVisionModel(t *testing.T) {
|
||||
s, _, pixels := cloudAgentVisionFixture(t)
|
||||
input := canvasGenerationInput{
|
||||
Mode: "text", Prompt: "规划图层", Metadata: map[string]interface{}{"edit": "layer-planning"},
|
||||
ReferenceImages: []providerMedia{{StorageKey: "resource:ref-one", URL: "https://invalid.example/stale.png", DataURL: "data:image/png;base64,c3RhbGU="}},
|
||||
}
|
||||
if err := s.hydrateGenerationMedia("user", &input, providerMediaHydrationPolicy{preferURL: true}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := "data:image/png;base64," + base64.StdEncoding.EncodeToString(pixels)
|
||||
if input.ReferenceImages[0].DataURL != want || input.ReferenceImages[0].URL != "" {
|
||||
t.Fatal("planning did not use the authorized resource's actual pixels")
|
||||
}
|
||||
content, err := textChatContent(input)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
raw, err := json.Marshal(content)
|
||||
if err != nil || !strings.Contains(string(raw), want) || strings.Contains(string(raw), "invalid.example") {
|
||||
t.Fatalf("vision payload lost stored pixels: %v", err)
|
||||
}
|
||||
foreign := canvasGenerationInput{Mode: "text", Metadata: input.Metadata, ReferenceImages: []providerMedia{{StorageKey: "resource:ref-one"}}}
|
||||
if err := s.hydrateGenerationMedia("other-user", &foreign, providerMediaHydrationPolicy{preferURL: true}); err == nil {
|
||||
t.Fatal("planning allowed another user's resource")
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,10 @@ func (s *Service) hydrateGenerationMedia(userID string, input *canvasGenerationI
|
||||
groups := [][]providerMedia{input.ReferenceImages, input.ReferenceVideos, input.ReferenceAudios}
|
||||
for groupIndex, group := range groups {
|
||||
mediaPolicy := policy
|
||||
// 仅 Agent 图片走内存字节;视频、音频及普通生成任务保留原来的协议策略。
|
||||
if groupIndex == 0 && input.Mode == "text" && input.AgentRequests != nil && input.AgentRequests.Canonical != nil {
|
||||
// 识图规划与 Agent 看图都发送归属校验后的实际像素,避免供应商无法读取签名 URL。
|
||||
// 视频、音频及其他普通生成任务保留原来的协议策略。
|
||||
inlineVision := input.Mode == "text" && ((input.AgentRequests != nil && input.AgentRequests.Canonical != nil) || metadataString(input.Metadata, "edit") == "layer-planning")
|
||||
if groupIndex == 0 && inlineVision {
|
||||
mediaPolicy = providerMediaHydrationPolicy{imageOnly: true}
|
||||
if input.Config.CapabilityConfig != nil && input.Config.CapabilityConfig.Text != nil {
|
||||
limits := input.Config.CapabilityConfig.Text.References
|
||||
@@ -107,6 +109,9 @@ func (s *Service) hydrateProviderMedia(userID string, media *providerMedia, poli
|
||||
}
|
||||
mimeType := normalizedMediaMimeType(firstNonEmpty(media.MimeType, resource.MimeType), data)
|
||||
media.DataURL = dataURL(mimeType, data)
|
||||
if policy.imageOnly {
|
||||
media.URL = ""
|
||||
}
|
||||
media.MimeType = mimeType
|
||||
media.Bytes = int64(len(data))
|
||||
media.Width = resource.Width
|
||||
|
||||
@@ -36,6 +36,9 @@ func TestImageToolsProvidersBuildAsyncImageRequests(t *testing.T) {
|
||||
if body["image"] != "https://cdn.example/source.png" {
|
||||
t.Fatalf("source image mapping = %#v", body["image"])
|
||||
}
|
||||
if tc.provider == "image-tools-layer-decomposition" && body["output_format"] != "png" {
|
||||
t.Fatalf("layer outputs must preserve alpha: %#v", body["output_format"])
|
||||
}
|
||||
result, err := adapter.ParseCreate(context.Background(), []byte(`{"data":{"id":"task-image-tool-1","status":"processing"}}`))
|
||||
if err != nil || result.TaskID != "task-image-tool-1" || result.Status != StatusProcessing {
|
||||
t.Fatalf("create result = %#v, err = %v", result, err)
|
||||
|
||||
Reference in New Issue
Block a user