project-standalo-sonic-cloud/skills/guardrail-orchestrator/scripts/visualize_design.py

344 lines
14 KiB
Python

#!/usr/bin/env python3
"""
Design visualization for guardrail workflow.
Generates ASCII art visualization of pages, components, and API endpoints
from the project manifest.
Usage:
python3 visualize_design.py --manifest project_manifest.json
"""
import argparse
import json
import os
import sys
from pathlib import Path
def load_manifest(manifest_path: str) -> dict | None:
"""Load manifest if it exists."""
if not os.path.exists(manifest_path):
return None
try:
with open(manifest_path) as f:
return json.load(f)
except (json.JSONDecodeError, IOError):
return None
def get_status_icon(status: str) -> str:
"""Get icon for entity status."""
icons = {
'PENDING': '',
'APPROVED': '',
'IMPLEMENTED': '🟢',
'IN_PROGRESS': '🔄',
'REJECTED': '',
}
return icons.get(status, '')
def visualize_page(page: dict, components: list, indent: str = "") -> list:
"""Generate ASCII visualization for a page."""
lines = []
name = page.get('name', 'Unknown')
status = page.get('status', 'PENDING')
file_path = page.get('file_path', '')
description = page.get('description', '')
icon = get_status_icon(status)
# Page header
lines.append(f"{indent}{'' * 60}")
lines.append(f"{indent}{icon} PAGE: {name:<50}")
lines.append(f"{indent}{' ' * 3}Path: {file_path:<48}")
if description:
desc_short = description[:45] + '...' if len(description) > 45 else description
lines.append(f"{indent}{' ' * 3}Desc: {desc_short:<48}")
lines.append(f"{indent}{'' * 60}")
# Find components used by this page
page_components = []
page_id = page.get('id', '')
for comp in components:
deps = comp.get('dependencies', [])
used_by = comp.get('used_by', [])
if page_id in deps or page_id in used_by or page.get('name', '').lower() in str(comp).lower():
page_components.append(comp)
if page_components:
lines.append(f"{indent}│ COMPONENTS: │")
for comp in page_components:
comp_name = comp.get('name', 'Unknown')
comp_status = comp.get('status', 'PENDING')
comp_icon = get_status_icon(comp_status)
lines.append(f"{indent}{comp_icon} {comp_name:<53}")
else:
lines.append(f"{indent}│ (No components defined yet) │")
lines.append(f"{indent}{'' * 60}")
return lines
def visualize_component_tree(components: list) -> list:
"""Generate ASCII tree of components."""
lines = []
if not components:
return [" (No components defined)"]
lines.append("┌─────────────────────────────────────────────────────────────┐")
lines.append("│ 🧩 COMPONENTS │")
lines.append("├─────────────────────────────────────────────────────────────┤")
for i, comp in enumerate(components):
name = comp.get('name', 'Unknown')
status = comp.get('status', 'PENDING')
file_path = comp.get('file_path', '')
icon = get_status_icon(status)
is_last = i == len(components) - 1
prefix = "└──" if is_last else "├──"
lines.append(f"{prefix} {icon} {name:<50}")
lines.append(f"{' ' if is_last else ''} {file_path:<50}")
lines.append("└─────────────────────────────────────────────────────────────┘")
return lines
def visualize_api_endpoints(endpoints: list) -> list:
"""Generate ASCII visualization of API endpoints."""
lines = []
if not endpoints:
return []
lines.append("┌─────────────────────────────────────────────────────────────┐")
lines.append("│ 🔌 API ENDPOINTS │")
lines.append("├─────────────────────────────────────────────────────────────┤")
for endpoint in endpoints:
name = endpoint.get('name', 'Unknown')
method = endpoint.get('method', 'GET')
path = endpoint.get('path', endpoint.get('file_path', ''))
status = endpoint.get('status', 'PENDING')
icon = get_status_icon(status)
method_colors = {
'GET': '🟢',
'POST': '🟡',
'PUT': '🟠',
'PATCH': '🟠',
'DELETE': '🔴',
}
method_icon = method_colors.get(method.upper(), '')
lines.append(f"{icon} {method_icon} {method.upper():<6} {name:<45}")
lines.append(f"│ Path: {path:<47}")
lines.append("└─────────────────────────────────────────────────────────────┘")
return lines
def visualize_page_flow(pages: list) -> list:
"""Generate ASCII flow diagram of pages."""
lines = []
if not pages:
return []
lines.append("")
lines.append("📱 PAGE FLOW DIAGRAM")
lines.append("" * 65)
lines.append("")
# Simple flow visualization
for i, page in enumerate(pages):
name = page.get('name', 'Unknown')
status = page.get('status', 'PENDING')
icon = get_status_icon(status)
# Page box
box_width = max(len(name) + 4, 20)
lines.append(f"{'' * box_width}")
lines.append(f"{icon} {name.center(box_width - 4)}")
lines.append(f"{'' * box_width}")
# Arrow to next page (if not last)
if i < len(pages) - 1:
lines.append(f" {''.center(box_width + 4)}")
lines.append(f" {''.center(box_width + 4)}")
lines.append("")
return lines
def visualize_data_flow(manifest: dict) -> list:
"""Generate data flow visualization."""
lines = []
pages = manifest.get('entities', {}).get('pages', [])
components = manifest.get('entities', {}).get('components', [])
endpoints = manifest.get('entities', {}).get('api_endpoints', [])
if not any([pages, components, endpoints]):
return []
lines.append("")
lines.append("🔄 DATA FLOW ARCHITECTURE")
lines.append("" * 65)
lines.append("")
lines.append(" ┌─────────────────────────────────────────────────────────┐")
lines.append(" │ FRONTEND │")
lines.append(" │ ┌─────────┐ ┌─────────────┐ ┌───────────────┐ │")
lines.append(" │ │ Pages │───▶│ Components │───▶│ Hooks │ │")
page_count = len(pages)
comp_count = len(components)
lines.append(f" │ │ ({page_count:^3}) │ │ ({comp_count:^3}) │ │ (state) │ │")
lines.append(" │ └─────────┘ └─────────────┘ └───────┬───────┘ │")
lines.append(" │ │ │")
lines.append(" └────────────────────────────────────────────┼───────────┘")
lines.append("")
lines.append("")
lines.append(" ┌─────────────────────────────────────────────────────────┐")
lines.append(" │ BACKEND │")
lines.append(" │ ┌─────────────────────────────────────────────────┐ │")
lines.append(" │ │ API Endpoints │ │")
api_count = len(endpoints)
lines.append(f" │ │ ({api_count:^3}) │ │")
lines.append(" │ └──────────────────────────┬──────────────────────┘ │")
lines.append(" │ │ │")
lines.append(" │ ▼ │")
lines.append(" │ ┌─────────────────────────────────────────────────┐ │")
lines.append(" │ │ Database │ │")
lines.append(" │ └─────────────────────────────────────────────────┘ │")
lines.append(" └─────────────────────────────────────────────────────────┘")
return lines
def generate_full_visualization(manifest: dict) -> str:
"""Generate complete design visualization."""
lines = []
project_name = manifest.get('project', {}).get('name', 'Unknown Project')
entities = manifest.get('entities', {})
pages = entities.get('pages', [])
components = entities.get('components', [])
api_endpoints = entities.get('api_endpoints', [])
# Header
lines.append("")
lines.append("╔═══════════════════════════════════════════════════════════════╗")
lines.append("║ 📐 DESIGN VISUALIZATION ║")
lines.append(f"║ Project: {project_name:<51}")
lines.append("╚═══════════════════════════════════════════════════════════════╝")
lines.append("")
# Summary counts
lines.append("📊 ENTITY SUMMARY")
lines.append("" * 65)
lines.append(f" Pages: {len(pages):>3} │ Components: {len(components):>3} │ API Endpoints: {len(api_endpoints):>3}")
lines.append("")
# Page Flow
if pages:
lines.extend(visualize_page_flow(pages))
lines.append("")
# Detailed Pages with Components
if pages:
lines.append("")
lines.append("📄 PAGE DETAILS")
lines.append("" * 65)
for page in pages:
lines.extend(visualize_page(page, components))
lines.append("")
# Component Tree
if components:
lines.append("")
lines.append("🧩 COMPONENT HIERARCHY")
lines.append("" * 65)
lines.extend(visualize_component_tree(components))
lines.append("")
# API Endpoints
if api_endpoints:
lines.append("")
lines.append("🔌 API LAYER")
lines.append("" * 65)
lines.extend(visualize_api_endpoints(api_endpoints))
lines.append("")
# Data Flow Architecture
lines.extend(visualize_data_flow(manifest))
lines.append("")
# Legend
lines.append("")
lines.append("📋 LEGEND")
lines.append("" * 65)
lines.append(" ⏳ PENDING - Designed, awaiting approval")
lines.append(" ✅ APPROVED - Approved, ready for implementation")
lines.append(" 🔄 IN_PROGRESS - Currently being implemented")
lines.append(" 🟢 IMPLEMENTED - Implementation complete")
lines.append("")
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(description="Visualize design from manifest")
parser.add_argument("--manifest", required=True, help="Path to project_manifest.json")
parser.add_argument("--format", choices=['full', 'pages', 'components', 'api', 'flow'],
default='full', help="Visualization format")
args = parser.parse_args()
manifest = load_manifest(args.manifest)
if manifest is None:
print("❌ Error: Could not load manifest from", args.manifest)
return 1
entities = manifest.get('entities', {})
if not any([
entities.get('pages'),
entities.get('components'),
entities.get('api_endpoints')
]):
print("⚠️ No entities found in manifest. Design phase may not be complete.")
return 0
if args.format == 'full':
print(generate_full_visualization(manifest))
elif args.format == 'pages':
pages = entities.get('pages', [])
components = entities.get('components', [])
for page in pages:
print("\n".join(visualize_page(page, components)))
elif args.format == 'components':
components = entities.get('components', [])
print("\n".join(visualize_component_tree(components)))
elif args.format == 'api':
endpoints = entities.get('api_endpoints', [])
print("\n".join(visualize_api_endpoints(endpoints)))
elif args.format == 'flow':
pages = entities.get('pages', [])
print("\n".join(visualize_page_flow(pages)))
return 0
if __name__ == "__main__":
sys.exit(main())