|
1 | | -"""Flow View — wraps existing Cytoscape flow visualization.""" |
| 1 | +"""Flow View — serves existing Cytoscape flow visualization via iframe.""" |
2 | 2 | from __future__ import annotations |
3 | 3 |
|
4 | | -from nicegui import ui |
| 4 | +from typing import Any |
5 | 5 |
|
| 6 | +from nicegui import app, ui |
6 | 7 |
|
7 | | -def create_flow_page(service) -> None: |
| 8 | + |
| 9 | +def create_flow_page(service: Any) -> None: |
8 | 10 | """Build the Flow tab inside a NiceGUI page. |
9 | 11 |
|
10 | | - Attempts to generate an overview flow diagram as HTML via the service. |
11 | | - Falls back to a placeholder when no analysis data is available. |
| 12 | + The flow engine generates a full self-contained HTML page (DOCTYPE + Cytoscape.js |
| 13 | + + vendor JS). This cannot be embedded as a fragment via ui.html(). Instead, we |
| 14 | + serve it at a dedicated route and embed it in an iframe. |
12 | 15 | """ |
13 | | - with ui.element("div").classes("max-w-7xl mx-auto px-4 w-full"): |
14 | | - try: |
15 | | - result = service.generate_flow("overview", "html") |
16 | | - |
17 | | - # generate_flow returns a dict; the HTML is in the "content" key |
18 | | - html_content: str | None = None |
19 | | - if isinstance(result, dict): |
20 | | - html_content = result.get("content") or result.get("html") |
21 | | - elif isinstance(result, str): |
22 | | - html_content = result |
23 | | - |
24 | | - if html_content: |
25 | | - ui.html(html_content).classes("w-full") |
26 | | - else: |
27 | | - _show_placeholder() |
28 | | - |
29 | | - except Exception: # noqa: BLE001 |
30 | | - _show_placeholder() |
| 16 | + _flow_html: str | None = None |
| 17 | + |
| 18 | + try: |
| 19 | + result = service.generate_flow("overview", "html") |
| 20 | + if isinstance(result, str) and result.strip().startswith("<!"): |
| 21 | + _flow_html = result |
| 22 | + elif isinstance(result, dict): |
| 23 | + content = result.get("content") or result.get("html", "") |
| 24 | + if content and content.strip().startswith("<!"): |
| 25 | + _flow_html = content |
| 26 | + except Exception: # noqa: BLE001 |
| 27 | + _flow_html = None |
| 28 | + |
| 29 | + if _flow_html: |
| 30 | + # Register a dedicated route to serve the full-page HTML |
| 31 | + @app.get("/flow-embed", include_in_schema=False) |
| 32 | + async def _serve_flow_html(): |
| 33 | + from starlette.responses import HTMLResponse |
| 34 | + return HTMLResponse(_flow_html) |
| 35 | + |
| 36 | + with ui.column().classes("w-full h-full"): |
| 37 | + ui.html( |
| 38 | + '<iframe src="/flow-embed" ' |
| 39 | + 'style="width:100%;height:calc(100vh - 160px);border:none;" ' |
| 40 | + 'loading="lazy"></iframe>' |
| 41 | + ) |
| 42 | + else: |
| 43 | + _show_placeholder() |
31 | 44 |
|
32 | 45 |
|
33 | 46 | def _show_placeholder() -> None: |
34 | 47 | """Show a professional centered placeholder when no flow data is available.""" |
35 | | - with ui.card().classes("w-full max-w-md mx-auto mt-16"): |
36 | | - with ui.card_section().classes("items-center text-center"): |
37 | | - with ui.column().classes("items-center gap-3 py-8"): |
38 | | - ui.icon("account_tree", size="64px").classes("opacity-30") |
39 | | - ui.label("No flow data available").classes( |
40 | | - "text-xl font-medium opacity-70" |
41 | | - ) |
42 | | - ui.label( |
43 | | - "Run 'osscodeiq analyze <path>' to scan a codebase, " |
44 | | - "then refresh this page." |
45 | | - ).classes("text-sm opacity-50 max-w-xs text-center") |
| 48 | + with ui.column().classes("w-full items-center justify-center py-20"): |
| 49 | + with ui.card().classes("max-w-md text-center"): |
| 50 | + with ui.card_section(): |
| 51 | + with ui.column().classes("items-center gap-4 py-8"): |
| 52 | + ui.icon("account_tree", size="64px").classes("opacity-30") |
| 53 | + ui.label("No flow data available").classes( |
| 54 | + "text-xl font-medium opacity-70" |
| 55 | + ) |
| 56 | + ui.label( |
| 57 | + "Run 'osscodeiq analyze <path>' to generate flow diagrams, " |
| 58 | + "then refresh this page." |
| 59 | + ).classes("text-sm opacity-50") |
0 commit comments