Streamlit Frontend
Overview
The frontend is a multi-page Streamlit application at src/telemetry/frontend/. It communicates with the FastAPI backend via HTTP and renders telemetry visualizations, strategy recommendations, driver comparisons, and a chat interface.
Entry point: frontend/app/main.py.
Page map
| Page | File | Description |
|---|---|---|
| Dashboard | pages/dashboard.py |
Telemetry charts for selected session/driver |
| Comparison | pages/comparison.py |
Side-by-side telemetry for two drivers |
| Race Analysis | pages/race_analysis.py |
Tire, gap, and radio analysis tabs |
| Strategy | pages/strategy.py |
N25–N31 strategy advisor with agent tabs |
| Chat | pages/chat.py |
LM Studio chat interface |
| Model Lab | pages/model_lab.py |
Interactive ML model exploration |
Directory structure
frontend/
app/
main.py -- Streamlit entry point, page routing
setup_path.py -- sys.path configuration
styles.py -- GLOBAL_CSS constant
track_data.py -- Track metadata
pages/ -- One file per page
components/
chatbot/ -- Chat UI components
common/ -- Shared: driver_colors, chart_styles, loading
comparison/ -- Driver comparison charts
dashboard/ -- Dashboard-specific CSS and selectors
layout/ -- Navbar
race_analysis/ -- Tire charts, gap charts, radio panel
strategy/ -- Strategy card, scenario chart, agent tabs
streamlit_audio_viz/ -- Custom React component for audio visualization
telemetry/ -- Individual telemetry graphs
voice/ -- Voice input UI
services/
chat_service.py -- Chat HTTP client
strategy_service.py -- Strategy endpoints HTTP client
telemetry_service.py
voice_api.py
utils/
audio_utils.py
chat_navigation.py
chat_state.py
data_loaders.py
race_processing.py
race_viz.py
report_storage.py
time_formatters.py
shared/
img/ -- Static image assets
config.py -- BACKEND_URL, API_BASE_URL from env vars
Navigation
The app launches directly into the Dashboard. Page routing uses st.session_state['current_page']. The navbar (components/layout/navbar.py) sets this value. Pages are rendered conditionally in main.py.
Strategy page
The Strategy page (pages/strategy.py) is the primary interface for the N25–N31 agent system:
- Selectors: Year (hardcoded 2025), GP, Driver, Lap range, Analysis lap, Risk tolerance
- Run button: calls
StrategyService.get_recommend()which hits/api/v1/strategy/recommend - Results:
render_strategy_card()— recommendation card with action, confidence, reasoningrender_scenario_chart()— bar chart comparing MC scenario scoresrender_agent_tabs()— tabbed detail view for each sub-agent output
Chat tool-result rendering
The Chat page (pages/chat.py) consumes the MCP tool results streamed by /api/v1/chat/tool-message-stream (and the JSON variant at /api/v1/chat/tool-message). Each tool result carries a display_type hint (see TOOL_DISPLAY_MAP in Backend API) that tells the frontend how to render the payload.
Dispatch lives in components/chatbot/tool_result_renderer.py:
_RENDERERS = {
"metrics": _render_metrics,
"strategy_card": _render_strategy_card,
"table": _render_table,
"text": _render_text,
"chart": _render_chart,
}
Inline Plotly charts (Phase 2 MCP telemetry tools)
The four telemetry tools (get_lap_times, get_telemetry, compare_drivers, get_race_data) are mapped to DisplayType.CHART and render as inline Plotly figures inside the chat bubble. _render_chart(data, tool_name) calls build_figure(tool_name, data) from components/chatbot/chart_builders.py, which dispatches to one of:
| Tool | Builder | Figure |
|---|---|---|
get_lap_times |
build_lap_times_figure |
Lap-time line per driver |
get_telemetry |
build_telemetry_figure |
Speed / throttle / brake traces for one lap |
compare_drivers |
build_compare_drivers_figure |
Fastest-lap side-by-side comparison |
get_race_data |
build_race_data_figure |
Full race overview (positions / gaps) |
Builders are Streamlit-free: they take the raw tool payload, pull per-driver colors via get_driver_color from components/common/driver_colors.py (see Driver colors), and apply the shared dark theme through _apply_base_layout(). The renderer wraps the figure with apply_telemetry_chart_styles() so the chart inherits the purple-outlined chat bubble look, and falls back to _render_text if the builder returns None.
Services layer
All HTTP calls go through service classes in services/. Each method returns a (success: bool, data: dict | None, error: str | None) triple so callers handle results consistently.
class StrategyService:
@staticmethod
def get_pace(lap_state) -> Tuple[bool, Optional[Dict], Optional[str]]: ...
@staticmethod
def get_tire(lap_state) -> Tuple[bool, Optional[Dict], Optional[str]]: ...
@staticmethod
def get_recommend(...) -> Tuple[bool, Optional[Dict], Optional[str]]: ...
Timeout is set to 300 seconds because first-call model loading (RoBERTa + SetFit + BERT-large + BGE-M3) is slow.
Configuration
| Variable | Default | Description |
|---|---|---|
BACKEND_URL |
http://localhost:8000 |
FastAPI backend base URL |
FRONTEND_URL |
http://localhost:8501 |
Frontend self-reference |
Appendix — CSS fixes
Scroll fix on Plotly charts
Streamlit wraps Plotly charts in div.stElementContainer elements that can develop unwanted horizontal scrollbars. Located in src/telemetry/frontend/components/common/chart_styles.py, apply_telemetry_chart_styles() injects CSS that hides the scrollbar on the parent wrapper without clipping chart content:
div.stElementContainer:has(div[data-testid="stPlotlyChart"]) {
scrollbar-width: none !important;
-ms-overflow-style: none !important;
}
div.stElementContainer:has(div[data-testid="stPlotlyChart"])::-webkit-scrollbar {
display: none !important;
}
The same function applies the visual treatment to all Plotly charts:
div[data-testid="stPlotlyChart"] {
outline: 2px solid #a78bfa !important;
outline-offset: -2px !important;
border-radius: 12px !important;
background-color: #181633 !important;
box-shadow: 0 4px 12px rgba(167, 139, 250, 0.2) !important;
}
outline is used instead of border because outlines do not affect the box model and cannot cause layout shifts or trigger scrollbars.