feat(render): Phase 2 — FlexStory render passthrough + journey template seed

Closes the render boundary so a user's scene list (order, per-scene content,
per-scene duration, theme) actually drives the FlexStory engine — the one gap the
scene-engine mapping found.

- render-svc GetFlexStoryProps (db.go): structured per-scene query that groups
  saved_scene_contents BY scene (the flat GetRenderBindings union collides when
  scenes share keys like "title"), recovers blockId from the scene key
  ("<BlockId>__<n>"), and emits the FlexStory props object
  {scenes:[{blockId,durationSec,props}], accentColor, …}.
- render-svc Claim (internal.go): when the template is Remotion + comp starts with
  "FlexStory", send that object as a single "__flexprops__" binding (no protocol
  struct change).
- node-agent remotionProps (remotion.go): if "__flexprops__" is present, pass it
  to `remotion render --props` verbatim (it's the complete props object).
- scripts/seed_flexstory.py: seeds the CharacterJourney template (7 scenes, theme
  colours, FLEXIBLE) with blockId-encoded scene keys, so the studio's existing
  CopyTemplateGraphAsync copies them into saved_scenes with zero studio-svc change.

Both Go services compile; template is live in the catalog (detail 200, per-aspect
previews). End-to-end render verification needs a live Remotion render node.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-23 13:45:04 +03:30
parent 2104dd3c84
commit f8ea9af3b6
11 changed files with 211 additions and 1 deletions
+10 -1
View File
@@ -321,7 +321,16 @@ func (h *InternalHandler) Claim(c *gin.Context) {
// User's edited input values → the node writes them into the AE project before
// rendering, or passes them as Remotion --props. Non-fatal: empty → template defaults.
bindings, _ := h.store.GetRenderBindings(c.Request.Context(), job.SavedProjectID)
// FlexStory (scene engine) needs the structured per-scene shape (grouped by scene
// + per-scene duration + theme colours); everything else uses the flat union.
var bindings []models.RenderBinding
if rcfg.Engine == "Remotion" && strings.HasPrefix(rcfg.CompName, "FlexStory") {
if flex, ferr := h.store.GetFlexStoryProps(c.Request.Context(), job.SavedProjectID); ferr == nil && flex != "" {
bindings = []models.RenderBinding{{Key: "__flexprops__", Type: "json", Value: flex}}
}
} else {
bindings, _ = h.store.GetRenderBindings(c.Request.Context(), job.SavedProjectID)
}
c.JSON(http.StatusOK, models.ClaimedJob{
JobID: job.ID,