From d82d4e333353153f7bd17a88d798dd1cb0e0435b Mon Sep 17 00:00:00 2001 From: "songtianyi.theo" Date: Fri, 3 Jul 2026 15:01:11 +0800 Subject: [PATCH] feat: align svglide schemas with anygen experiment --- internal/svglide/init_test.go | 7 ++-- internal/svglide/prompt.go | 57 ++++++++++++++++++++++++++------- internal/svglide/schema_test.go | 26 +++++++++------ 3 files changed, 66 insertions(+), 24 deletions(-) diff --git a/internal/svglide/init_test.go b/internal/svglide/init_test.go index 0dd429c65..fdce2dcae 100644 --- a/internal/svglide/init_test.go +++ b/internal/svglide/init_test.go @@ -148,9 +148,12 @@ func TestInitRunWritesDirectoryContract(t *testing.T) { name string want []string }{ + {name: "request.schema.json", want: []string{`"purpose"`, `"language"`, `"visual_style_query"`}}, + {name: "design_brief.schema.json", want: []string{`"visual_system"`, `"narrative_spine"`, `"depth"`, `"tone"`}}, + {name: "deck.schema.json", want: []string{`"main_title"`, `"style_instruction"`, `"aesthetic_direction"`}}, {name: "sources.schema.json", want: []string{`"retrieval"`}}, - {name: "slide_content.schema.json", want: []string{`"source_refs"`, `"visuals"`}}, - {name: "assets_plan.schema.json", want: []string{`"slide_id"`, `"status"`}}, + {name: "slide_content.schema.json", want: []string{`"source_refs"`, `"visuals"`, `"chart"`, `"table"`, `"crop"`}}, + {name: "assets_plan.schema.json", want: []string{`"experiment_unrestricted_assets"`, `"slide_id"`, `"status"`, `"deferred"`, `"chart"`, `"table"`, `"crop"`}}, {name: "quality.schema.json", want: []string{`"metrics"`}}, } { raw, err := os.ReadFile(filepath.Join(root, "schemas", tc.name)) diff --git a/internal/svglide/prompt.go b/internal/svglide/prompt.go index 228fc8bdd..8e98d71ee 100644 --- a/internal/svglide/prompt.go +++ b/internal/svglide/prompt.go @@ -9,9 +9,12 @@ func DefaultSchemas() map[string]string { "properties": { "title": {"type": "string"}, "input": {"type": "string"}, + "purpose": {"type": "string"}, "audience": {"type": "string"}, "delivery_mode": {"type": "string"}, - "pages": {"type": "integer"} + "language": {"type": "string"}, + "pages": {"type": "integer"}, + "visual_style_query": {"type": "array", "items": {"type": "string"}} } } `, @@ -64,11 +67,25 @@ func DefaultSchemas() map[string]string { "design_brief.schema.json": `{ "type": "object", "additionalProperties": false, - "required": ["narrative_spine", "depth", "tone"], + "required": ["narrative_spine", "depth", "tone", "visual_system"], "properties": { - "narrative_spine": {"type": "string"}, - "depth": {"type": "string"}, - "tone": {"type": "string"} + "design_rationale": {"type": "string"}, + "narrative_spine": {"type": "object"}, + "depth": {"type": "object"}, + "tone": {"type": "string"}, + "visual_system": { + "type": "object", + "required": ["color_system", "typography", "layout_language"], + "properties": { + "color_system": {"type": "object"}, + "typography": {"type": "object"}, + "layout_language": {"type": "object"}, + "imagery_treatment": {"type": "object"}, + "material_texture": {"type": "object"}, + "decoration_language": {"type": "object"}, + "mood_coordinates": {"type": "object"} + } + } } } `, @@ -79,16 +96,30 @@ func DefaultSchemas() map[string]string { "properties": { "color_system": {"type": "object"}, "typography": {"type": "object"}, - "layout_language": {"type": "string"} + "layout_language": {"type": "object"}, + "imagery_treatment": {"type": "object"}, + "material_texture": {"type": "object"}, + "decoration_language": {"type": "object"}, + "mood_coordinates": {"type": "object"} } } `, "deck.schema.json": `{ "type": "object", "additionalProperties": false, - "required": ["title", "slides"], + "required": ["main_title", "style_instruction", "slides"], "properties": { + "main_title": {"type": "string"}, "title": {"type": "string"}, + "style_instruction": { + "type": "object", + "required": ["aesthetic_direction", "color_palette", "typography"], + "properties": { + "aesthetic_direction": {"type": "string"}, + "color_palette": {"type": "object"}, + "typography": {"type": "object"} + } + }, "slides": { "type": "array", "minItems": 1, @@ -99,6 +130,7 @@ func DefaultSchemas() map[string]string { "properties": { "id": {"type": "string"}, "title": {"type": "string"}, + "page_title": {"type": "string"}, "summary": {"type": "string"}, "role": {"type": "string"}, "key_message": {"type": "string"}, @@ -134,7 +166,7 @@ func DefaultSchemas() map[string]string { "required": ["id", "type", "instruction"], "properties": { "id": {"type": "string"}, - "type": {"type": "string", "enum": ["image", "diagram", "icon", "none"]}, + "type": {"type": "string", "enum": ["image", "diagram", "icon", "chart", "table", "crop", "none"]}, "instruction": {"type": "string"} } } @@ -148,8 +180,9 @@ func DefaultSchemas() map[string]string { "assets_plan.schema.json": `{ "type": "object", "additionalProperties": false, - "required": ["assets"], + "required": ["mode", "assets"], "properties": { + "mode": {"type": "string", "enum": ["experiment_unrestricted_assets"]}, "assets": { "type": "array", "items": { @@ -159,10 +192,10 @@ func DefaultSchemas() map[string]string { "properties": { "id": {"type": "string"}, "slide_id": {"type": "string"}, - "type": {"type": "string", "enum": ["image", "diagram", "icon"]}, - "path": {"type": "string", "pattern": "^assets/images/[^./%\\\\:](?:[^/%\\\\:.]|\\.[^./%\\\\:])*$"}, + "type": {"type": "string", "enum": ["image", "diagram", "icon", "chart", "table", "crop"]}, + "path": {"type": "string"}, "usage": {"type": "string"}, - "status": {"type": "string", "enum": ["ready", "missing"]} + "status": {"type": "string", "enum": ["ready", "missing", "deferred"]} } } } diff --git a/internal/svglide/schema_test.go b/internal/svglide/schema_test.go index f4b4eba08..eb63854e1 100644 --- a/internal/svglide/schema_test.go +++ b/internal/svglide/schema_test.go @@ -35,7 +35,7 @@ func TestValidateStageOutputsRejectsDeckSlidePathsThatPreviewRejects(t *testing. t.Run(path, func(t *testing.T) { initStatusTestRun(t) setCurrentStageForStatusTest(t, StageOutline) - mustWriteTestFile(t, "demo/outline/deck.json", `{"title":"Demo Deck","slides":[{"id":"s1","title":"First claim","summary":"First summary","role":"cover","key_message":"First key message","path":"`+path+`"}]}`) + mustWriteTestFile(t, "demo/outline/deck.json", validSchemaDeckJSON(path)) err := ValidateStageOutputs("demo") if err == nil { @@ -51,7 +51,7 @@ func TestValidateStageOutputsRejectsDeckSlidePathsThatPreviewRejects(t *testing. func TestCompleteCurrentStageRejectsInvalidDeckSlidePath(t *testing.T) { initStatusTestRun(t) setCurrentStageForStatusTest(t, StageOutline) - mustWriteTestFile(t, "demo/outline/deck.json", `{"title":"Demo Deck","slides":[{"id":"s1","title":"First claim","summary":"First summary","role":"cover","key_message":"First key message","path":"slides/a%20.svg"}]}`) + mustWriteTestFile(t, "demo/outline/deck.json", validSchemaDeckJSON("slides/a%20.svg")) _, err := CompleteCurrentStage("demo") if err == nil { @@ -204,7 +204,7 @@ func TestValidateStageOutputsRejectsSlideContentMissingSourceRefsOrVisualIds(t * func TestValidateStageOutputsRejectsAssetsMissingStatus(t *testing.T) { initStatusTestRun(t) setCurrentStageForStatusTest(t, StageAssets) - if err := os.WriteFile(filepath.Join("demo", "assets", "assets_plan.json"), []byte(`{"assets":[{"id":"a1","slide_id":"s1","type":"image","path":"assets/images/a.png","usage":"hero image"}]}`), 0o644); err != nil { + if err := os.WriteFile(filepath.Join("demo", "assets", "assets_plan.json"), []byte(`{"mode":"experiment_unrestricted_assets","assets":[{"id":"a1","slide_id":"s1","type":"image","path":"https://example.com/a.png","usage":"hero image"}]}`), 0o644); err != nil { t.Fatal(err) } @@ -217,7 +217,7 @@ func TestValidateStageOutputsRejectsAssetsMissingStatus(t *testing.T) { } } -func TestValidateStageOutputsRejectsUnsafeAssetPaths(t *testing.T) { +func TestValidateStageOutputsAcceptsExperimentAssetPaths(t *testing.T) { tests := []struct { name string path string @@ -229,16 +229,13 @@ func TestValidateStageOutputsRejectsUnsafeAssetPaths(t *testing.T) { t.Run(tt.name, func(t *testing.T) { initStatusTestRun(t) setCurrentStageForStatusTest(t, StageAssets) - if err := os.WriteFile(filepath.Join("demo", "assets", "assets_plan.json"), []byte(`{"assets":[{"id":"a1","slide_id":"s1","type":"image","path":"`+tt.path+`","usage":"hero image","status":"ready"}]}`), 0o644); err != nil { + if err := os.WriteFile(filepath.Join("demo", "assets", "assets_plan.json"), []byte(`{"mode":"experiment_unrestricted_assets","assets":[{"id":"a1","slide_id":"s1","type":"image","path":"`+tt.path+`","usage":"hero image","status":"ready"}]}`), 0o644); err != nil { t.Fatal(err) } err := ValidateStageOutputs("demo") - if err == nil { - t.Fatal("expected asset path schema validation error") - } - if !strings.Contains(err.Error(), "assets/assets_plan.json") || !strings.Contains(err.Error(), "path") { - t.Fatalf("error = %v, want assets/assets_plan.json and path", err) + if err != nil { + t.Fatalf("expected experiment asset path to pass schema validation, got %v", err) } }) } @@ -268,11 +265,20 @@ func TestDefaultSchemasIncludeAnyGenQualityContracts(t *testing.T) { if !strings.Contains(schemas["assets_plan.schema.json"], `"slide_id"`) { t.Fatalf("assets schema missing slide_id: %s", schemas["assets_plan.schema.json"]) } + for _, want := range []string{`"experiment_unrestricted_assets"`, `"chart"`, `"table"`, `"crop"`, `"deferred"`} { + if !strings.Contains(schemas["assets_plan.schema.json"], want) { + t.Fatalf("assets schema missing %s: %s", want, schemas["assets_plan.schema.json"]) + } + } if !strings.Contains(schemas["quality.schema.json"], `"metrics"`) { t.Fatalf("quality schema missing metrics: %s", schemas["quality.schema.json"]) } } +func validSchemaDeckJSON(path string) string { + return `{"main_title":"Demo Deck","style_instruction":{"aesthetic_direction":"Editorial report","color_palette":{},"typography":{}},"slides":[{"id":"s1","title":"First claim","summary":"First summary","role":"cover","key_message":"First key message","path":"` + path + `"}]}` +} + func validQualityReportJSON() string { return `{"status":"passed","issues":[],"metrics":{"slides":1,"sources":1,"web_sources":0,"assets":0,"slides_with_source_refs":1,"slides_with_visuals":1}}` }