Spaces:
Sleeping
Sleeping
| from __future__ import annotations | |
| from typing import Any, Optional | |
| from app.agents.d3.engine import D3Engine | |
| from app.agents.formula_engine import FormulaEngine | |
| from app.agents.modality_router import ModalityDecision, VisualModalityRouter | |
| from app.agents.shell_visual_engine import ShellVisualEngine | |
| from app.agents.text_ref_engine import TextRefEngine | |
| from app.agents.tutor_agent import TutorAgent | |
| from app.schemas.graph import HTML5VisualPayload | |
| class VisualizationService: | |
| """Thin dispatch boundary in front of the 5 visual-generation engines. | |
| Does no chunk/RAG work itself -- it only (1) delegates modality classification | |
| to `VisualModalityRouter`, and (2) routes an already-decided modality to whichever | |
| of the 5 engines owns it: formula/graph/2d_text/3d/2d_anim. It never declines on | |
| its own, even with zero/weak chunks -- each engine independently handles that by | |
| synthesizing an illustrative result and reporting `HTML5VisualPayload.source` | |
| accordingly, since the student has already explicitly asked for a visual. | |
| """ | |
| def __init__( | |
| self, | |
| router: Optional[VisualModalityRouter] = None, | |
| formula: Optional[FormulaEngine] = None, | |
| text_ref: Optional[TextRefEngine] = None, | |
| d3: Optional[D3Engine] = None, | |
| shell: Optional[ShellVisualEngine] = None, | |
| ) -> None: | |
| self.router = router or VisualModalityRouter() | |
| self.formula = formula or FormulaEngine() | |
| self.text_ref = text_ref or TextRefEngine() | |
| self.d3 = d3 or D3Engine() | |
| self.shell = shell or ShellVisualEngine() | |
| def classify( | |
| self, | |
| selection_text: str, | |
| card_markdown: str, | |
| chunks: list[dict[str, Any]], | |
| familiarity: str, | |
| ) -> ModalityDecision: | |
| return self.router.classify(selection_text, card_markdown, chunks, familiarity) | |
| def generate( | |
| self, | |
| concept: str, | |
| modality: str, | |
| familiarity: str, | |
| chunks: list[dict[str, Any]] | None = None, | |
| web_results: list[dict[str, Any]] | None = None, | |
| ) -> HTML5VisualPayload: | |
| chunks = chunks or [] | |
| if modality == "formula": | |
| return self.formula.generate(concept, chunks, familiarity) | |
| if modality == "2d_text": | |
| return self.text_ref.generate(concept, chunks, familiarity, web_results) | |
| if modality == "graph": | |
| return self.d3.generate(concept, chunks, familiarity) | |
| if modality in ("3d", "2d_anim"): | |
| return self.shell.generate(concept, modality, chunks, familiarity) | |
| # Defensive fallback for a bad/unrecognized modality string -- the router | |
| # itself never produces one of these, since `ModalityDecision.modality` | |
| # is a closed Literal of the 5 real values. | |
| reason = "This one is better discussed in chat." | |
| return HTML5VisualPayload( | |
| html_code=TutorAgent._decline_html(concept, reason), | |
| animation_type="2d_text", | |
| explanation=reason, | |
| ) | |