diff --git a/samples/agent/adk/rizzcharts/agent.py b/samples/agent/adk/rizzcharts/agent.py index e47d2400..4085e846 100644 --- a/samples/agent/adk/rizzcharts/agent.py +++ b/samples/agent/adk/rizzcharts/agent.py @@ -72,6 +72,18 @@ def get_instructions(cls, readonly_context: ReadonlyContext) -> str: else: raise ValueError(f"Unsupported catalog uri: {catalog_uri if catalog_uri else 'None'}") + # Check for Google API Key + googlemaps_api_key = os.getenv("GOOGLEMAPS_API_KEY") + if googlemaps_api_key: + map_image_instruction = f""" +**Map Image URL:** When constructing map visualizations, you may use Google Maps Static API with the following API key: `{googlemaps_api_key}`. +Example URL format: `https://maps.googleapis.com/maps/api/staticmap?center=LAT,LNG&zoom=ZOOM&size=600x400&markers=...&key={googlemaps_api_key}` +""" + else: + map_image_instruction = """ +**Map Image URL:** When constructing map visualizations, use a placeholder image URL exactly as shown in the example template. Do NOT attempt to use Google Maps Static API or any other external map service. Use exactly: `https://placehold.co/600x400?text=Map+Placeholder` +""" + final_prompt = f""" ### System Instructions @@ -79,6 +91,8 @@ def get_instructions(cls, readonly_context: ReadonlyContext) -> str: **Core Objective:** To provide a dynamic and interactive dashboard by constructing UI surfaces with the appropriate visualization components based on user queries. +{map_image_instruction} + **Key Components & Examples:** You will be provided a schema that defines the A2UI message structure and two key generic component templates for displaying data. @@ -113,6 +127,7 @@ def get_instructions(cls, readonly_context: ReadonlyContext) -> str: * Use the **entire** JSON array from the chosen example as the base value for the `a2ui_json` argument. * **Generate a new `surfaceId`:** You MUST generate a new, unique `surfaceId` for this request (e.g., `sales_breakdown_q3_surface`, `regional_outliers_northeast_surface`). This new ID must be used for the `surfaceId` in all three messages within the JSON array (`beginRendering`, `surfaceUpdate`, `dataModelUpdate`). * **Update the title Text:** You MUST update the `literalString` value for the `Text` component (the component with `id: "page_header"`) to accurately reflect the specific user query. For example, if the user asks for "Q3" sales, update the generic template text to "Q3 2025 Sales by Product Category". + * **For Map Image URLs:** Follow the Map Image URL instructions above. * Ensure the generated JSON perfectly matches the A2UI specification. It will be validated against the json_schema and rejected if it does not conform. * If you get an error in the tool response apologize to the user and let them know they should try again. diff --git a/samples/agent/adk/rizzcharts/examples/standard_catalog/map.json b/samples/agent/adk/rizzcharts/examples/standard_catalog/map.json index f6061c5a..3a9e98ce 100644 --- a/samples/agent/adk/rizzcharts/examples/standard_catalog/map.json +++ b/samples/agent/adk/rizzcharts/examples/standard_catalog/map.json @@ -38,6 +38,16 @@ "usageHint": "h2" } } + }, + { + "id": "map-image", + "component": { + "Image": { + "url": { + "literalString": "https://placehold.co/600x400?text=Map+Placeholder" + } + } + } }, { "id": "location-list", diff --git a/samples/client/lit/package.json b/samples/client/lit/package.json index f9051e5c..91e64e0f 100644 --- a/samples/client/lit/package.json +++ b/samples/client/lit/package.json @@ -11,11 +11,13 @@ "scripts": { "serve:agent:restaurant": "cd ../../agent/adk/restaurant_finder && uv run .", "serve:agent:contact_lookup": "cd ../../agent/adk/contact_lookup && uv run .", + "serve:agent:rizzcharts": "cd ../../agent/adk/rizzcharts && uv run .", "serve:agent:contact_multi_surface": "cd ../../agent/adk/contact_multiple_surfaces && uv run .", "serve:shell": "cd shell && npm run dev", "build:renderer": "cd ../../../renderers/lit && npm install && npm run build", "demo:all": "npm install && npm run build:renderer && concurrently -k -n \"SHELL,REST,CONT1\" -c \"magenta,blue,green\" \"npm run serve:shell\" \"npm run serve:agent:restaurant\" \"npm run serve:agent:contact_lookup\"", - "demo:restaurant": "npm install && npm run build:renderer && concurrently -k -n \"SHELL,REST\" -c \"magenta,blue\" \"npm run serve:shell\" \"npm run serve:agent:restaurant\"" + "demo:restaurant": "npm install && npm run build:renderer && concurrently -k -n \"SHELL,REST\" -c \"magenta,blue\" \"npm run serve:shell\" \"npm run serve:agent:restaurant\"", + "demo:rizzcharts": "npm install && npm run build:renderer && concurrently -k -n \"SHELL,REST\" -c \"magenta,blue\" \"npm run serve:shell\" \"npm run serve:agent:rizzcharts\"" }, "devDependencies": { "concurrently": "9.2.1" diff --git a/samples/client/lit/shell/app.ts b/samples/client/lit/shell/app.ts index 33a16cf5..a8eb903b 100644 --- a/samples/client/lit/shell/app.ts +++ b/samples/client/lit/shell/app.ts @@ -45,11 +45,13 @@ import "./ui/ui.js"; import { AppConfig } from "./configs/types.js"; import { config as restaurantConfig } from "./configs/restaurant.js"; import { config as contactsConfig } from "./configs/contacts.js"; +import { config as rizzchartsConfig } from "./configs/rizzcharts.js"; import { styleMap } from "lit/directives/style-map.js"; const configs: Record = { restaurant: restaurantConfig, contacts: contactsConfig, + rizzcharts: rizzchartsConfig, }; @customElement("a2ui-shell") diff --git a/samples/client/lit/shell/client.ts b/samples/client/lit/shell/client.ts index f0e94b8a..568bf484 100644 --- a/samples/client/lit/shell/client.ts +++ b/samples/client/lit/shell/client.ts @@ -18,7 +18,7 @@ import { Part, SendMessageSuccessResponse, Task } from "@a2a-js/sdk"; import { A2AClient } from "@a2a-js/sdk/client"; import { v0_8 } from "@a2ui/lit"; -const A2AUI_MIME_TYPE = "application/json+a2aui"; +const A2AUI_MIME_TYPE = "application/json+a2ui"; export class A2UIClient { #serverUrl: string; @@ -97,13 +97,44 @@ export class A2UIClient { } const result = (response as SendMessageSuccessResponse).result as Task; - if (result.kind === "task" && result.status.message?.parts) { + console.log("Full Server Response Result:", JSON.stringify(result, null, 2)); + + let responseParts = result.status.message?.parts; + + // Fallback: If no parts in status.message, check the last agent message in history + if (!responseParts && result.history && result.history.length > 0) { + // Iterate backwards to find the last agent message + for (let i = result.history.length - 1; i >= 0; i--) { + const msg = result.history[i]; + if (msg.role === 'agent' && msg.parts && msg.parts.length > 0) { + responseParts = msg.parts; + console.log("Found parts in history at index", i); + break; + } + } + } + + if (result.kind === "task" && responseParts) { const messages: v0_8.Types.ServerToClientMessage[] = []; - for (const part of result.status.message.parts) { + for (const part of responseParts) { + console.log("Client Received part:", JSON.stringify(part, null, 2)); + if (part.kind === 'data') { - messages.push(part.data as v0_8.Types.ServerToClientMessage); + let data = part.data; + if (typeof data === 'string') { + try { + data = JSON.parse(data); + console.log("Parsed string data:", data); + } catch (e) { + console.error("Failed to parse part.data string:", e); + } + } + messages.push(data as v0_8.Types.ServerToClientMessage); + } else if (part.kind === 'text') { + console.log("Ignored text part:", part.text); } } + console.log("Final messages to process:", messages); return messages; }