Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions cockpit/chat/generative-ui/python/prompts/dashboard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Airline Operations Dashboard Agent

You are a dashboard agent that builds interactive airline-operations KPI dashboards using a JSON render spec format. You have access to tools that query an airline's flight, fleet, and on-time performance data.

## Your Behavior

### First message (no existing dashboard)

1. Generate a complete dashboard layout as a JSON render spec (see format below)
2. Call ALL four data tools to populate the dashboard
3. After the tools return, provide a brief conversational summary

### Follow-up messages (dashboard already exists)

Categorize the user's request:

- **Data change** (e.g., "show last 6 months", "filter to cancelled flights only"): Call only the relevant tool(s) with updated parameters. Do NOT regenerate the spec. Just respond conversationally confirming the update.
- **Structural change** (e.g., "add a new chart", "remove the table"): Regenerate the full spec with the modification, then call tools to populate any new components.
- **Question about data** (e.g., "why did on-time % dip in December?"): Respond conversationally in plain text. Do NOT output JSON or call tools.

## JSON Render Spec Format

Your spec response MUST be raw JSON only — no markdown, no code fences, no surrounding text.

```
{
"elements": { [key: string]: Element },
"root": string
}
```

An Element has:
```
{
"type": string,
"props": { ... },
"children?": string[]
}
```

### Props with State Bindings

Use `{ "$state": "/json/pointer/path" }` for props that will be populated by tool results. The dashboard renders skeleton placeholders until the data arrives.

Example: `"value": { "$state": "/on_time/value" }` — this prop will be populated when the `/on_time/value` state path receives data.

## Available Component Types

| Type | Props | Children | Description |
|------|-------|----------|-------------|
| `dashboard_grid` | *(none)* | Yes | Top-level vertical layout with section spacing |
| `container` | `direction` ("row" or "column") | Yes | Flex layout container |
| `stat_card` | `label` (string), `value` ($state), `delta` ($state) | No | Metric summary card |
| `line_chart` | `title` (string), `data` ($state array), `xKey` (string), `yKey` (string) | No | SVG line chart |
| `bar_chart` | `title` (string), `data` ($state array), `labelKey` (string), `valueKey` (string) | No | SVG bar chart |
| `data_grid` | `title` (string), `rows` ($state array), `columns` (string[]) | No | Data table |

## State Path Conventions

Use these state paths to match what the tools populate:

- `/on_time/value`, `/on_time/delta` — from query_airline_kpis
- `/flights_today/value`, `/flights_today/delta` — from query_airline_kpis
- `/avg_delay/value`, `/avg_delay/delta` — from query_airline_kpis
- `/load_factor/value`, `/load_factor/delta` — from query_airline_kpis
- `/on_time_trend` — array from query_on_time_trend
- `/flights_by_airline` — array from query_flights_by_airline
- `/recent_disruptions` — array from query_recent_disruptions

## Example Spec

For "show me the dashboard":

{"elements":{"root":{"type":"dashboard_grid","children":["stats_row","charts_row","table_section"]},"stats_row":{"type":"container","props":{"direction":"row"},"children":["on_time_card","flights_card","delay_card","load_card"]},"on_time_card":{"type":"stat_card","props":{"label":"On-time %","value":{"$state":"/on_time/value"},"delta":{"$state":"/on_time/delta"}}},"flights_card":{"type":"stat_card","props":{"label":"Flights Today","value":{"$state":"/flights_today/value"},"delta":{"$state":"/flights_today/delta"}}},"delay_card":{"type":"stat_card","props":{"label":"Avg Delay","value":{"$state":"/avg_delay/value"},"delta":{"$state":"/avg_delay/delta"}}},"load_card":{"type":"stat_card","props":{"label":"Load Factor","value":{"$state":"/load_factor/value"},"delta":{"$state":"/load_factor/delta"}}},"charts_row":{"type":"container","props":{"direction":"row"},"children":["trend_chart","airline_chart"]},"trend_chart":{"type":"line_chart","props":{"title":"On-time % Trend","data":{"$state":"/on_time_trend"},"xKey":"month","yKey":"on_time_pct"}},"airline_chart":{"type":"bar_chart","props":{"title":"Flights by Airline","data":{"$state":"/flights_by_airline"},"labelKey":"airline","valueKey":"count"}},"table_section":{"type":"data_grid","props":{"title":"Recent Disruptions","rows":{"$state":"/recent_disruptions"},"columns":["flight_number","type","minutes","route","date"]}}},"root":"root"}
74 changes: 0 additions & 74 deletions cockpit/chat/generative-ui/python/prompts/generative-ui.md

This file was deleted.

146 changes: 83 additions & 63 deletions cockpit/chat/generative-ui/python/src/dashboard_tools.py
Original file line number Diff line number Diff line change
@@ -1,97 +1,117 @@
"""Mock SaaS metrics data tools for the generative-ui dashboard example."""
"""Aviation KPI tools for the c-generative-ui standalone demo.

Standalone copy — analytics constants are inlined here because this
backend has no shared aviation_data.py module. Mirrors the umbrella
backend at cockpit/langgraph/streaming/python/src/dashboard_tools.py.
"""

from langchain_core.tools import tool

# ── Hardcoded SaaS dataset ──────────────────────────────────────────────────

_MRR_TREND = [
{"month": "2025-05", "mrr": 28000},
{"month": "2025-06", "mrr": 29500},
{"month": "2025-07", "mrr": 30200},
{"month": "2025-08", "mrr": 31800},
{"month": "2025-09", "mrr": 32500},
{"month": "2025-10", "mrr": 33000},
{"month": "2025-11", "mrr": 34200},
{"month": "2025-12", "mrr": 35800},
{"month": "2026-01", "mrr": 37000},
{"month": "2026-02", "mrr": 38500},
{"month": "2026-03", "mrr": 40200},
{"month": "2026-04", "mrr": 42000},
# ── Analytics fixtures (inlined; no aviation_data.py in standalone) ─────────

KPI_SNAPSHOT = {
"on_time_pct": 84.2,
"on_time_delta": "+1.4%",
"flights_today": 312,
"flights_today_delta": "+8",
"avg_delay_min": 12,
"avg_delay_delta": "-2 min",
"load_factor_pct": 78.5,
"load_factor_delta": "+0.6%",
}

ON_TIME_TREND = [
{"month": "2025-05", "on_time_pct": 82.4},
{"month": "2025-06", "on_time_pct": 81.1},
{"month": "2025-07", "on_time_pct": 79.8},
{"month": "2025-08", "on_time_pct": 80.5},
{"month": "2025-09", "on_time_pct": 83.2},
{"month": "2025-10", "on_time_pct": 84.0},
{"month": "2025-11", "on_time_pct": 82.6},
{"month": "2025-12", "on_time_pct": 78.9},
{"month": "2026-01", "on_time_pct": 80.2},
{"month": "2026-02", "on_time_pct": 81.7},
{"month": "2026-03", "on_time_pct": 82.8},
{"month": "2026-04", "on_time_pct": 84.2},
]

_SUBSCRIBERS_BY_PLAN = [
{"plan": "free", "count": 1200},
{"plan": "starter", "count": 850},
{"plan": "pro", "count": 420},
{"plan": "enterprise", "count": 95},
FLIGHTS_BY_AIRLINE = [
{"airline": "American", "count": 87},
{"airline": "United", "count": 92},
{"airline": "Delta", "count": 78},
{"airline": "JetBlue", "count": 55},
]

_CHURNED_ACCOUNTS = [
{"name": "Acme Corp", "plan": "pro", "mrr_lost": 450, "date": "2026-04-01"},
{"name": "Widgetly", "plan": "starter", "mrr_lost": 120, "date": "2026-03-28"},
{"name": "DataPipe Inc", "plan": "enterprise", "mrr_lost": 2400, "date": "2026-03-25"},
{"name": "NovaTech", "plan": "pro", "mrr_lost": 450, "date": "2026-03-20"},
{"name": "CloudSync", "plan": "starter", "mrr_lost": 120, "date": "2026-03-15"},
{"name": "ByteForge", "plan": "pro", "mrr_lost": 450, "date": "2026-03-10"},
{"name": "Quantum Labs", "plan": "enterprise", "mrr_lost": 2400, "date": "2026-03-05"},
{"name": "FlowState", "plan": "starter", "mrr_lost": 120, "date": "2026-02-28"},
{"name": "CipherNet", "plan": "pro", "mrr_lost": 450, "date": "2026-02-20"},
{"name": "Luminary AI", "plan": "starter", "mrr_lost": 120, "date": "2026-02-15"},
RECENT_DISRUPTIONS = [
{"flight_number": "UA123", "type": "delayed", "minutes": 45, "route": "LAX→JFK", "date": "2026-05-14"},
{"flight_number": "AA456", "type": "cancelled", "minutes": 0, "route": "JFK→LAX", "date": "2026-05-14"},
{"flight_number": "DL789", "type": "delayed", "minutes": 22, "route": "ATL→ORD", "date": "2026-05-13"},
{"flight_number": "B6101", "type": "delayed", "minutes": 68, "route": "BOS→MIA", "date": "2026-05-13"},
{"flight_number": "UA204", "type": "cancelled", "minutes": 0, "route": "SFO→SEA", "date": "2026-05-12"},
{"flight_number": "AA318", "type": "delayed", "minutes": 15, "route": "DFW→DEN", "date": "2026-05-12"},
{"flight_number": "DL552", "type": "delayed", "minutes": 35, "route": "ATL→MIA", "date": "2026-05-11"},
{"flight_number": "B6217", "type": "delayed", "minutes": 80, "route": "JFK→BOS", "date": "2026-05-11"},
{"flight_number": "UA640", "type": "cancelled", "minutes": 0, "route": "ORD→DEN", "date": "2026-05-10"},
{"flight_number": "AA871", "type": "delayed", "minutes": 25, "route": "LAX→DFW", "date": "2026-05-10"},
]


# ── Tools ───────────────────────────────────────────────────────────────────

@tool
def query_mrr() -> dict:
"""Get current Monthly Recurring Revenue (MRR) with month-over-month delta."""
current = _MRR_TREND[-1]["mrr"]
previous = _MRR_TREND[-2]["mrr"]
delta_pct = ((current - previous) / previous) * 100
total_subs = sum(p["count"] for p in _SUBSCRIBERS_BY_PLAN)
arpu = round(current / total_subs, 2)
def query_airline_kpis() -> dict:
"""Snapshot of operational KPIs across the fleet: on-time %, flights today,
average delay (minutes), and load factor."""
snap = KPI_SNAPSHOT
return {
"mrr": {"value": current, "delta": f"+{delta_pct:.1f}%", "period": "month"},
"subscribers": {"total": total_subs, "delta": "+42"},
"churn": {"rate": "3.2%", "delta": "-0.4%"},
"arpu": {"value": f"${arpu:.2f}", "delta": "+$1.20"},
"on_time": {"value": f"{snap['on_time_pct']}%", "delta": snap["on_time_delta"]},
"flights_today": {"value": snap["flights_today"], "delta": snap["flights_today_delta"]},
"avg_delay": {"value": f"{snap['avg_delay_min']} min", "delta": snap["avg_delay_delta"]},
"load_factor": {"value": f"{snap['load_factor_pct']}%", "delta": snap["load_factor_delta"]},
}


@tool
def query_subscribers_by_plan(plans: list[str] | None = None) -> list[dict]:
"""Get subscriber counts broken down by plan tier.
def query_on_time_trend(months: int = 12) -> list[dict]:
"""On-time performance over time, as percentage by month.

Args:
plans: Optional list of plan names to filter by (e.g., ["pro", "enterprise"]).
Returns all plans if not specified.
months: Number of months to return (default 12). Valid: 3, 6, 12, 24.
"""
if plans:
return [p for p in _SUBSCRIBERS_BY_PLAN if p["plan"] in plans]
return _SUBSCRIBERS_BY_PLAN
months = min(months, len(ON_TIME_TREND))
return ON_TIME_TREND[-months:]


@tool
def query_mrr_trend(months: int = 12) -> list[dict]:
"""Get MRR trend over time.
def query_flights_by_airline(airlines: list[str] | None = None) -> list[dict]:
"""Daily flight counts per airline.

Args:
months: Number of months to return (default 12). Valid values: 3, 6, 12, 24.
airlines: Optional filter list, e.g. ["American", "United"]. All four
returned if omitted.
"""
months = min(months, len(_MRR_TREND))
return _MRR_TREND[-months:]
if airlines:
return [a for a in FLIGHTS_BY_AIRLINE if a["airline"] in airlines]
return FLIGHTS_BY_AIRLINE


@tool
def query_churned_accounts(limit: int = 5, plan: str | None = None) -> list[dict]:
"""Get recently churned accounts.
def query_recent_disruptions(limit: int = 5, type: str | None = None) -> list[dict]:
"""Recent flight delays or cancellations.

Args:
limit: Maximum number of accounts to return (default 5).
plan: Optional plan name to filter by (e.g., "enterprise").
limit: Maximum entries to return (default 5).
type: Optional filter, "delayed" or "cancelled".
"""
filtered = _CHURNED_ACCOUNTS
if plan:
filtered = [a for a in filtered if a["plan"] == plan]
filtered = RECENT_DISRUPTIONS
if type:
filtered = [d for d in filtered if d["type"] == type]
return filtered[:limit]


ALL_TOOLS = [query_mrr, query_subscribers_by_plan, query_mrr_trend, query_churned_accounts]
ALL_TOOLS = [
query_airline_kpis,
query_on_time_trend,
query_flights_by_airline,
query_recent_disruptions,
]
16 changes: 8 additions & 8 deletions cockpit/chat/generative-ui/python/src/graph.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Multi-node LangGraph graph for the SaaS metrics dashboard.
"""Multi-node LangGraph graph for the airline operations KPI dashboard.

Flow:
router → generate_shell (first turn) or plan_tools (follow-up)
Expand Down Expand Up @@ -78,17 +78,17 @@ async def emit_state(state: DashboardState) -> DashboardState:
except (json.JSONDecodeError, TypeError):
continue

if msg.name == "query_mrr":
if msg.name == "query_airline_kpis":
for section_key, section_val in data.items():
if isinstance(section_val, dict):
for k, v in section_val.items():
tool_results[f"/{section_key}/{k}"] = v
elif msg.name == "query_subscribers_by_plan":
tool_results["/subscribers_by_plan"] = data
elif msg.name == "query_mrr_trend":
tool_results["/mrr_trend"] = data
elif msg.name == "query_churned_accounts":
tool_results["/churned_accounts"] = data
elif msg.name == "query_on_time_trend":
tool_results["/on_time_trend"] = data
elif msg.name == "query_flights_by_airline":
tool_results["/flights_by_airline"] = data
elif msg.name == "query_recent_disruptions":
tool_results["/recent_disruptions"] = data
elif msg.type == "ai":
break

Expand Down
2 changes: 1 addition & 1 deletion cockpit/chat/generative-ui/python/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const chatGenerativeUiPythonModule: CockpitCapabilityModule = {
},
title: 'Chat Generative UI (Python)',
docsPath: '/docs/chat/core-capabilities/generative-ui/overview/python',
promptAssetPaths: ['cockpit/chat/generative-ui/python/prompts/generative-ui.md'],
promptAssetPaths: ['cockpit/chat/generative-ui/python/prompts/dashboard.md'],
codeAssetPaths: [
'cockpit/chat/generative-ui/angular/src/app/generative-ui.component.ts',
'cockpit/chat/generative-ui/angular/src/app/app.config.ts',
Expand Down
Loading
Loading