Spaces:
Sleeping
Sleeping
| """ | |
| LangGraph State Definitions | |
| Design System Extractor v2 | |
| Defines the state schema and type hints for LangGraph workflow. | |
| """ | |
| from typing import TypedDict, Annotated, Sequence, Optional | |
| from datetime import datetime | |
| from langgraph.graph.message import add_messages | |
| from core.token_schema import ( | |
| DiscoveredPage, | |
| ExtractedTokens, | |
| NormalizedTokens, | |
| UpgradeRecommendations, | |
| FinalTokens, | |
| Viewport, | |
| ) | |
| # ============================================================================= | |
| # STATE ANNOTATIONS | |
| # ============================================================================= | |
| def merge_lists(left: list, right: list) -> list: | |
| """Merge two lists, avoiding duplicates.""" | |
| seen = set() | |
| result = [] | |
| for item in left + right: | |
| key = str(item) if not hasattr(item, 'url') else item.url | |
| if key not in seen: | |
| seen.add(key) | |
| result.append(item) | |
| return result | |
| def replace_value(left, right): | |
| """Replace left with right (simple override).""" | |
| return right if right is not None else left | |
| # ============================================================================= | |
| # MAIN WORKFLOW STATE | |
| # ============================================================================= | |
| class AgentState(TypedDict): | |
| """ | |
| Main state for the LangGraph workflow. | |
| This state is passed between all agents and accumulates data | |
| as the workflow progresses through stages. | |
| """ | |
| # ------------------------------------------------------------------------- | |
| # INPUT | |
| # ------------------------------------------------------------------------- | |
| base_url: str # The website URL to extract from | |
| # ------------------------------------------------------------------------- | |
| # DISCOVERY STAGE (Agent 1 - Part 1) | |
| # ------------------------------------------------------------------------- | |
| discovered_pages: Annotated[list[DiscoveredPage], merge_lists] | |
| pages_to_crawl: list[str] # User-confirmed pages | |
| # ------------------------------------------------------------------------- | |
| # EXTRACTION STAGE (Agent 1 - Part 2) | |
| # ------------------------------------------------------------------------- | |
| # Desktop extraction | |
| desktop_extraction: Optional[ExtractedTokens] | |
| desktop_crawl_progress: float # 0.0 to 1.0 | |
| # Mobile extraction | |
| mobile_extraction: Optional[ExtractedTokens] | |
| mobile_crawl_progress: float # 0.0 to 1.0 | |
| # ------------------------------------------------------------------------- | |
| # NORMALIZATION STAGE (Agent 2) | |
| # ------------------------------------------------------------------------- | |
| desktop_normalized: Optional[NormalizedTokens] | |
| mobile_normalized: Optional[NormalizedTokens] | |
| # User decisions from Stage 1 review | |
| accepted_colors: list[str] # List of accepted color values | |
| rejected_colors: list[str] # List of rejected color values | |
| accepted_typography: list[str] | |
| rejected_typography: list[str] | |
| accepted_spacing: list[str] | |
| rejected_spacing: list[str] | |
| # ------------------------------------------------------------------------- | |
| # ADVISOR STAGE (Agent 3) | |
| # ------------------------------------------------------------------------- | |
| upgrade_recommendations: Optional[UpgradeRecommendations] | |
| # User selections from Stage 2 playground | |
| selected_type_scale: Optional[str] # ID of selected scale | |
| selected_spacing_system: Optional[str] | |
| selected_naming_convention: Optional[str] | |
| selected_color_ramps: dict[str, bool] # {"primary": True, "secondary": False} | |
| selected_a11y_fixes: list[str] # IDs of accepted fixes | |
| # ------------------------------------------------------------------------- | |
| # GENERATION STAGE (Agent 4) | |
| # ------------------------------------------------------------------------- | |
| desktop_final: Optional[FinalTokens] | |
| mobile_final: Optional[FinalTokens] | |
| # Version info | |
| version_label: str # e.g., "v1-recovered", "v2-upgraded" | |
| # ------------------------------------------------------------------------- | |
| # WORKFLOW METADATA | |
| # ------------------------------------------------------------------------- | |
| current_stage: str # "discover", "extract", "normalize", "advise", "generate", "export" | |
| # Human checkpoints | |
| awaiting_human_input: bool | |
| checkpoint_name: Optional[str] # "confirm_pages", "review_tokens", "select_upgrades", "approve_export" | |
| # Errors and warnings (accumulated) | |
| errors: Annotated[list[str], merge_lists] | |
| warnings: Annotated[list[str], merge_lists] | |
| # Messages for LLM agents (if using chat-based agents) | |
| messages: Annotated[Sequence[dict], add_messages] | |
| # Timing | |
| started_at: Optional[datetime] | |
| stage_started_at: Optional[datetime] | |
| # ============================================================================= | |
| # STAGE-SPECIFIC STATES (for parallel execution) | |
| # ============================================================================= | |
| class DiscoveryState(TypedDict): | |
| """State for page discovery sub-graph.""" | |
| base_url: str | |
| discovered_pages: list[DiscoveredPage] | |
| discovery_complete: bool | |
| error: Optional[str] | |
| class ExtractionState(TypedDict): | |
| """State for extraction sub-graph (per viewport).""" | |
| viewport: Viewport | |
| pages_to_crawl: list[str] | |
| extraction_result: Optional[ExtractedTokens] | |
| progress: float | |
| current_page: Optional[str] | |
| error: Optional[str] | |
| class NormalizationState(TypedDict): | |
| """State for normalization sub-graph.""" | |
| raw_tokens: ExtractedTokens | |
| normalized_tokens: Optional[NormalizedTokens] | |
| duplicates_found: list[tuple[str, str]] | |
| error: Optional[str] | |
| class AdvisorState(TypedDict): | |
| """State for advisor sub-graph.""" | |
| normalized_desktop: NormalizedTokens | |
| normalized_mobile: Optional[NormalizedTokens] | |
| recommendations: Optional[UpgradeRecommendations] | |
| error: Optional[str] | |
| class GenerationState(TypedDict): | |
| """State for generation sub-graph.""" | |
| normalized_tokens: NormalizedTokens | |
| selected_upgrades: dict[str, str] | |
| final_tokens: Optional[FinalTokens] | |
| error: Optional[str] | |
| # ============================================================================= | |
| # CHECKPOINT STATES (Human-in-the-loop) | |
| # ============================================================================= | |
| class PageConfirmationState(TypedDict): | |
| """State for page confirmation checkpoint.""" | |
| discovered_pages: list[DiscoveredPage] | |
| confirmed_pages: list[str] | |
| user_confirmed: bool | |
| class TokenReviewState(TypedDict): | |
| """State for token review checkpoint (Stage 1 UI).""" | |
| desktop_tokens: NormalizedTokens | |
| mobile_tokens: Optional[NormalizedTokens] | |
| # User decisions | |
| color_decisions: dict[str, bool] # {value: accepted} | |
| typography_decisions: dict[str, bool] | |
| spacing_decisions: dict[str, bool] | |
| user_confirmed: bool | |
| class UpgradeSelectionState(TypedDict): | |
| """State for upgrade selection checkpoint (Stage 2 UI).""" | |
| recommendations: UpgradeRecommendations | |
| current_tokens: NormalizedTokens | |
| # User selections | |
| selected_options: dict[str, str] # {category: option_id} | |
| user_confirmed: bool | |
| class ExportApprovalState(TypedDict): | |
| """State for export approval checkpoint (Stage 3 UI).""" | |
| desktop_final: FinalTokens | |
| mobile_final: Optional[FinalTokens] | |
| version_label: str | |
| user_confirmed: bool | |
| # ============================================================================= | |
| # STATE FACTORY FUNCTIONS | |
| # ============================================================================= | |
| def create_initial_state(base_url: str) -> AgentState: | |
| """Create initial state for a new workflow.""" | |
| return { | |
| # Input | |
| "base_url": base_url, | |
| # Discovery | |
| "discovered_pages": [], | |
| "pages_to_crawl": [], | |
| # Extraction | |
| "desktop_extraction": None, | |
| "desktop_crawl_progress": 0.0, | |
| "mobile_extraction": None, | |
| "mobile_crawl_progress": 0.0, | |
| # Normalization | |
| "desktop_normalized": None, | |
| "mobile_normalized": None, | |
| "accepted_colors": [], | |
| "rejected_colors": [], | |
| "accepted_typography": [], | |
| "rejected_typography": [], | |
| "accepted_spacing": [], | |
| "rejected_spacing": [], | |
| # Advisor | |
| "upgrade_recommendations": None, | |
| "selected_type_scale": None, | |
| "selected_spacing_system": None, | |
| "selected_naming_convention": None, | |
| "selected_color_ramps": {}, | |
| "selected_a11y_fixes": [], | |
| # Generation | |
| "desktop_final": None, | |
| "mobile_final": None, | |
| "version_label": "v1-recovered", | |
| # Workflow | |
| "current_stage": "discover", | |
| "awaiting_human_input": False, | |
| "checkpoint_name": None, | |
| "errors": [], | |
| "warnings": [], | |
| "messages": [], | |
| # Timing | |
| "started_at": datetime.now(), | |
| "stage_started_at": datetime.now(), | |
| } | |
| def get_stage_progress(state: AgentState) -> dict: | |
| """Get progress information for the current workflow.""" | |
| stages = ["discover", "extract", "normalize", "advise", "generate", "export"] | |
| current_idx = stages.index(state["current_stage"]) if state["current_stage"] in stages else 0 | |
| return { | |
| "current_stage": state["current_stage"], | |
| "stage_index": current_idx, | |
| "total_stages": len(stages), | |
| "progress_percent": (current_idx / len(stages)) * 100, | |
| "awaiting_human": state["awaiting_human_input"], | |
| "checkpoint": state["checkpoint_name"], | |
| } | |