# Copyright (c) Microsoft. All rights reserved. """Weather widget rendering for ChatKit integration sample.""" import base64 from dataclasses import dataclass from chatkit.actions import ActionConfig from chatkit.widgets import Box, Button, Card, Col, Image, Row, Text, Title, WidgetRoot WEATHER_ICON_COLOR = "#1D4ED8" WEATHER_ICON_ACCENT = "#DBEAFE" # Popular cities for the selector POPULAR_CITIES = [ {"value": "seattle", "label": "Seattle, WA", "description": "Pacific Northwest"}, {"value": "new_york", "label": "New York, NY", "description": "East Coast"}, {"value": "san_francisco", "label": "San Francisco, CA", "description": "Bay Area"}, {"value": "chicago", "label": "Chicago, IL", "description": "Midwest"}, {"value": "miami", "label": "Miami, FL", "description": "Southeast"}, {"value": "austin", "label": "Austin, TX", "description": "Southwest"}, {"value": "boston", "label": "Boston, MA", "description": "New England"}, {"value": "denver", "label": "Denver, CO", "description": "Mountain West"}, {"value": "portland", "label": "Portland, OR", "description": "Pacific Northwest"}, {"value": "atlanta", "label": "Atlanta, GA", "description": "Southeast"}, ] # Mapping from city values to display names for weather queries CITY_VALUE_TO_NAME = {city["value"]: city["label"] for city in POPULAR_CITIES} def _sun_svg() -> str: """Generate SVG for sunny weather icon.""" color = WEATHER_ICON_COLOR accent = WEATHER_ICON_ACCENT return ( '' f'' f'' '' '' '' '' '' '' '' '' "" "" ) def _cloud_svg() -> str: """Generate SVG for cloudy weather icon.""" color = WEATHER_ICON_COLOR accent = WEATHER_ICON_ACCENT return ( '' f'' "" ) def _rain_svg() -> str: """Generate SVG for rainy weather icon.""" color = WEATHER_ICON_COLOR accent = WEATHER_ICON_ACCENT return ( '' f'' f'' '' '' '' "" "" ) def _storm_svg() -> str: """Generate SVG for stormy weather icon.""" color = WEATHER_ICON_COLOR accent = WEATHER_ICON_ACCENT return ( '' f'' f'' "" ) def _snow_svg() -> str: """Generate SVG for snowy weather icon.""" color = WEATHER_ICON_COLOR accent = WEATHER_ICON_ACCENT return ( '' f'' f'' '' '' '' '' '' '' "" "" ) def _fog_svg() -> str: """Generate SVG for foggy weather icon.""" color = WEATHER_ICON_COLOR accent = WEATHER_ICON_ACCENT return ( '' f'' f'' '' '' "" "" ) def _encode_svg(svg: str) -> str: """Encode SVG as base64 data URI.""" encoded = base64.b64encode(svg.encode("utf-8")).decode("ascii") return f"data:image/svg+xml;base64,{encoded}" # Weather condition to icon mapping WEATHER_ICONS = { "sunny": _encode_svg(_sun_svg()), "cloudy": _encode_svg(_cloud_svg()), "rainy": _encode_svg(_rain_svg()), "stormy": _encode_svg(_storm_svg()), "snowy": _encode_svg(_snow_svg()), "foggy": _encode_svg(_fog_svg()), } DEFAULT_WEATHER_ICON = _encode_svg(_cloud_svg()) @dataclass class WeatherData: """Weather data container.""" location: str condition: str temperature: int humidity: int wind_speed: int def render_weather_widget(data: WeatherData) -> WidgetRoot: """Render a weather widget from weather data. Args: data: WeatherData containing weather information Returns: A ChatKit WidgetRoot (Card) displaying the weather information """ # Get weather icon weather_icon_src = WEATHER_ICONS.get(data.condition.lower(), DEFAULT_WEATHER_ICON) # Build the widget header = Box( padding=5, background="surface-tertiary", children=[ Row( justify="between", align="center", children=[ Col( align="start", gap=1, children=[ Text( value=data.location, size="lg", weight="semibold", ), Text( value="Current conditions", color="tertiary", size="xs", ), ], ), Box( padding=3, radius="full", background="blue-100", children=[ Image( src=weather_icon_src, alt=data.condition, size=28, fit="contain", ) ], ), ], ), Row( align="start", gap=4, children=[ Title( value=f"{data.temperature}°C", size="lg", weight="semibold", ), Col( align="start", gap=1, children=[ Text( value=data.condition.title(), color="secondary", size="sm", weight="medium", ), ], ), ], ), ], ) # Details section details = Box( padding=5, gap=4, children=[ Text(value="Weather details", weight="semibold", size="sm"), Row( gap=3, wrap="wrap", children=[ _detail_chip("Humidity", f"{data.humidity}%"), _detail_chip("Wind", f"{data.wind_speed} km/h"), ], ), ], ) return Card( key="weather", padding=0, children=[header, details], ) def _detail_chip(label: str, value: str) -> Box: """Create a detail chip widget component.""" return Box( padding=3, radius="xl", background="surface-tertiary", width=150, minWidth=150, maxWidth=150, minHeight=80, maxHeight=80, flex="0 0 auto", children=[ Col( align="stretch", gap=2, children=[ Text(value=label, size="xs", weight="medium", color="tertiary"), Row( justify="center", margin={"top": 2}, children=[Text(value=value, weight="semibold", size="lg")], ), ], ) ], ) def weather_widget_copy_text(data: WeatherData) -> str: """Generate plain text representation of weather data. Args: data: WeatherData containing weather information Returns: Plain text description for copy/paste functionality """ return ( f"Weather in {data.location}:\n" f"• Condition: {data.condition.title()}\n" f"• Temperature: {data.temperature}°C\n" f"• Humidity: {data.humidity}%\n" f"• Wind: {data.wind_speed} km/h" ) def render_city_selector_widget() -> WidgetRoot: """Render an interactive city selector widget. This widget displays popular cities as a visual selection interface. Users can click or ask about any city to get weather information. Returns: A ChatKit WidgetRoot (Card) with city selection display """ # Create location icon SVG location_icon = _encode_svg( '' f'' f'' "" ) # Header section header = Box( padding=5, background="surface-tertiary", children=[ Row( gap=3, align="center", children=[ Box( padding=3, radius="full", background="blue-100", children=[ Image( src=location_icon, alt="Location", size=28, fit="contain", ) ], ), Col( align="start", gap=1, children=[ Title( value="Popular Cities", size="md", weight="semibold", ), Text( value="Select a city or ask about any location", color="tertiary", size="xs", ), ], ), ], ), ], ) # Create city chips in a grid layout city_chips: list[Button] = [] for city in POPULAR_CITIES: # Create a button that sends an action to query weather for the selected city chip = Button( label=city["label"], variant="outline", size="md", onClickAction=ActionConfig( type="city_selected", payload={"city_value": city["value"], "city_label": city["label"]}, handler="server", # Handle on server-side ), ) city_chips.append(chip) # Arrange in rows of 3 city_rows: list[Row] = [] for i in range(0, len(city_chips), 3): row_chips: list[Button] = city_chips[i : i + 3] city_rows.append( Row( gap=3, wrap="wrap", justify="start", children=list(row_chips), # Convert to generic list ) ) # Cities display section cities_section = Box( padding=5, gap=3, children=[ *city_rows, Box( padding=3, radius="md", background="blue-50", children=[ Text( value="💡 Click any city to get its weather, or ask about any other location!", size="xs", color="secondary", ), ], ), ], ) return Card( key="city_selector", padding=0, children=[header, cities_section], ) def city_selector_copy_text() -> str: """Generate plain text representation of city selector. Returns: Plain text description for copy/paste functionality """ cities_list = "\n".join([f"• {city['label']}" for city in POPULAR_CITIES]) return f"Popular cities (click to get weather):\n{cities_list}\n\nYou can also ask about weather in any other location!"