@remixmate/cli 0.12.5 → 0.12.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/manifest.json +2 -2
- package/package.json +1 -1
- package/skills/gen-script/SKILL.md +10 -4
- package/skills/gen-script/scripts/gen_script.py +66 -4
- package/skills/gen-script/version.json +1 -1
- package/skills/render-video/scripts/render_video.py +34 -4
- package/skills/template-registry/scripts/registry_loader.py +5 -0
- package/skills/template-registry/version.json +1 -1
- package/skills/template-registry/video_dsl/runtime/dsl_validator.py +43 -0
- package/skills/template-registry/video_dsl/runtime/payload_contract.py +10 -1
- package/skills/template-registry/video_dsl/runtime/prompt_enhancer.py +3 -1
package/dist/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remixmate/cli",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.7",
|
|
4
4
|
"description": "AI media generation skills for Claude Code / Codex — 12 skills covering image, video, voice, digital human, web screenshot, web recording, script, template registry, rendering, Jianying export, and video deconstruction.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -78,7 +78,7 @@ The intent is to avoid the failure mode where "the generic DSL looks compatible
|
|
|
78
78
|
4. Combine that with the template's `slotMapping`, `requiredProps`, `optionalProps`, `propExtractors`, `assetRequirements`, `supportedAspectRatios`, `supportedDurations`, `constraints`, and `scenePatterns`.
|
|
79
79
|
5. Generate the DSL in the template's native shape — not the generic DSL shape.
|
|
80
80
|
6. Make sure the DSL explicitly contains every template-specific field, e.g. `templateData.words`, `customPayload.slideId`, `visuals.avatar.assetRef`.
|
|
81
|
-
7. After generation, run
|
|
81
|
+
7. After generation, run `gen_script.py --validate -i <dsl.json>` and fix every error it reports, then show the script summary to the user for confirmation. It checks the template contract, not just the DSL shape: undeclared `customPayload` field names, values outside a declared `enum` (e.g. `layout: "bogus"`, `style.preset: "nwes"`), and every `assetRef` / `*AssetRef` (`logoAssetRef`, `avatarAssetRef` …) pointing at an asset that `assets[]` does not declare. These are the same checks render-video runs before spending credits; catching them here saves a confirmed script from being rejected later.
|
|
82
82
|
|
|
83
83
|
### Document-driven templates: pass the source URL, don't author the payload
|
|
84
84
|
|
|
@@ -123,7 +123,7 @@ Picking the layout is only half of it. Each layout reads its own field names out
|
|
|
123
123
|
|
|
124
124
|
This has shipped broken videos: a 7-scene deck guessed generic names (`title`, `description`, `ctaText`) where the chosen layouts read different ones. Five of seven scenes rendered as empty cards and placeholder copy — after paying for TTS and the render. Which names each layout reads is the template's fact; the template's `llmHint` calls out the usual traps.
|
|
125
125
|
|
|
126
|
-
So: **copy the field names out of `slideSchemas[slideId]`.** The DSL validator
|
|
126
|
+
So: **copy the field names out of `slideSchemas[slideId]`.** The DSL validator (`gen_script.py --validate`, and again in render-video) rejects unknown field names and out-of-enum values before any asset is generated and tells you the declared ones — per layout from `slideSchemas` for multi-layout templates, from `customPayloadSchema` for every other template. Reading the contract is still the primary move, not the fallback: the validator checks names and enums, not whether the content fits the layout.
|
|
127
127
|
|
|
128
128
|
### On-screen text: the rules live in the template, not here
|
|
129
129
|
|
|
@@ -283,6 +283,12 @@ Without this flag the user's images are **ignored entirely** and every scene get
|
|
|
283
283
|
AI-generated picture. That failure is quiet: the video renders fine, it just isn't made of
|
|
284
284
|
the material the user handed you.
|
|
285
285
|
|
|
286
|
+
**One shared background.** A template that declares `capabilities.backgroundStrategy: "shared"`
|
|
287
|
+
(e.g. an audiogram whose whole video is one "show poster") gets a skeleton with a single
|
|
288
|
+
`img-background` asset that every scene references, instead of one image per scene. Keep it
|
|
289
|
+
that way; switch to another image only where the template's `llmHint` says you may (such as a
|
|
290
|
+
new chapter). `--scene-images` still maps the user's own images one per scene.
|
|
291
|
+
|
|
286
292
|
### The mapping rule (positional, then AI fills the rest)
|
|
287
293
|
|
|
288
294
|
Each entry is one scene's visual, and it is either a **URL** (use that existing asset) or the
|
|
@@ -473,7 +479,7 @@ python3 <SkillDir>/scripts/gen_script.py \
|
|
|
473
479
|
| `--scenes` | Scene count. | auto-planned |
|
|
474
480
|
| `--voice-id` | Narration voice id. | resolved from template + language fallback |
|
|
475
481
|
| `--speed` | Narration speech rate (0.5–2.0), written to `global.narration.speed` and applied when `prepare_video_assets` calls gen-voice. Pass it whenever the user picked a speed (the replicate form's 语速 field sends one). Above ~1.3 subtitle alignment drifts and the delivery turns mechanical — shorten the script instead. | `1.0` |
|
|
476
|
-
| `--variant` | Template visual variant id (a key of the template's `variants`; `template-registry --list-templates` prints them as `variantIds`). Written to `meta.templateVariant`, which takes precedence over the fuzzy `--style` match. Pass it whenever the user picked a style — the replicate card shows it as `名字(id)`, e.g. `视觉风格: 紫色科技(tech-purple)`; pass the id in the parentheses. Requires `--template-id`; unknown ids are rejected. | template default |
|
|
482
|
+
| `--variant` | Template visual variant id (a key of the template's `variants`; `template-registry --list-templates` prints them as `variantIds`). Written to `meta.templateVariant`, which takes precedence over the fuzzy `--style` match, and the variant's own `imageStyleGuide` is what the skeleton's image prompts / `negativePrompt` are built from (pass it to `gen_script` rather than setting `meta.templateVariant` afterwards, or the prompts keep the template's default look). Pass it whenever the user picked a style — the replicate card shows it as `名字(id)`, e.g. `视觉风格: 紫色科技(tech-purple)`; pass the id in the parentheses. Requires `--template-id`; unknown ids are rejected. | template default |
|
|
477
483
|
| `--background-preset` | Scene background preset, one of the template's `customPayloadSchema.background.preset` values. Written to every scene's `customPayload.background.preset` (other keys of the template's default background are kept). Pass it whenever the user picked a background, e.g. `背景样式: 纯色(solid)` → `solid`. `image` / `video` also need `customPayload.background.assetRef`. Requires `--template-id`; unsupported values are rejected. | template default |
|
|
478
484
|
| `--font-id` | Font library key (`font.uniq_id`), written to `global.font.fontId`; ab-render resolves and installs the real font at render time. Pass it whenever the user picked a font — the replicate card shows it as `名字(id)`, e.g. `字体: <名字>(<uniqId>)`; pass the id in the parentheses. Never pass a CSS family or the display name: an unknown key falls back to the template font with no error. | template font |
|
|
479
485
|
| `--font-name` | Display name of the picked font → `global.font.fontName` (display only, never used for rendering). Pass it with `--font-id`. | — |
|
|
@@ -482,7 +488,7 @@ python3 <SkillDir>/scripts/gen_script.py \
|
|
|
482
488
|
| `--template-candidates` | Repeatable. Template ids offered to the user, recommended first → `meta.templateSelection.candidates`. With `user-confirmed`, `--template-id` must be one of them. | — |
|
|
483
489
|
| `--allow-digital-human` | Whether to allow digital-human assets. | off |
|
|
484
490
|
| `--allow-ai-video` | Whether to allow AI-generated video assets. | off |
|
|
485
|
-
| `--validate` | Validate-only mode:
|
|
491
|
+
| `--validate` | Validate-only mode: validate the input DSL against the DSL schema **and** the template contract (customPayload field names, enum values, asset references). Warnings are printed but do not fail. | — |
|
|
486
492
|
| `--input` | Input DSL file path (required in validate mode). | — |
|
|
487
493
|
| `-o` / `--output` | Output DSL file path. | stdout |
|
|
488
494
|
| `--stub-image-url` | Test mode: every image AssetRef is written as existing + generated + this URL, no prompt (env: `STUB_IMAGE_URL`). | — |
|
|
@@ -302,6 +302,33 @@ def validate_dsl(dsl: dict) -> list:
|
|
|
302
302
|
return errors_as_strings(validate_structural(dsl))
|
|
303
303
|
|
|
304
304
|
|
|
305
|
+
def validate_authored_dsl(dsl: dict) -> tuple[list, list]:
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
from video_dsl.runtime.dsl_validator import (
|
|
316
|
+
validate_integrity,
|
|
317
|
+
validate_structural,
|
|
318
|
+
errors_as_strings,
|
|
319
|
+
)
|
|
320
|
+
found, seen = [], set()
|
|
321
|
+
|
|
322
|
+
for e in validate_structural(dsl) + validate_integrity(dsl):
|
|
323
|
+
key = (e.path, e.message)
|
|
324
|
+
if key not in seen:
|
|
325
|
+
seen.add(key)
|
|
326
|
+
found.append(e)
|
|
327
|
+
errors = [e for e in found if getattr(e, "severity", "error") == "error"]
|
|
328
|
+
warnings = [e for e in found if getattr(e, "severity", "error") != "error"]
|
|
329
|
+
return errors_as_strings(errors), errors_as_strings(warnings)
|
|
330
|
+
|
|
331
|
+
|
|
305
332
|
def plan_scenes(
|
|
306
333
|
topic: str,
|
|
307
334
|
duration: int,
|
|
@@ -609,6 +636,8 @@ def _template_needs_image(template_config: dict | None) -> bool:
|
|
|
609
636
|
|
|
610
637
|
|
|
611
638
|
|
|
639
|
+
|
|
640
|
+
|
|
612
641
|
|
|
613
642
|
|
|
614
643
|
|
|
@@ -674,6 +703,7 @@ def _resolve_contract(template_config: dict | None) -> dict:
|
|
|
674
703
|
"duration_model": caps.get("durationModel"),
|
|
675
704
|
"payload_defaults": payload_defaults,
|
|
676
705
|
"primary_visual_type": primary_visual_type,
|
|
706
|
+
"background_strategy": "shared" if caps.get("backgroundStrategy") == "shared" else "per-scene",
|
|
677
707
|
"default_layout": caps.get("defaultLayout"),
|
|
678
708
|
"authored_scenes": caps.get("authoredScenes") is True,
|
|
679
709
|
|
|
@@ -1518,6 +1548,7 @@ def build_dsl(
|
|
|
1518
1548
|
scene_images: list[str] | None = None,
|
|
1519
1549
|
item_count: int | None = None,
|
|
1520
1550
|
source_document_url: str | None = None,
|
|
1551
|
+
variant: str | None = None,
|
|
1521
1552
|
) -> dict:
|
|
1522
1553
|
|
|
1523
1554
|
|
|
@@ -1708,16 +1739,33 @@ def build_dsl(
|
|
|
1708
1739
|
|
|
1709
1740
|
|
|
1710
1741
|
|
|
1711
|
-
|
|
1742
|
+
|
|
1743
|
+
|
|
1744
|
+
|
|
1745
|
+
image_style_guide = (
|
|
1746
|
+
resolve_style_guide(template_config, variant or "") if _HAS_ENHANCER else {}
|
|
1747
|
+
)
|
|
1712
1748
|
image_compose = _HAS_ENHANCER and is_compose_mode(image_style_guide)
|
|
1713
1749
|
image_ratio = image_style_guide.get("ratio") or ratio
|
|
1714
1750
|
|
|
1715
1751
|
tail_pad_sec = _nb.template_tail_pad_sec(template_config) if _HAS_BUDGET else None
|
|
1752
|
+
|
|
1753
|
+
|
|
1754
|
+
|
|
1755
|
+
|
|
1756
|
+
shared_background = (
|
|
1757
|
+
contract["background_strategy"] == "shared"
|
|
1758
|
+
and primary_visual_type == "image"
|
|
1759
|
+
and not provided_slots
|
|
1760
|
+
)
|
|
1761
|
+
shared_background_planned = False
|
|
1716
1762
|
for idx, plan in enumerate(scene_plans):
|
|
1717
1763
|
scene_id = f"scene-{idx + 1:02d}"
|
|
1718
1764
|
|
|
1719
1765
|
if primary_visual_type == "video":
|
|
1720
1766
|
visual_asset_id = f"video-{scene_id}"
|
|
1767
|
+
elif shared_background:
|
|
1768
|
+
visual_asset_id = "img-background"
|
|
1721
1769
|
else:
|
|
1722
1770
|
visual_asset_id = f"img-{scene_id}"
|
|
1723
1771
|
narration_asset_id = f"narration-{scene_id}" if needs_narration else None
|
|
@@ -1733,7 +1781,11 @@ def build_dsl(
|
|
|
1733
1781
|
scene_has_visual = has_visual and _slot_reads_visual(template_config, plan["purpose"])
|
|
1734
1782
|
|
|
1735
1783
|
|
|
1736
|
-
if scene_has_visual and
|
|
1784
|
+
if scene_has_visual and shared_background and shared_background_planned:
|
|
1785
|
+
pass
|
|
1786
|
+
elif scene_has_visual and primary_visual_type == "image":
|
|
1787
|
+
if shared_background:
|
|
1788
|
+
shared_background_planned = True
|
|
1737
1789
|
slot = provided_slots[idx] if idx < len(provided_slots) else ""
|
|
1738
1790
|
slot_is_ai, slot_prompt = _parse_ai_slot(slot)
|
|
1739
1791
|
if slot and not slot_is_ai:
|
|
@@ -1760,6 +1812,12 @@ def build_dsl(
|
|
|
1760
1812
|
else:
|
|
1761
1813
|
if image_compose:
|
|
1762
1814
|
image_result = {"prompt": COMPOSE_SUBJECT_PLACEHOLDER}
|
|
1815
|
+
elif shared_background:
|
|
1816
|
+
|
|
1817
|
+
|
|
1818
|
+
image_result = generate_image_prompt(
|
|
1819
|
+
"point", topic, style, narration_text="", style_guide=image_style_guide,
|
|
1820
|
+
)
|
|
1763
1821
|
else:
|
|
1764
1822
|
image_result = generate_image_prompt(
|
|
1765
1823
|
plan["purpose"], topic, style,
|
|
@@ -2082,7 +2140,8 @@ Examples:
|
|
|
2082
2140
|
"--variant", default=None,
|
|
2083
2141
|
help="Template visual variant id (a key of the template's `variants`, listed as variantIds by "
|
|
2084
2142
|
"template-registry --list-templates). Written to meta.templateVariant, which overrides the "
|
|
2085
|
-
"fuzzy --style match
|
|
2143
|
+
"fuzzy --style match, and the variant's imageStyleGuide drives the skeleton's image "
|
|
2144
|
+
"prompts. When the user picked a style in the replicate card (shown as "
|
|
2086
2145
|
"`名字(id)`, e.g. `紫色科技(tech-purple)`), pass that id. Requires --template-id.",
|
|
2087
2146
|
)
|
|
2088
2147
|
parser.add_argument(
|
|
@@ -2189,7 +2248,9 @@ Examples:
|
|
|
2189
2248
|
sys.exit(1)
|
|
2190
2249
|
with open(args.input, "r", encoding="utf-8") as f:
|
|
2191
2250
|
dsl = json.load(f)
|
|
2192
|
-
errors =
|
|
2251
|
+
errors, warnings = validate_authored_dsl(dsl)
|
|
2252
|
+
for w in warnings:
|
|
2253
|
+
print(f"⚠️ {w}", file=sys.stderr)
|
|
2193
2254
|
if errors:
|
|
2194
2255
|
print("❌ DSL validation failed:", file=sys.stderr)
|
|
2195
2256
|
for err in errors:
|
|
@@ -2316,6 +2377,7 @@ Examples:
|
|
|
2316
2377
|
scene_images=args.scene_images,
|
|
2317
2378
|
item_count=args.item_count,
|
|
2318
2379
|
source_document_url=args.source_document_url,
|
|
2380
|
+
variant=args.variant,
|
|
2319
2381
|
)
|
|
2320
2382
|
except ValueError as exc:
|
|
2321
2383
|
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
"skillName": "gen-script",
|
|
3
3
|
"repoName": "agent-skill-media-maker",
|
|
4
4
|
"skillId": "474",
|
|
5
|
-
"version": "
|
|
5
|
+
"version": "V22",
|
|
6
6
|
"skillDescription": "Video-script generation skill. Turns a user-supplied topic into a structured Video DSL (JSON) that describes the full video — scene structure, asset requirements, and narrative flow.\n\nUse this skill as soon as the user mentions any of these intents:\n- Write a video script, generate a video script, plan a video, write storyboards\n- Create a short video, plan video content, generate a Video DSL\n- Turn a topic into a video structure / video content plan\n\nEven when the user does not say \"generate the DSL\", use this skill whenever they want to turn a topic into a structured video plan.\n\n⚠️ Stop-and-confirm gate: after this skill returns a DSL, show the full script and wait for the user's explicit confirmation. Never call `prepare_video_assets` in the same turn."
|
|
7
7
|
}
|
|
@@ -779,6 +779,12 @@ def _probe_video_duration(url: str):
|
|
|
779
779
|
return probe_video_duration(url)
|
|
780
780
|
|
|
781
781
|
|
|
782
|
+
def _probe_video_meta(url: str) -> dict:
|
|
783
|
+
|
|
784
|
+
from _video_probe import probe_video_meta
|
|
785
|
+
return probe_video_meta(url)
|
|
786
|
+
|
|
787
|
+
|
|
782
788
|
def build_render_plan(dsl: dict, binding: dict) -> dict:
|
|
783
789
|
|
|
784
790
|
fps = dsl.get("global", {}).get("fps", 30)
|
|
@@ -868,11 +874,35 @@ def build_render_plan(dsl: dict, binding: dict) -> dict:
|
|
|
868
874
|
|
|
869
875
|
|
|
870
876
|
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
|
|
884
|
+
meta_by_url: dict = {}
|
|
871
885
|
for aid, asset in assets_by_id.items():
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
886
|
+
url = asset.get("url")
|
|
887
|
+
if asset["type"] != "video" or asset["source"] != "existing" or not url:
|
|
888
|
+
continue
|
|
889
|
+
need_size = not (asset.get("width") and asset.get("height"))
|
|
890
|
+
need_duration = not asset.get("duration")
|
|
891
|
+
if not (need_size or need_duration):
|
|
892
|
+
continue
|
|
893
|
+
meta = meta_by_url.get(url)
|
|
894
|
+
if meta is None and need_size:
|
|
895
|
+
meta = meta_by_url[url] = _probe_video_meta(url)
|
|
896
|
+
meta = meta or {}
|
|
897
|
+
if need_size and meta.get("width") and meta.get("height"):
|
|
898
|
+
asset["width"], asset["height"] = meta["width"], meta["height"]
|
|
899
|
+
if need_duration:
|
|
900
|
+
if meta.get("duration_ms"):
|
|
901
|
+
asset["duration"] = meta["duration_ms"] / 1000.0
|
|
902
|
+
else:
|
|
903
|
+
probed = _probe_video_duration(url)
|
|
904
|
+
if probed:
|
|
905
|
+
asset["duration"] = probed
|
|
876
906
|
|
|
877
907
|
timeline = []
|
|
878
908
|
current_frame = 0
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
"skillName": "template-registry",
|
|
3
3
|
"repoName": "agent-skill-media-maker",
|
|
4
4
|
"skillId": "475",
|
|
5
|
-
"version": "
|
|
5
|
+
"version": "V29",
|
|
6
6
|
"skillDescription": "Video-template registry skill. Stores every video-template definition and lists the available templates (templateId / name / aspect ratio / style tags).\n\nUse this skill as soon as the user mentions any of these intents:\n- View available templates / list every template\n\nNote: DSL→TemplateBinding is no longer a separate exposed step — once prepare_video_assets receives a template_id it builds the binding internally."
|
|
7
7
|
}
|
|
@@ -1014,6 +1014,10 @@ def _walk_payload(
|
|
|
1014
1014
|
|
|
1015
1015
|
|
|
1016
1016
|
|
|
1017
|
+
|
|
1018
|
+
|
|
1019
|
+
|
|
1020
|
+
|
|
1017
1021
|
errors = _check_payload_object(
|
|
1018
1022
|
data,
|
|
1019
1023
|
schema,
|
|
@@ -1086,6 +1090,10 @@ def _check_payload_object(
|
|
|
1086
1090
|
"the slide renders its built-in placeholder instead of your content.",
|
|
1087
1091
|
))
|
|
1088
1092
|
|
|
1093
|
+
for key, sub in merged_props.items():
|
|
1094
|
+
if key in data and isinstance(sub, dict):
|
|
1095
|
+
errors += _enum_errors(data[key], sub, path=f"{path}.{key}", scene=scene, key=key)
|
|
1096
|
+
|
|
1089
1097
|
|
|
1090
1098
|
|
|
1091
1099
|
for key, sub in props.items():
|
|
@@ -1104,6 +1112,41 @@ def _check_payload_object(
|
|
|
1104
1112
|
return errors
|
|
1105
1113
|
|
|
1106
1114
|
|
|
1115
|
+
def _enum_errors(
|
|
1116
|
+
value: object, schema: dict, *, path: str, scene: str, key: str
|
|
1117
|
+
) -> list[ValidationError]:
|
|
1118
|
+
|
|
1119
|
+
|
|
1120
|
+
|
|
1121
|
+
|
|
1122
|
+
|
|
1123
|
+
enum = schema.get("enum")
|
|
1124
|
+
if isinstance(enum, list) and enum:
|
|
1125
|
+
if value is None or isinstance(value, (dict, list)) or value in enum:
|
|
1126
|
+
return []
|
|
1127
|
+
hint = ""
|
|
1128
|
+
if isinstance(value, str):
|
|
1129
|
+
hit = difflib.get_close_matches(
|
|
1130
|
+
value, [e for e in enum if isinstance(e, str)], n=1, cutoff=0.6
|
|
1131
|
+
)
|
|
1132
|
+
if hit:
|
|
1133
|
+
hint = f" Did you mean '{hit[0]}'?"
|
|
1134
|
+
allowed = ", ".join(repr(e) for e in enum)
|
|
1135
|
+
return [ValidationError(
|
|
1136
|
+
path,
|
|
1137
|
+
f"scene '{scene}': '{key}' is {value!r}, which is not one of [{allowed}] — the "
|
|
1138
|
+
"template ignores an unknown value and falls back to its default, so the look "
|
|
1139
|
+
"you asked for silently never appears." + hint,
|
|
1140
|
+
)]
|
|
1141
|
+
items = schema.get("items")
|
|
1142
|
+
if schema.get("type") == "array" and isinstance(items, dict) and isinstance(value, list):
|
|
1143
|
+
errors: list[ValidationError] = []
|
|
1144
|
+
for i, item in enumerate(value):
|
|
1145
|
+
errors += _enum_errors(item, items, path=f"{path}[{i}]", scene=scene, key=f"{key}[{i}]")
|
|
1146
|
+
return errors
|
|
1147
|
+
return []
|
|
1148
|
+
|
|
1149
|
+
|
|
1107
1150
|
def _is_blank(value: object) -> bool:
|
|
1108
1151
|
|
|
1109
1152
|
|
|
@@ -59,13 +59,22 @@ def required_payload_errors(template: dict, props: dict) -> list[str]:
|
|
|
59
59
|
|
|
60
60
|
def asset_references(value, path: str = "", *, bound_props: bool = False):
|
|
61
61
|
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
62
67
|
if isinstance(value, list):
|
|
63
68
|
for index, item in enumerate(value):
|
|
64
69
|
yield from asset_references(item, f"{path}[{index}]", bound_props=bound_props)
|
|
65
70
|
elif isinstance(value, dict):
|
|
66
71
|
for key, child in value.items():
|
|
67
72
|
child_path = f"{path}.{key}" if path else key
|
|
68
|
-
if
|
|
73
|
+
if (
|
|
74
|
+
key == "assetRef"
|
|
75
|
+
or key.endswith("AssetRef")
|
|
76
|
+
or (bound_props and key.endswith("AssetId"))
|
|
77
|
+
):
|
|
69
78
|
if isinstance(child, str) and child.strip():
|
|
70
79
|
yield child_path, child
|
|
71
80
|
else:
|
|
@@ -462,7 +462,9 @@ def compose_dsl_image_prompts(dsl: dict, template: Optional[dict]) -> list[str]:
|
|
|
462
462
|
|
|
463
463
|
|
|
464
464
|
meta = dsl.get("meta") or {}
|
|
465
|
-
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
variant_id = str(meta.get("templateVariant") or meta.get("variantId") or "")
|
|
466
468
|
if not is_compose_mode(resolve_style_guide(template, variant_id)):
|
|
467
469
|
return []
|
|
468
470
|
|