HomePilot / backend /app /interactive /scene /interaction_layout.py
HomePilot Deploy Bot
chore(hf): sync HomePilot to HF Space
921d377
Raw
History Blame Contribute Delete
3.48 kB
"""
Interaction-UI placement per node.
For each node the runtime needs to know:
- where choice buttons go (bottom-full-bar | bottom-right-stack)
- where hotspots live (x/y as normalized 0..1 coords if any)
- whether a text chat box is visible
- any timer overlay (auto-advance after N seconds)
The frontend reads ``ix_nodes.interaction_layout`` and renders
accordingly. This module writes sensible defaults; designers can
override per-node in the editor.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any, Dict, List
@dataclass(frozen=True)
class InteractionLayout:
# Where choice chips render
choices_placement: str = "bottom_bar" # bottom_bar | right_stack | overlay_center
# Chat input visible for free text?
chat_visible: bool = True
# Hotspots — list of {x, y, w, h, edge_id} where coords are 0..1 normalized
hotspots: List[Dict[str, Any]] = field(default_factory=list)
# Auto-advance timer (0 = disabled)
auto_advance_sec: int = 0
# Show the action catalog right-panel?
show_action_panel: bool = True
# Show the progression (XP/mastery) meter?
show_progress_meter: bool = True
def plan_interaction_layout(
node_kind: str, *, mode: str, branch_count: int = 0,
) -> InteractionLayout:
"""Choose a sensible default layout for a node.
Teaching / enterprise modes hide the right-panel action catalog
to keep the learner focused on the scene's task; social /
mature modes show it so viewers can use level-gated actions.
"""
show_panel = mode in ("social_romantic", "mature_gated", "sfw_general")
show_progress = mode != "sfw_general" # general doesn't use a scheme by default
if node_kind == "decision":
return InteractionLayout(
choices_placement="bottom_bar",
chat_visible=False, # decisions are chip-driven
auto_advance_sec=0,
show_action_panel=show_panel,
show_progress_meter=show_progress,
)
if node_kind == "ending":
return InteractionLayout(
choices_placement="overlay_center",
chat_visible=False,
auto_advance_sec=0,
show_action_panel=False,
show_progress_meter=show_progress,
)
if node_kind == "assessment":
return InteractionLayout(
choices_placement="right_stack",
chat_visible=True,
auto_advance_sec=0,
show_action_panel=False,
show_progress_meter=show_progress,
)
if node_kind == "remediation":
return InteractionLayout(
choices_placement="bottom_bar",
chat_visible=True,
auto_advance_sec=0,
show_action_panel=False,
show_progress_meter=show_progress,
)
# default — scene / merge
return InteractionLayout(
choices_placement="bottom_bar",
chat_visible=True,
auto_advance_sec=0,
show_action_panel=show_panel,
show_progress_meter=show_progress,
)
def to_dict(layout: InteractionLayout) -> Dict[str, Any]:
return {
"choices_placement": layout.choices_placement,
"chat_visible": layout.chat_visible,
"hotspots": list(layout.hotspots),
"auto_advance_sec": layout.auto_advance_sec,
"show_action_panel": layout.show_action_panel,
"show_progress_meter": layout.show_progress_meter,
}