@remixmate/cli 0.9.71 → 0.9.73
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/LICENSE +45 -21
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/argv.js +1 -51
- package/dist/auth/auth-lock.js +1 -100
- package/dist/auth/auto-login.js +7 -75
- package/dist/auth/commands.js +40 -290
- package/dist/auth/credential-store.js +1 -143
- package/dist/auth/device-flow-runner.js +13 -110
- package/dist/auth/device-flow.js +1 -132
- package/dist/auth/ensure.js +1 -62
- package/dist/auth/environment.js +1 -69
- package/dist/auth/pending-store.js +1 -45
- package/dist/auth/resolve.js +1 -44
- package/dist/billing.js +3 -76
- package/dist/capabilities.js +1 -75
- package/dist/cli.js +39 -211
- package/dist/diagnostics.js +2 -70
- package/dist/doctor.js +9 -188
- package/dist/errors.js +1 -33
- package/dist/exec.js +4 -47
- package/dist/handlers/gen-digital-human.js +33 -264
- package/dist/handlers/gen-image.js +9 -113
- package/dist/handlers/gen-post.js +15 -188
- package/dist/handlers/gen-video.js +7 -185
- package/dist/handlers/gen-voice.js +19 -133
- package/dist/handlers/index.js +1 -20
- package/dist/handlers/shared.js +1 -66
- package/dist/handlers/template-init.js +1 -21
- package/dist/http.js +3 -155
- package/dist/index.js +1 -8
- package/dist/install.js +40 -448
- package/dist/manifest.json +53 -13
- package/dist/progress.js +2 -15
- package/dist/project/commands.js +34 -156
- package/dist/project/host.js +1 -151
- package/dist/project/resolve.js +1 -65
- package/dist/project/store.js +2 -97
- package/dist/project/take.js +1 -106
- package/dist/project/trace.js +5 -278
- package/dist/registry.js +3 -70
- package/dist/runner.js +7 -225
- package/dist/skill-schema.js +1 -194
- package/dist/template/args.js +1 -69
- package/dist/template/check.js +2 -30
- package/dist/template/commands.js +34 -163
- package/dist/template/constants.js +1 -46
- package/dist/template/init.js +5 -133
- package/dist/template/list.js +1 -46
- package/dist/template/preview.js +7 -318
- package/dist/template/proc.js +1 -43
- package/dist/template/project.js +6 -161
- package/dist/template/publish.js +5 -185
- package/dist/template/render-client.js +2 -79
- package/dist/template/upgrade.js +4 -123
- package/dist/text.js +1 -15
- package/package.json +2 -35
- package/skills/doc-parse/scripts/extract_via_render.py +32 -32
- package/skills/export-jianying/scripts/gen_jianying_draft.py +188 -188
- package/skills/gen-post/SKILL.md +23 -13
- package/skills/gen-post/skill.json +9 -10
- package/skills/gen-script/SKILL.md +23 -1
- package/skills/gen-script/scripts/gen_script.py +644 -551
- package/skills/gen-script/skill.json +12 -0
- package/skills/gen-script/version.json +1 -1
- package/skills/prepare-video-assets/scripts/prepare_video_assets.py +21 -21
- package/skills/render-video/scripts/_chrome_vendor.py +17 -17
- package/skills/render-video/scripts/_digital_human_segments.py +34 -34
- package/skills/render-video/scripts/_video_probe.py +33 -33
- package/skills/render-video/scripts/_vod_polling.py +13 -13
- package/skills/render-video/scripts/remote_renderer_client.py +42 -42
- package/skills/render-video/scripts/render_video.py +639 -639
- package/skills/render-video/scripts/required_props.py +37 -37
- package/skills/render-video/scripts/upload_video.py +35 -35
- package/skills/render-video/version.json +1 -1
- package/skills/template-registry/SKILL.md +16 -0
- package/skills/template-registry/scripts/countdown_spec.py +92 -92
- package/skills/template-registry/scripts/doc_extract.py +40 -40
- package/skills/template-registry/scripts/list_templates.py +303 -191
- package/skills/template-registry/scripts/log_diagnostics.py +1 -1
- package/skills/template-registry/scripts/match_template.py +87 -87
- package/skills/template-registry/scripts/payload_hydrator.py +89 -89
- package/skills/template-registry/scripts/registry_loader.py +159 -159
- package/skills/template-registry/scripts/render_job_client.py +75 -75
- package/skills/template-registry/scripts/showcase_loader.py +44 -44
- package/skills/template-registry/skill.json +4 -1
- package/skills/template-registry/version.json +1 -1
- package/skills/template-registry/video_dsl/runtime/__init__.py +8 -8
- package/skills/template-registry/video_dsl/runtime/dsl_validator.py +329 -329
- package/skills/template-registry/video_dsl/runtime/narration_budget.py +36 -36
- package/skills/template-registry/video_dsl/runtime/payload_contract.py +7 -7
- package/skills/template-registry/video_dsl/runtime/prompt_enhancer.py +98 -98
- package/skills/template-registry/video_dsl/runtime/timeline_compiler.py +127 -127
- package/skills/template-registry/video_dsl/schema/video-dsl-v1alpha1.json +14 -1
- package/skills/video-parser/scripts/analyze_video.py +173 -173
- package/skills/video-parser/scripts/deconstruct_video.py +91 -91
- package/skills/video-parser/scripts/parse_via_render.py +21 -21
- package/skills/web-screenshot/scripts/_media_screenshot/__init__.py +4 -4
- package/skills/web-screenshot/scripts/_media_screenshot/bootstrap.py +7 -7
- package/skills/web-screenshot/scripts/_media_screenshot/browser.py +4 -4
- package/skills/web-screenshot/scripts/_media_screenshot/cli_args.py +4 -4
- package/skills/web-screenshot/scripts/_media_screenshot/js_loader.py +2 -2
- package/skills/web-screenshot/scripts/_media_screenshot/overlay.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/reader.py +40 -40
- package/skills/web-screenshot/scripts/_media_screenshot/recording.py +11 -11
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/__init__.py +8 -8
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/arrow.py +2 -2
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/caption.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/click.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/code_lines.py +5 -5
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/focus.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/highlight.py +3 -3
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/hover.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/redact.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/scroll.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/sequence.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/title_card.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/type.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/wait.py +1 -1
- package/skills/web-screenshot/scripts/_media_screenshot/scenes/zoom.py +2 -2
- package/skills/web-screenshot/scripts/_media_screenshot/screenshot.py +15 -15
- package/skills/web-screenshot/scripts/_media_screenshot/storyboard.py +2 -2
- package/skills/web-screenshot/scripts/_media_screenshot/template.py +11 -11
- package/skills/web-screenshot/scripts/_media_screenshot/trim.py +10 -10
- package/skills/web-screenshot/scripts/_media_screenshot/urlguard.py +22 -22
- package/skills/web-screenshot/scripts/_media_upload.py +33 -33
- package/skills/web-screenshot/scripts/read_page.py +15 -15
- package/skills/web-screenshot/scripts/record.py +26 -26
- package/skills/web-screenshot/scripts/screenshot.py +5 -5
- package/dist/argv.d.ts +0 -15
- package/dist/auth/auth-lock.d.ts +0 -26
- package/dist/auth/auto-login.d.ts +0 -20
- package/dist/auth/commands.d.ts +0 -8
- package/dist/auth/credential-store.d.ts +0 -60
- package/dist/auth/device-flow-runner.d.ts +0 -58
- package/dist/auth/device-flow.d.ts +0 -52
- package/dist/auth/ensure.d.ts +0 -55
- package/dist/auth/environment.d.ts +0 -38
- package/dist/auth/pending-store.d.ts +0 -33
- package/dist/auth/resolve.d.ts +0 -30
- package/dist/billing.d.ts +0 -44
- package/dist/capabilities.d.ts +0 -63
- package/dist/cli.d.ts +0 -13
- package/dist/diagnostics.d.ts +0 -24
- package/dist/doctor.d.ts +0 -22
- package/dist/errors.d.ts +0 -29
- package/dist/exec.d.ts +0 -18
- package/dist/handlers/gen-digital-human.d.ts +0 -17
- package/dist/handlers/gen-image.d.ts +0 -20
- package/dist/handlers/gen-post.d.ts +0 -21
- package/dist/handlers/gen-video.d.ts +0 -19
- package/dist/handlers/gen-voice.d.ts +0 -26
- package/dist/handlers/index.d.ts +0 -20
- package/dist/handlers/shared.d.ts +0 -26
- package/dist/handlers/template-init.d.ts +0 -10
- package/dist/http.d.ts +0 -72
- package/dist/index.d.ts +0 -9
- package/dist/install.d.ts +0 -71
- package/dist/progress.d.ts +0 -23
- package/dist/project/commands.d.ts +0 -19
- package/dist/project/host.d.ts +0 -91
- package/dist/project/resolve.d.ts +0 -43
- package/dist/project/store.d.ts +0 -31
- package/dist/project/take.d.ts +0 -50
- package/dist/project/trace.d.ts +0 -98
- package/dist/registry.d.ts +0 -30
- package/dist/runner.d.ts +0 -39
- package/dist/skill-schema.d.ts +0 -138
- package/dist/template/args.d.ts +0 -25
- package/dist/template/check.d.ts +0 -23
- package/dist/template/commands.d.ts +0 -25
- package/dist/template/constants.d.ts +0 -39
- package/dist/template/init.d.ts +0 -28
- package/dist/template/list.d.ts +0 -23
- package/dist/template/preview.d.ts +0 -60
- package/dist/template/proc.d.ts +0 -26
- package/dist/template/project.d.ts +0 -47
- package/dist/template/publish.d.ts +0 -43
- package/dist/template/render-client.d.ts +0 -39
- package/dist/template/upgrade.d.ts +0 -44
- package/dist/text.d.ts +0 -8
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
"""
|
|
2
|
-
template-registry 库模块 — DSL → TemplateBinding 的纯函数实现。
|
|
3
1
|
|
|
4
|
-
仅作为 Python library 被 render-video 的 render_video.py / gen-script 的
|
|
5
|
-
gen_script.py 通过 `sys.path.insert` 后 `import match_template` 引入;
|
|
6
|
-
对外不暴露 binding CLI。template-registry skill 的 `--list-templates` 入口由
|
|
7
|
-
sibling 的 `list_templates.py` 实现(它复用 registry_loader 的统一加载 +
|
|
8
|
-
状态门控),两者共享同一份 registry 读取逻辑,无 TS 侧重复实现。
|
|
9
2
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
15
|
|
|
16
16
|
from registry_loader import load_registry_data
|
|
17
17
|
import sys
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def load_registry() -> list:
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
return load_registry_data().get("templates", [])
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
def load_full_template(template: dict) -> dict:
|
|
26
|
-
"""Load the full template definition.
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
32
|
return template
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
def _resolve_slot_def(slot_mapping: dict, purpose: str) -> dict:
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
slot_def = slot_mapping.get(purpose) or slot_mapping.get("default") or {}
|
|
38
38
|
visited: set = set()
|
|
39
39
|
while "$ref" in slot_def:
|
|
@@ -46,15 +46,15 @@ def _resolve_slot_def(slot_mapping: dict, purpose: str) -> dict:
|
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
def _extract_prop(scene: dict, extractor: dict):
|
|
49
|
-
"""Extract a single prop value from a scene using an extractor definition.
|
|
50
49
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
55
|
source = extractor.get("from", "")
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
if source == "textLayers":
|
|
59
59
|
role = extractor.get("role", "")
|
|
60
60
|
for tl in scene.get("textLayers") or []:
|
|
@@ -62,7 +62,7 @@ def _extract_prop(scene: dict, extractor: dict):
|
|
|
62
62
|
return tl.get("content")
|
|
63
63
|
return None
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
|
|
66
66
|
val = scene
|
|
67
67
|
for part in source.split("."):
|
|
68
68
|
if not isinstance(val, dict):
|
|
@@ -72,7 +72,7 @@ def _extract_prop(scene: dict, extractor: dict):
|
|
|
72
72
|
|
|
73
73
|
|
|
74
74
|
def build_binding(template: dict, dsl: dict) -> dict:
|
|
75
|
-
|
|
75
|
+
|
|
76
76
|
bindings = []
|
|
77
77
|
slot_mapping = template.get("slotMapping", {})
|
|
78
78
|
|
|
@@ -84,41 +84,41 @@ def build_binding(template: dict, dsl: dict) -> dict:
|
|
|
84
84
|
comp_id = slot_def.get("compositionId", "GenericScene")
|
|
85
85
|
slot_id = slot_def.get("slotId", f"{purpose}-slot")
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
|
|
88
88
|
props = {}
|
|
89
89
|
for prop_name, extractor in slot_def.get("propExtractors", {}).items():
|
|
90
90
|
val = _extract_prop(scene, extractor)
|
|
91
91
|
if val is not None:
|
|
92
92
|
props[prop_name] = val
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
107
|
custom = scene.get("customPayload") or {}
|
|
108
108
|
template_data = custom.get("templateData") or scene.get("templateData") or {}
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
122
122
|
extractors = slot_def.get("propExtractors", {}) or {}
|
|
123
123
|
top_keys = {k for k in custom.keys() if k != "templateData"}
|
|
124
124
|
dual_channel = []
|
|
@@ -130,7 +130,7 @@ def build_binding(template: dict, dsl: dict) -> dict:
|
|
|
130
130
|
dual_channel.append(k)
|
|
131
131
|
dual_channel = sorted(dual_channel)
|
|
132
132
|
|
|
133
|
-
|
|
133
|
+
|
|
134
134
|
for k, v in template_data.items():
|
|
135
135
|
if k not in props:
|
|
136
136
|
props[k] = v
|
|
@@ -140,16 +140,16 @@ def build_binding(template: dict, dsl: dict) -> dict:
|
|
|
140
140
|
if k not in props:
|
|
141
141
|
props[k] = v
|
|
142
142
|
|
|
143
|
-
|
|
143
|
+
|
|
144
144
|
anim = scene.get("animationHints") or {}
|
|
145
145
|
entrance = anim.get("entrance", "fade")
|
|
146
146
|
transition_type = entrance if entrance != "none" else "cut"
|
|
147
147
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|
|
153
153
|
required_props = slot_def.get("requiredProps") or []
|
|
154
154
|
missing_required = [p for p in required_props if p not in props]
|
|
155
155
|
|
|
@@ -174,33 +174,33 @@ def build_binding(template: dict, dsl: dict) -> dict:
|
|
|
174
174
|
)
|
|
175
175
|
bindings.append(binding_row)
|
|
176
176
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
|
|
181
181
|
|
|
182
182
|
typography = template.get("defaultTypography", {})
|
|
183
183
|
motion = template.get("defaultMotionPreset", "smooth")
|
|
184
184
|
colors = template.get("defaultColorScheme", [])
|
|
185
185
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
|
|
194
194
|
user_font_id = ((dsl.get("global", {}) or {}).get("font", {}) or {}).get("fontId")
|
|
195
195
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
|
|
204
204
|
variant_id = None
|
|
205
205
|
variant_reason = None
|
|
206
206
|
meta = dsl.get("meta", {}) or {}
|
|
@@ -218,7 +218,7 @@ def build_binding(template: dict, dsl: dict) -> dict:
|
|
|
218
218
|
elif variants:
|
|
219
219
|
raw_style = (meta.get("style") or "").strip().lower()
|
|
220
220
|
if raw_style:
|
|
221
|
-
|
|
221
|
+
|
|
222
222
|
s = raw_style
|
|
223
223
|
for sep in [",", ",", ";", ";", " ", "/", "·", "、"]:
|
|
224
224
|
s = s.replace(sep, ",")
|
|
@@ -232,10 +232,10 @@ def build_binding(template: dict, dsl: dict) -> dict:
|
|
|
232
232
|
if "\u4e00" <= ch <= "\u9fff":
|
|
233
233
|
tokens.add(ch)
|
|
234
234
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
|
|
239
239
|
by_tier: dict = {}
|
|
240
240
|
for vid in sorted(variants.keys()):
|
|
241
241
|
vtags = [str(t).strip().lower() for t in (variants[vid].get("styleTags") or []) if str(t).strip()]
|
|
@@ -251,7 +251,7 @@ def build_binding(template: dict, dsl: dict) -> dict:
|
|
|
251
251
|
|
|
252
252
|
if by_tier:
|
|
253
253
|
best_tier = min(by_tier.keys())
|
|
254
|
-
winners = by_tier[best_tier]
|
|
254
|
+
winners = by_tier[best_tier]
|
|
255
255
|
variant_id = winners[0]
|
|
256
256
|
if len(winners) > 1:
|
|
257
257
|
variant_reason = (
|
|
@@ -278,7 +278,7 @@ def build_binding(template: dict, dsl: dict) -> dict:
|
|
|
278
278
|
if vcolors:
|
|
279
279
|
colors = vcolors
|
|
280
280
|
|
|
281
|
-
|
|
281
|
+
|
|
282
282
|
if isinstance(user_font_id, str) and user_font_id.strip():
|
|
283
283
|
typography = {**typography, "fontId": user_font_id.strip()}
|
|
284
284
|
|
|
@@ -1,41 +1,41 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Fill a template-declared ``customPayload`` field from a source document, by reference.
|
|
3
|
-
|
|
4
|
-
问题(2026-09-23 线上 conv_Sesw2g8oM81 的根因)
|
|
5
|
-
---------------------------------------------
|
|
6
|
-
``ppt-to-video`` 要求每个场景都有 ``customPayload.slide`` —— 一整页 PPT 的节点树、
|
|
7
|
-
坐标、配色、beats。这份数据只有一个合法产出方(ab-render 的 ``/extract`` layout 层),
|
|
8
|
-
而它**体积大到不能过模型**:一页带内嵌图标可达几百 KB,23 页就是好几 MB。
|
|
9
|
-
|
|
10
|
-
于是模板的 llmHint 写着「把返回的 slides[i].slide 整个填进 customPayload.slide」,
|
|
11
|
-
doc-parse 的 SKILL.md 写着「feed slides[i] straight into customPayload.slide」,而实际
|
|
12
|
-
链路是:doc_parse 把全量写进本地文件 → 模型没有任何文件读取工具 → 拿不到 →
|
|
13
|
-
prepare_video_assets 报 ``customPayload.slide (required)`` ×N。模型无论怎么改都过不了。
|
|
14
|
-
|
|
15
|
-
解法
|
|
16
|
-
----
|
|
17
|
-
**不让这份数据过模型**。DSL 里只留一个引用(``meta.sourceDocument``),skill 侧在
|
|
18
|
-
校验之前自己去把全量取回来填进每个场景。模型负责的是它真正能负责的东西:旁白、
|
|
19
|
-
场景与页的对应关系、以及 mode 的下压。
|
|
20
|
-
|
|
21
|
-
这与 ``extractCustomPayloadOverrides`` / ``extractNarrationOverrides`` 是同一类教训的
|
|
22
|
-
第三次:**合并层丢掉了数据,而下游报的错指向模型**。前两次丢的是 slideId 和 items。
|
|
23
|
-
|
|
24
|
-
模板怎么声明
|
|
25
|
-
-----------
|
|
26
|
-
``template.json`` 的 ``capabilities.payloadHydration``::
|
|
27
|
-
|
|
28
|
-
"payloadHydration": {
|
|
29
|
-
"payloadField": "slide", # 要填的 customPayload 字段名
|
|
30
|
-
"source": "doc-extract", # 数据来源(目前只有这一个)
|
|
31
|
-
"docKind": "pptx",
|
|
32
|
-
"fields": "content,layout", # 传给 /extract 的层
|
|
33
|
-
"indexField": "slideIndex" # 场景→页的映射键(写在 customPayload 里)
|
|
34
|
-
}
|
|
35
2
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
39
|
|
|
40
40
|
from __future__ import annotations
|
|
41
41
|
|
|
@@ -45,7 +45,7 @@ from typing import Callable, Optional
|
|
|
45
45
|
|
|
46
46
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
47
47
|
|
|
48
|
-
from doc_extract import (
|
|
48
|
+
from doc_extract import (
|
|
49
49
|
DocExtractError,
|
|
50
50
|
extract_document,
|
|
51
51
|
slides_by_index,
|
|
@@ -57,29 +57,29 @@ DEFAULT_INDEX_FIELD = "slideIndex"
|
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
def declared_subfields(template_config: Optional[dict], payload_field: str) -> Optional[set[str]]:
|
|
60
|
-
"""模板为 ``customPayload.<payload_field>`` 声明的子字段名集合。
|
|
61
60
|
|
|
62
|
-
注入必须按这个集合**投影**,不能把抽取器返回的整个对象原样塞进去 ——
|
|
63
|
-
两边的字段集根本不是一回事:
|
|
64
61
|
|
|
65
|
-
抽取器返回(每页) slideIndex / title / text / rawNotes / verdict / coverage /
|
|
66
|
-
flags / nodes / beats / mode / modeReason / ambientIds /
|
|
67
|
-
background / recommendedSec / canvas …
|
|
68
|
-
模板声明(slide) nodes / beats / mode / modeReason / ambientIds /
|
|
69
|
-
background / recommendedSec / canvas
|
|
70
62
|
|
|
71
|
-
前者多出来的那些是**内容层与抽取器自己的记账字段**(备注原文、质检结论、覆盖率),
|
|
72
|
-
渲染端一个都不读。而 ``dsl_validator._check_custom_payload_fields`` 会逐字段核对
|
|
73
|
-
声明表,遇到没声明的就报「the slide does not read 'x' — the value is silently
|
|
74
|
-
dropped」并整单拒绝。2026-09-23 线上 conv_StrnauS7TaZNUQV4BfLX 就是这样:
|
|
75
|
-
注入明明成功(23/23 页),却因为多了 5 个字段被校验整单拒掉,而且**重试必然复现**。
|
|
76
63
|
|
|
77
|
-
为什么用投影而不是写个黑名单:抽取器以后再加字段(verdict / coverage 就是后加的)
|
|
78
|
-
黑名单就会再漏一次。声明表是渲染端真正读的那一份,按它投影才不会二次失效。
|
|
79
64
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
83
|
if not template_config:
|
|
84
84
|
return None
|
|
85
85
|
cps = template_config.get("customPayloadSchema")
|
|
@@ -95,21 +95,21 @@ def declared_subfields(template_config: Optional[dict], payload_field: str) -> O
|
|
|
95
95
|
|
|
96
96
|
|
|
97
97
|
class HydrationError(RuntimeError):
|
|
98
|
-
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
|
|
99
103
|
|
|
100
|
-
这个类存在的全部意义是替换掉 ``customPayload.slide (required)`` ×20 那种报错 ——
|
|
101
|
-
它说的是"某个字段没填",而真相是"这个字段的数据源没有通路",两者要采取的行动
|
|
102
|
-
完全不同,而模型只看得到前者。
|
|
103
|
-
"""
|
|
104
104
|
|
|
105
105
|
|
|
106
106
|
def hydration_spec(template_config: Optional[dict]) -> Optional[dict]:
|
|
107
|
-
"""读 ``capabilities.payloadHydration``;未声明 / 声明无效返回 None。
|
|
108
107
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
113
|
caps = (template_config or {}).get("capabilities")
|
|
114
114
|
if not isinstance(caps, dict):
|
|
115
115
|
return None
|
|
@@ -140,16 +140,16 @@ def hydration_spec(template_config: Optional[dict]) -> Optional[dict]:
|
|
|
140
140
|
|
|
141
141
|
|
|
142
142
|
def source_document_ref(dsl: dict, override_url: Optional[str] = None) -> Optional[dict]:
|
|
143
|
-
"""取 DSL 里的源文件引用。``override_url`` 优先(CLI 显式传入时)。
|
|
144
143
|
|
|
145
|
-
位置是 ``meta.sourceDocument`` 而不是场景级:一条片子对一份源文件,写在场景里
|
|
146
|
-
会得到 23 份同样的 URL,还得处理它们互相矛盾的情况。
|
|
147
144
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|
|
153
153
|
if override_url:
|
|
154
154
|
return {"url": override_url}
|
|
155
155
|
meta = dsl.get("meta")
|
|
@@ -178,16 +178,16 @@ def hydrate_dsl(
|
|
|
178
178
|
override_url: Optional[str] = None,
|
|
179
179
|
log: Optional[Callable[[str], None]] = None,
|
|
180
180
|
) -> list[str]:
|
|
181
|
-
"""把源文件的每一页填进对应场景的 ``customPayload.<payloadField>``。
|
|
182
181
|
|
|
183
|
-
返回一份给人看的报告行列表(调用方自己决定打到哪)。什么都没做时返回空列表。
|
|
184
182
|
|
|
185
|
-
映射规则:场景的 ``customPayload.<indexField>`` 是 1-based 页号,缺省按场景顺序
|
|
186
|
-
取第 i 页。显式页号优先 —— 用户可能只讲其中几页,或者把两页并成一个场景。
|
|
187
183
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
|
|
191
191
|
emit = log or (lambda _msg: None)
|
|
192
192
|
|
|
193
193
|
spec = hydration_spec(template_config)
|
|
@@ -244,13 +244,13 @@ def hydrate_dsl(
|
|
|
244
244
|
filled = 0
|
|
245
245
|
missing: list[str] = []
|
|
246
246
|
|
|
247
|
-
|
|
248
|
-
|
|
247
|
+
|
|
248
|
+
|
|
249
249
|
allowed = declared_subfields(template_config, field)
|
|
250
250
|
dropped: set[str] = set()
|
|
251
251
|
|
|
252
|
-
|
|
253
|
-
|
|
252
|
+
|
|
253
|
+
|
|
254
254
|
index_is_declared = index_field in (declared_subfields(template_config, index_field) or ())
|
|
255
255
|
if not index_is_declared:
|
|
256
256
|
cps = (template_config or {}).get("customPayloadSchema")
|
|
@@ -271,7 +271,7 @@ def hydrate_dsl(
|
|
|
271
271
|
else:
|
|
272
272
|
dropped |= {k for k in page if k not in allowed}
|
|
273
273
|
payload[field] = {k: v for k, v in page.items() if k in allowed}
|
|
274
|
-
|
|
274
|
+
|
|
275
275
|
if index_is_declared:
|
|
276
276
|
payload.setdefault(index_field, page_no)
|
|
277
277
|
filled += 1
|
|
@@ -283,15 +283,15 @@ def hydrate_dsl(
|
|
|
283
283
|
f"{source_meta.get('name', 'the source document')}"
|
|
284
284
|
)
|
|
285
285
|
if dropped:
|
|
286
|
-
|
|
287
|
-
|
|
286
|
+
|
|
287
|
+
|
|
288
288
|
report.append(
|
|
289
289
|
f" (已按模板声明投影,忽略 {len(dropped)} 个渲染端不读的字段:"
|
|
290
290
|
f"{', '.join(sorted(dropped)[:8])})"
|
|
291
291
|
)
|
|
292
292
|
|
|
293
293
|
if missing:
|
|
294
|
-
|
|
294
|
+
|
|
295
295
|
raise HydrationError(
|
|
296
296
|
f"{len(missing)} scene(s) point at a page that the source document does not have "
|
|
297
297
|
f"({'; '.join(missing[:5])}"
|
|
@@ -300,7 +300,7 @@ def hydrate_dsl(
|
|
|
300
300
|
f"or fix customPayload.{index_field} on those scenes."
|
|
301
301
|
)
|
|
302
302
|
|
|
303
|
-
|
|
303
|
+
|
|
304
304
|
flagged = [
|
|
305
305
|
f"page {no}: {'; '.join(page.get('flags') or [])}"
|
|
306
306
|
for no, page in sorted(pages.items())
|