diff --git a/.gemini/GEMINI.md b/.gemini/GEMINI.md index ebef2caba..7e191be38 100644 --- a/.gemini/GEMINI.md +++ b/.gemini/GEMINI.md @@ -21,7 +21,7 @@ The A2UI repository is organized into several key directories: - `eval/`: Genkit-based evaluation framework. - `samples/`: Contains sample implementations. - `agent/`: - - `adk/`: Python-based ADK agent samples (e.g., `contact_lookup`, `restaurant_finder`, `rizzcharts`, `orchestrator`). + - `adk/`: Python-based ADK agent samples (e.g., `contact_lookup`, `restaurant_finder`, `orchestrator`). - `mcp/`: MCP server sample (A2UI over MCP). - `client/`: Web client implementations. - `lit/`: Clients using Lit and Vite (e.g., `contact`, `shell`). @@ -127,7 +127,7 @@ Then run the Angular client: ```bash cd samples/client/angular npm install -npm start -- contact # Replace 'contact' with the desired project name (e.g., restaurant, gallery, rizzcharts) +npm start -- contact # Replace 'contact' with the desired project name (e.g., restaurant, orchestrator) ``` ### Running Tools diff --git a/.github/workflows/ng_build_and_test.yml b/.github/workflows/ng_build_and_test.yml index e0fb336e0..2adbb43a2 100644 --- a/.github/workflows/ng_build_and_test.yml +++ b/.github/workflows/ng_build_and_test.yml @@ -51,10 +51,6 @@ jobs: working-directory: ./samples/client/angular run: npm run build restaurant - - name: Build Rizzchart sample - working-directory: ./samples/client/angular - run: npm run build rizzcharts - - name: Build Orchestrator working-directory: ./samples/client/angular run: npm run build orchestrator diff --git a/.github/workflows/python_samples_build.yml b/.github/workflows/python_samples_build.yml index 6435adc28..91e7b30ee 100644 --- a/.github/workflows/python_samples_build.yml +++ b/.github/workflows/python_samples_build.yml @@ -62,7 +62,3 @@ jobs: - name: Build restaurant_finder working-directory: samples/agent/adk/restaurant_finder run: uv build . - - - name: Build rizzcharts - working-directory: samples/agent/adk/rizzcharts - run: uv build . diff --git a/agent_sdks/agent_development.md b/agent_sdks/agent_development.md index 8bb3e688d..947122ea4 100644 --- a/agent_sdks/agent_development.md +++ b/agent_sdks/agent_development.md @@ -105,7 +105,7 @@ selected_catalog = schema_manager.get_selected_catalog() try: # Parse the LLM's JSON part parsed_json = json.loads(json_string) - + # Validate and fix against the schema selected_catalog.payload_fixer.validate_and_fix(parsed_json) except Exception as e: @@ -146,8 +146,6 @@ agent = LlmAgent(instruction=instruction, ...) Some agents may need to attach different catalogs or examples depending on the user's request, client capabilities, or conversational context. This is common for dashboard-style agents that support multiple distinct visualization types (e.g., Charts vs. Maps). -**Example Sample:** [rizzcharts](../../../samples/agent/adk/rizzcharts) - #### 2a. Injecting Catalogs into Session State In a dynamic scenario, you don't provide a static catalog to the agent. Instead, you resolve the selected catalog at runtime (e.g., during session preparation) and store it in the session state. @@ -155,10 +153,10 @@ In a dynamic scenario, you don't provide a static catalog to the agent. Instead, # In your AgentExecutor subclass async def _prepare_session(self, context, run_request, runner): session = await super()._prepare_session(context, run_request, runner) - + # 1. Determine client capabilities from metadata capabilities = context.message.metadata.get("a2ui_client_capabilities") - + # 2. Get selected catalog and load examples a2ui_catalog = self.schema_manager.get_selected_catalog( client_ui_capabilities=capabilities diff --git a/docs/agents.md b/docs/agents.md index 6cb5f320b..fea3a50d2 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -23,16 +23,13 @@ User interactions from the client can be treated as new user input. The A2UI repository includes sample agents you can learn from: -- [Restaurant Finder](https://github.com/google/A2UI/tree/main/samples/agent/adk/restaurant_finder) +- [Restaurant Finder](https://github.com/google/A2UI/tree/main/samples/agent/adk/restaurant_finder) - Table reservations with forms - Written with the ADK -- [Contact Lookup](https://github.com/google/A2UI/tree/main/samples/agent/adk/contact_lookup) +- [Contact Lookup](https://github.com/google/A2UI/tree/main/samples/agent/adk/contact_lookup) - Search with result lists - Written with the ADK -- [Rizzcharts](https://github.com/google/A2UI/tree/main/samples/agent/adk/rizzcharts) - - A2UI Custom components demo - - Written with the ADK -- [Orchestrator](https://github.com/google/A2UI/tree/main/samples/agent/adk/orchestrator) +- [Orchestrator](https://github.com/google/A2UI/tree/main/samples/agent/adk/orchestrator) - Passes A2UI messages from remote subagents - Written with the ADK @@ -40,7 +37,7 @@ The A2UI repository includes sample agents you can learn from: ### 1. User Facing Agent (standalone) -A user facing agent is one that is directly interacted with by the user. +A user facing agent is one that is directly interacted with by the user. ### 2. User Facing Agent as a host for a Remote Agent diff --git a/docs/catalogs.md b/docs/catalogs.md index c4b471111..c1e4e542a 100644 --- a/docs/catalogs.md +++ b/docs/catalogs.md @@ -61,7 +61,7 @@ Whether you are building a simple prototype or a complex production application, To help developers get started quickly, the A2UI team maintains the [Basic Catalog](../specification/v0_9/json/basic_catalog.json). -This is a pre-defined catalog file that contains a standard set of general-purpose components (Buttons, Inputs, Cards) and functions. It is not a special "type" of catalog; it is simply a version of a catalog that we have already written and have open source renderers for. +This is a pre-defined catalog file that contains a standard set of general-purpose components (Buttons, Inputs, Cards) and functions. It is not a special "type" of catalog; it is simply a version of a catalog that we have already written and have open source renderers for. The basic catalog allows you to bootstrap an application or validate A2UI concepts without needing to write your own schema from scratch. It is intentionally sparse to remain easily implementable by different renderers. @@ -77,8 +77,6 @@ By defining your own catalog, you restrict the agent to using exactly the compon For simplicity we recommend building catalogs that directly reflect a client's design system rather than trying to map the Basic Catalog to it through an adapter. Since A2UI is designed for GenUI, we expect the LLM can interpret different catalogs for different clients. -[See an example Rizzcharts catalog](../samples/agent/adk/rizzcharts/rizzcharts_catalog_definition.json) - ### Recommendations | Usecase | Recommendation | Effort | @@ -149,7 +147,7 @@ When the agent uses that catalog, it generates a payload strictly conforming to ### Freestanding Catalogs -A2UI Catalogs must be standalone (no references to external files) to simplify LLM inference and dependency management. +A2UI Catalogs must be standalone (no references to external files) to simplify LLM inference and dependency management. While the final catalog must be freestanding, you may still author your catalogs modularly using JSON Schema `$ref` pointing to external documents during local development. Run `scripts/build_catalog.py` before distributing your catalog to bundle all external file references into a single, independent JSON Schema file: @@ -202,7 +200,7 @@ This catalog imports only `Text` from the Basic Catalog to build a simple Popup "allOf": [ { "$ref": "basic_catalog.json#/components/Text" }, { - "Popup": { + "Popup": { "type": "object", "description": "A modal overlay that displays an icon and text.", "properties": { @@ -225,17 +223,17 @@ Client renderers implement the catalog by mapping the schema definition to actua Example typescript renderer for the hello world catalog ```typescript -import { Catalog, DEFAULT_CATALOG } from '@a2ui/angular'; -import { inputBinding } from '@angular/core'; - -export const RIZZ_CHARTS_CATALOG = { - ...DEFAULT_CATALOG, // Include the basic catalog - HelloWorldBanner: { - type: () => import('./hello_world_banner').then((r) => r.HelloWorldBanner), - bindings: ({ properties }) => [ +import { Catalog, DEFAULT_CATALOG } from '@a2ui/angular'; +import { inputBinding } from '@angular/core'; + +export const RIZZ_CHARTS_CATALOG = { + ...DEFAULT_CATALOG, // Include the basic catalog + HelloWorldBanner: { + type: () => import('./hello_world_banner').then((r) => r.HelloWorldBanner), + bindings: ({ properties }) => [ inputBinding('message', () => ('message' in properties && properties['message']) || undefined) - ], - }, + ], + }, } as Catalog; ``` @@ -247,7 +245,7 @@ import { Component, Input } from '@angular/core'; @Component({ selector: 'hello-world-banner', - imports: [], + imports: [], template: `

Hello World Banner

@@ -260,17 +258,15 @@ export class HelloWorldBanner extends DynamicComponent { } ``` -You can see a working example of a client renderer in the [Rizzcharts demo](../samples/client/angular/projects/rizzcharts/src/a2ui-catalog/catalog.ts). - ## A2UI Catalog Negotiation Because clients and agents can support multiple catalogs, they must agree on which catalog to use through a catalog negotiation handshake. -### Step 1: Agent advertises its support catalogs (optional) +### Step 1: Agent advertises its support catalogs (optional) The agent may optionally advertise which catalogs it is capable of speaking (e.g., in the A2A Agent Card). This is informational; it helps the client know if the agent supports their specific features, but the client doesn’t have to use it. -Example of an A2A AgentCard advertising that the agent supports the basic and rizzcharts catalogs +Example of an A2A AgentCard advertising that the agent supports the basic and custom dashboard catalogs ```json { @@ -284,7 +280,7 @@ Example of an A2A AgentCard advertising that the agent supports the basic and ri "params": { "supportedCatalogIds": [ "https://a2ui.org/specification/v0_9/basic_catalog.json", - "https://github.com/.../rizzcharts_catalog_definition.json" + "https://github.com/.../dashboard_catalog_definition.json" ] } } @@ -310,7 +306,7 @@ Example of A2A message containing the supportedCatalogIds in metadata "a2uiClientCapabilities": { "supportedCatalogIds": [ "https://a2ui.org/specification/v0_9/basic_catalog.json", - "https://github.com/.../rizzcharts_catalog_definition.json" + "https://github.com/.../dashboard_catalog_definition.json" ] } } @@ -340,19 +336,19 @@ A2UI component catalogs require versioning because catalog definitions are often The `catalogId` is a unique text identifier used for negotiation between the client and the agent. -* **Format:** While the `catalogId` is technically a string, the A2UI convention is to use a **URI** (e.g., `https://example.com/catalogs/mysurface/v1/catalog.json`). -* **Purpose:** We use URIs to make the ID globally unique and easy for human developers to inspect in a browser. +* **Format:** While the `catalogId` is technically a string, the A2UI convention is to use a **URI** (e.g., `https://example.com/catalogs/mysurface/v1/catalog.json`). +* **Purpose:** We use URIs to make the ID globally unique and easy for human developers to inspect in a browser. * **No Runtime Fetching:** This URI does not imply that the agent or client downloads the catalog at runtime. **The catalog definition must be known to the agent and client beforehand (at compile/deploy time)**. The URI serves only as a stable identifier. ### Versioning Guidelines -Unlike standard JSON parsers where extra data is safely ignored, A2UI requires strict versioning to prevent semantic errors. If a client silently drops a new component (like a new "Itinerary" component) because it doesn't recognize it, the user misses critical information. +Unlike standard JSON parsers where extra data is safely ignored, A2UI requires strict versioning to prevent semantic errors. If a client silently drops a new component (like a new "Itinerary" component) because it doesn't recognize it, the user misses critical information. To ensure the agent only generates UI the client can fully render, any structural change—even purely additive ones—requires a new catalog version. This is enforced by the A2UI JSON Schema which generally does not allow for unrecognized properties. * **Structural Changes (New Version Required)** Any change that alters the semantic meaning of the A2UI JSON payload requires a new catalog version. This ensures the Agent never sends a component the Client cannot render. A new catalog version is required for: * **Adding a new component:** (e.g., adding `FacePile` or `Itinerary`). - * **Adding a new property:** Even if optional, if the Agent generates it, it expects it to be rendered. + * **Adding a new property:** Even if optional, if the Agent generates it, it expects it to be rendered. * **Renaming/Removing fields:** These are standard breaking changes. * **Metadata Changes (Same Version Allowed)** You may keep the same catalog version only if the change has no impact on the generated JSON structure or the renderer's behavior. You can keep the version when @@ -367,28 +363,28 @@ We recommend including the version in the catalogId. This allows using A2UI cata | Change Type | URI Example | Description | | :--- | :--- | :--- | -| **Current** | .../rizzcharts/v1/catalog.json | The stable, production schema. | -| **Breaking** | .../rizzcharts/v2/catalog.json | A new schema introducing renamed components or structural changes. | +| **Current** | .../dashboard/v1/catalog.json | The stable, production schema. | +| **Breaking** | .../dashboard/v2/catalog.json | A new schema introducing renamed components or structural changes. | ### Handling Migrations To upgrade a catalog without breaking active agents, use A2UI Catalog Negotiation: -1. **Client Update:** The client updates its list of supportedCatalogIds to include *both* the old and new versions (e.g., [".../v2/...", ".../v1/..."]). -2. **Agent Update:** Agents are rebuilt with the v2 schema. When they see the client supports v2, they prefer it. +1. **Client Update:** The client updates its list of supportedCatalogIds to include *both* the old and new versions (e.g., [".../v2/...", ".../v1/..."]). +2. **Agent Update:** Agents are rebuilt with the v2 schema. When they see the client supports v2, they prefer it. 3. **Legacy Support:** Older agents that have not yet been rebuilt will continue to match against v1 in the client's list, ensuring they remain functional. -## A2UI Schema Validation & Fallback +## A2UI Schema Validation & Fallback To ensure a stable user experience, A2UI employs a two-phase validation strategy. This "defense in depth" approach catches errors as early as possible while ensuring clients remain robust when facing unexpected payloads. ### Two-Phase Validation -1. **Agent-Side (Pre-Send):** Before transmitting any UI payload, the agent runtime validates the generated JSON against the catalog definition. - * Purpose: To catch hallucinated properties or malformed structures at the source. - * Outcome: If validation fails, the agent can attempt to fix or regenerate the A2UI JSON, or it can do graceful degradation such as falling back to text in a conversational app. -2. **Client-Side:** Upon receiving the payload, the client library validates the JSON against its local definition of the catalog. - * Purpose: Security and stability. This ensures that the code executing on the user's device strictly conforms to the expected contract, protecting against version mismatches or compromised agent outputs. +1. **Agent-Side (Pre-Send):** Before transmitting any UI payload, the agent runtime validates the generated JSON against the catalog definition. + * Purpose: To catch hallucinated properties or malformed structures at the source. + * Outcome: If validation fails, the agent can attempt to fix or regenerate the A2UI JSON, or it can do graceful degradation such as falling back to text in a conversational app. +2. **Client-Side:** Upon receiving the payload, the client library validates the JSON against its local definition of the catalog. + * Purpose: Security and stability. This ensures that the code executing on the user's device strictly conforms to the expected contract, protecting against version mismatches or compromised agent outputs. * Outcome: Failures here are reported back to the agent using the “error” client message ### Graceful Degradation @@ -397,7 +393,7 @@ Even if a payload passes schema validation, the renderer may encounter runtime i Clients should not crash when encountering these errors. Instead, they should employ Graceful Degradation: -* **Unknown Components:** If a component is recognized in the schema but not implemented in the renderer, render a "safe" fallback (e.g., a generic card with the component's debug name) or skip rendering that specific node entirely. +* **Unknown Components:** If a component is recognized in the schema but not implemented in the renderer, render a "safe" fallback (e.g., a generic card with the component's debug name) or skip rendering that specific node entirely. * **Text Fallback:** If the entire surface fails to render, display the raw text description (if available) or a generic error message: *"This interface could not be displayed."* ### Client-to-Server Error Reporting diff --git a/samples/agent/adk/orchestrator/README.md b/samples/agent/adk/orchestrator/README.md index 23538c89e..325bbfaa3 100644 --- a/samples/agent/adk/orchestrator/README.md +++ b/samples/agent/adk/orchestrator/README.md @@ -33,26 +33,20 @@ Subagents are configured using RemoteA2aAgent which translates ADK events to A2A ``` ```bash - cd samples/agent/adk/contact_lookup - uv run . --port=10004 - ``` - - ```bash - cd samples/agent/adk/rizzcharts - uv run . --port=10005 - ``` + cd samples/agent/adk/contact_lookup + uv run . --port=10004 + ``` 3. Run the orchestrator agent: ```bash cd samples/agent/adk/orchestrator - uv run . --port=10002 --subagent_urls=http://localhost:10003 --subagent_urls=http://localhost:10004 --subagent_urls=http://localhost:10005 + uv run . --port=10002 --subagent_urls=http://localhost:10003 --subagent_urls=http://localhost:10004 ``` -4. Try commands that work with any agent: +4. Try commands that work with any agent: a. "Who is Alex Jordan?" (routed to contact lookup agent) b. "Show me chinese food restaurants in NYC" (routed to restaurant finder agent) - c. "Show my sales data for Q4" (routed to rizzcharts) ## Disclaimer diff --git a/samples/agent/adk/pyproject.toml b/samples/agent/adk/pyproject.toml index c63dd5bf8..0be6fcc13 100644 --- a/samples/agent/adk/pyproject.toml +++ b/samples/agent/adk/pyproject.toml @@ -3,7 +3,7 @@ url = "https://pypi.org/simple" default = true [tool.uv.workspace] -members = ["contact_lookup", "contact_multiple_surfaces", "orchestrator", "restaurant_finder", "rizzcharts"] +members = ["contact_lookup", "contact_multiple_surfaces", "orchestrator", "restaurant_finder"] [tool.uv.sources] a2ui-agent = { path = "../../../agent_sdks/python", editable = true } diff --git a/samples/agent/adk/rizzcharts/.env.example b/samples/agent/adk/rizzcharts/.env.example deleted file mode 100644 index c4522b4ec..000000000 --- a/samples/agent/adk/rizzcharts/.env.example +++ /dev/null @@ -1,7 +0,0 @@ -# Copy this file to .env and fill in your API key -# Get your API key at: https://aistudio.google.com/apikey - -GEMINI_API_KEY=your_gemini_api_key_here - -# Optional: Use Vertex AI instead of Gemini API -# GOOGLE_GENAI_USE_VERTEXAI=TRUE diff --git a/samples/agent/adk/rizzcharts/README.md b/samples/agent/adk/rizzcharts/README.md deleted file mode 100644 index b158d72a2..000000000 --- a/samples/agent/adk/rizzcharts/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# A2UI Rizzcharts Agent Sample - -This sample uses the Agent Development Kit (ADK) along with the A2A protocol to create an ecommerce dashboard using a custom catalog that is hosted as an A2A server. - -## Prerequisites - -- Python 3.9 or higher -- [UV](https://docs.astral.sh/uv/) -- Access to an LLM and API Key - -## Running the Sample - -1. Navigate to the samples directory: - - ```bash - cd samples/agent/adk/rizzcharts - ``` - -2. Create an environment file with your API key: - - ```bash - cp .env.example .env - # Edit .env with your actual API key (do not commit .env) - ``` - -3. Run an agent: - - ```bash - uv run . - ``` - -## Disclaimer - -Important: The sample code provided is for demonstration purposes and illustrates the mechanics of A2UI and the Agent-to-Agent (A2A) protocol. When building production applications, it is critical to treat any agent operating outside of your direct control as a potentially untrusted entity. - -All operational data received from an external agent—including its AgentCard, messages, artifacts, and task statuses—should be handled as untrusted input. For example, a malicious agent could provide crafted data in its fields (e.g., name, skills.description) that, if used without sanitization to construct prompts for a Large Language Model (LLM), could expose your application to prompt injection attacks. - -Similarly, any UI definition or data stream received must be treated as untrusted. Malicious agents could attempt to spoof legitimate interfaces to deceive users (phishing), inject malicious scripts via property values (XSS), or generate excessive layout complexity to degrade client performance (DoS). If your application supports optional embedded content (such as iframes or web views), additional care must be taken to prevent exposure to malicious external sites. - -Developer Responsibility: Failure to properly validate data and strictly sandbox rendered content can introduce severe vulnerabilities. Developers are responsible for implementing appropriate security measures—such as input sanitization, Content Security Policies (CSP), strict isolation for optional embedded content, and secure credential handling—to protect their systems and users. \ No newline at end of file diff --git a/samples/agent/adk/rizzcharts/__init__.py b/samples/agent/adk/rizzcharts/__init__.py deleted file mode 100644 index 48c55d1a3..000000000 --- a/samples/agent/adk/rizzcharts/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from . import agent diff --git a/samples/agent/adk/rizzcharts/__main__.py b/samples/agent/adk/rizzcharts/__main__.py deleted file mode 100644 index 9c988f102..000000000 --- a/samples/agent/adk/rizzcharts/__main__.py +++ /dev/null @@ -1,133 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import logging -import os -import pathlib -import traceback - -import click -from a2a.server.apps import A2AStarletteApplication -from a2a.server.request_handlers import DefaultRequestHandler -from a2a.server.tasks import InMemoryTaskStore -from a2ui.core.schema.constants import VERSION_0_8 -from a2ui.core.schema.manager import A2uiSchemaManager, CatalogConfig -from a2ui.basic_catalog.provider import BasicCatalog -from agent_executor import RizzchartsAgentExecutor, get_a2ui_enabled, get_a2ui_catalog, get_a2ui_examples -from agent import RizzchartsAgent -from google.adk.artifacts import InMemoryArtifactService -from google.adk.memory.in_memory_memory_service import InMemoryMemoryService -from google.adk.models.lite_llm import LiteLlm -from google.adk.runners import Runner -from google.adk.sessions.in_memory_session_service import InMemorySessionService -from dotenv import load_dotenv -from starlette.middleware.cors import CORSMiddleware - -load_dotenv() - -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - - -class MissingAPIKeyError(Exception): - """Exception for missing API key.""" - - -@click.command() -@click.option("--host", default="localhost") -@click.option("--port", default=10002) -def main(host, port): - try: - # Check for API key only if Vertex AI is not configured - if not os.getenv("GOOGLE_GENAI_USE_VERTEXAI") == "TRUE": - if not os.getenv("GEMINI_API_KEY"): - raise MissingAPIKeyError( - "GEMINI_API_KEY environment variable not set and GOOGLE_GENAI_USE_VERTEXAI" - " is not TRUE." - ) - - lite_llm_model = os.getenv("LITELLM_MODEL", "gemini/gemini-2.5-flash") - - base_url = f"http://{host}:{port}" - - schema_manager = A2uiSchemaManager( - VERSION_0_8, - catalogs=[ - CatalogConfig.from_path( - name="rizzcharts", - catalog_path="rizzcharts_catalog_definition.json", - examples_path="examples/rizzcharts_catalog", - ), - BasicCatalog.get_config( - version=VERSION_0_8, - examples_path="examples/standard_catalog", - ), - ], - accepts_inline_catalogs=True, - ) - - agent = RizzchartsAgent( - base_url=base_url, - model=LiteLlm(model=lite_llm_model), - schema_manager=schema_manager, - a2ui_enabled_provider=get_a2ui_enabled, - a2ui_catalog_provider=get_a2ui_catalog, - a2ui_examples_provider=get_a2ui_examples, - ) - runner = Runner( - app_name=agent.name, - agent=agent, - artifact_service=InMemoryArtifactService(), - session_service=InMemorySessionService(), - memory_service=InMemoryMemoryService(), - ) - - agent_executor = RizzchartsAgentExecutor( - base_url=base_url, - runner=runner, - schema_manager=schema_manager, - ) - - request_handler = DefaultRequestHandler( - agent_executor=agent_executor, - task_store=InMemoryTaskStore(), - ) - server = A2AStarletteApplication( - agent_card=agent.get_agent_card(), http_handler=request_handler - ) - import uvicorn - - app = server.build() - - app.add_middleware( - CORSMiddleware, - allow_origins=["http://localhost:5173"], - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], - ) - - uvicorn.run(app, host=host, port=port) - except MissingAPIKeyError as e: - logger.error(f"Error: {e} {traceback.format_exc()}") - exit(1) - except Exception as e: - logger.error( - f"An error occurred during server startup: {e} {traceback.format_exc()}" - ) - exit(1) - - -if __name__ == "__main__": - main() diff --git a/samples/agent/adk/rizzcharts/agent.py b/samples/agent/adk/rizzcharts/agent.py deleted file mode 100644 index d6ce9cd1c..000000000 --- a/samples/agent/adk/rizzcharts/agent.py +++ /dev/null @@ -1,204 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import json -import logging -from pathlib import Path -import pkgutil -from typing import Any, ClassVar -from a2a.types import AgentCapabilities, AgentCard, AgentSkill -from a2ui.a2a import get_a2ui_agent_extension -from a2ui.adk.a2a_extension.send_a2ui_to_client_toolset import SendA2uiToClientToolset, A2uiEnabledProvider, A2uiCatalogProvider, A2uiExamplesProvider -from a2ui.core.schema.manager import A2uiSchemaManager -from google.adk.agents.llm_agent import LlmAgent -from google.adk.agents.readonly_context import ReadonlyContext -from google.adk.planners.built_in_planner import BuiltInPlanner -from google.genai import types -from pydantic import PrivateAttr - -try: - from .tools import get_sales_data, get_store_sales -except ImportError: - from tools import get_sales_data, get_store_sales - -logger = logging.getLogger(__name__) - -RIZZCHARTS_CATALOG_URI = "https://github.com/google/A2UI/blob/main/samples/agent/adk/rizzcharts/rizzcharts_catalog_definition.json" - -ROLE_DESCRIPTION = """ -You are an expert A2UI Ecommerce Dashboard analyst. Your primary function is to translate user requests for ecommerce data into A2UI JSON payloads to display charts and visualizations. You MUST use the `send_a2ui_json_to_client` tool with the `a2ui_json` argument set to the A2UI JSON payload to send to the client. You should also include a brief text message with each response saying what you did and asking if you can help with anything else. -""" - -WORKFLOW_DESCRIPTION = """ -Your task is to analyze the user's request, fetch the necessary data, select the correct generic template, and send the corresponding A2UI JSON payload. - -1. **Analyze the Request:** Determine the user's intent (Visual Chart vs. Geospatial Map). - * "show my sales breakdown by product category for q3" -> **Intent:** Chart. - * "show revenue trends yoy by month" -> **Intent:** Chart. - * "were there any outlier stores in the northeast region" -> **Intent:** Map. - -2. **Fetch Data:** Select and use the appropriate tool to retrieve the necessary data. - * Use **`get_sales_data`** for general sales, revenue, and product category trends (typically for Charts). - * Use **`get_store_sales`** for regional performance, store locations, and geospatial outliers (typically for Maps). - -3. **Select Example:** Based on the intent, choose the correct example block to use as your template. - * **Intent** (Chart/Data Viz) -> Use `---BEGIN CHART EXAMPLE---`. - * **Intent** (Map/Geospatial) -> Use `---BEGIN MAP EXAMPLE---`. - -4. **Construct the JSON Payload:** - * 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". - * 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. - -5. **Call the Tool:** Call the `send_a2ui_json_to_client` tool with the fully constructed `a2ui_json` payload. -""" - -UI_DESCRIPTION = """ -**Core Objective:** To provide a dynamic and interactive dashboard by constructing UI surfaces with the appropriate visualization components based on user queries. - -**Key Components & Examples:** - -You will be provided a schema that defines the A2UI message structure and two key generic component templates for displaying data. - -1. **Charts:** Used for requests about sales breakdowns, revenue performance, comparisons, or trends. - * **Template:** Use the JSON from `---BEGIN CHART EXAMPLE---`. -2. **Maps:** Used for requests about regional data, store locations, geography-based performance, or regional outliers. - * **Template:** Use the JSON from `---BEGIN MAP EXAMPLE---`. - -You will also use layout components like `Column` (as the `root`) and `Text` (to provide a title). -""" - - -class RizzchartsAgent(LlmAgent): - """An agent that runs an ecommerce dashboard""" - - SUPPORTED_CONTENT_TYPES: ClassVar[list[str]] = ["text", "text/plain"] - base_url: str = "" - schema_manager: A2uiSchemaManager = None - _a2ui_enabled_provider: A2uiEnabledProvider = PrivateAttr() - _a2ui_catalog_provider: A2uiCatalogProvider = PrivateAttr() - _a2ui_examples_provider: A2uiExamplesProvider = PrivateAttr() - - def __init__( - self, - model: Any, - base_url: str, - schema_manager: A2uiSchemaManager, - a2ui_enabled_provider: A2uiEnabledProvider, - a2ui_catalog_provider: A2uiCatalogProvider, - a2ui_examples_provider: A2uiExamplesProvider, - ): - """Initializes the RizzchartsAgent. - - Args: - model: The LLM model to use. - base_url: The base URL for the agent. - schema_manager: The A2UI schema manager. - a2ui_enabled_provider: A provider to check if A2UI is enabled. - a2ui_catalog_provider: A provider to retrieve the A2UI catalog (A2uiCatalog object). - a2ui_examples_provider: A provider to retrieve the A2UI examples (str). - """ - - system_instructions = schema_manager.generate_system_prompt( - role_description=ROLE_DESCRIPTION, - workflow_description=WORKFLOW_DESCRIPTION, - ui_description=UI_DESCRIPTION, - include_schema=False, - include_examples=False, - validate_examples=False, - ) - super().__init__( - model=model, - name="rizzcharts_agent", - description="An agent that lets sales managers request sales data.", - instruction=system_instructions, - tools=[ - get_store_sales, - get_sales_data, - SendA2uiToClientToolset( - a2ui_catalog=a2ui_catalog_provider, - a2ui_enabled=a2ui_enabled_provider, - a2ui_examples=a2ui_examples_provider, - ), - ], - planner=BuiltInPlanner( - thinking_config=types.ThinkingConfig( - include_thoughts=True, - ) - ), - disallow_transfer_to_peers=True, - base_url=base_url, - schema_manager=schema_manager, - ) - - self._a2ui_enabled_provider = a2ui_enabled_provider - self._a2ui_catalog_provider = a2ui_catalog_provider - self._a2ui_examples_provider = a2ui_examples_provider - - def get_agent_card(self) -> AgentCard: - """Returns the AgentCard defining this agent's metadata and skills. - - Returns: - An AgentCard object. - """ - return AgentCard( - name="Ecommerce Dashboard Agent", - description=( - "This agent visualizes ecommerce data, showing sales breakdowns, YOY" - " revenue performance, and regional sales outliers." - ), - url=self.base_url, - version="1.0.0", - default_input_modes=RizzchartsAgent.SUPPORTED_CONTENT_TYPES, - default_output_modes=RizzchartsAgent.SUPPORTED_CONTENT_TYPES, - capabilities=AgentCapabilities( - streaming=True, - extensions=[ - get_a2ui_agent_extension( - self.schema_manager.accepts_inline_catalogs, - self.schema_manager.supported_catalog_ids, - ) - ], - ), - skills=[ - AgentSkill( - id="view_sales_by_category", - name="View Sales by Category", - description=( - "Displays a pie chart of sales broken down by product category for" - " a given time period." - ), - tags=["sales", "breakdown", "category", "pie chart", "revenue"], - examples=[ - "show my sales breakdown by product category for q3", - "What's the sales breakdown for last month?", - ], - ), - AgentSkill( - id="view_regional_outliers", - name="View Regional Sales Outliers", - description=( - "Displays a map showing regional sales outliers or store-level" - " performance." - ), - tags=["sales", "regional", "outliers", "stores", "map", "performance"], - examples=[ - "interesting. were there any outlier stores", - "show me a map of store performance", - ], - ), - ], - ) diff --git a/samples/agent/adk/rizzcharts/agent_executor.py b/samples/agent/adk/rizzcharts/agent_executor.py deleted file mode 100644 index cfc96c053..000000000 --- a/samples/agent/adk/rizzcharts/agent_executor.py +++ /dev/null @@ -1,137 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import logging -from pathlib import Path -from typing import override - -from a2a.server.agent_execution import RequestContext -from a2a.types import AgentCapabilities, AgentCard, AgentExtension, AgentSkill -from a2ui.a2a import get_a2ui_agent_extension -from a2ui.a2a import try_activate_a2ui_extension -from a2ui.adk.a2a_extension.send_a2ui_to_client_toolset import convert_send_a2ui_to_client_genai_part_to_a2a_part -from a2ui.core.schema.constants import A2UI_CLIENT_CAPABILITIES_KEY -from a2ui.core.schema.manager import A2uiSchemaManager -from google.adk.a2a.converters.request_converter import AgentRunRequest -from google.adk.a2a.executor.a2a_agent_executor import A2aAgentExecutor -from google.adk.a2a.executor.a2a_agent_executor import A2aAgentExecutorConfig -from google.adk.agents.invocation_context import new_invocation_context_id -from google.adk.agents.readonly_context import ReadonlyContext -from google.adk.events.event import Event -from google.adk.events.event_actions import EventActions -from google.adk.runners import Runner - -logger = logging.getLogger(__name__) - -_A2UI_ENABLED_KEY = "system:a2ui_enabled" -_A2UI_CATALOG_KEY = "system:a2ui_catalog" -_A2UI_EXAMPLES_KEY = "system:a2ui_examples" - - -def get_a2ui_catalog(ctx: ReadonlyContext): - """Retrieves the A2UI catalog from the session state. - - Args: - ctx: The ReadonlyContext for resolving the catalog. - - Returns: - The A2UI catalog or None if not found. - """ - return ctx.state.get(_A2UI_CATALOG_KEY) - - -def get_a2ui_examples(ctx: ReadonlyContext): - """Retrieves the A2UI examples from the session state. - - Args: - ctx: The ReadonlyContext for resolving the examples. - - Returns: - The A2UI examples or None if not found. - """ - return ctx.state.get(_A2UI_EXAMPLES_KEY) - - -def get_a2ui_enabled(ctx: ReadonlyContext): - """Checks if A2UI is enabled in the current session. - - Args: - ctx: The ReadonlyContext for resolving enablement. - - Returns: - True if A2UI is enabled, False otherwise. - """ - return ctx.state.get(_A2UI_ENABLED_KEY, False) - - -class RizzchartsAgentExecutor(A2aAgentExecutor): - """Executor for the Rizzcharts agent that handles A2UI session setup.""" - - def __init__( - self, - base_url: str, - runner: Runner, - schema_manager: A2uiSchemaManager, - ): - self._base_url = base_url - self.schema_manager = schema_manager - - config = A2aAgentExecutorConfig( - gen_ai_part_converter=convert_send_a2ui_to_client_genai_part_to_a2a_part - ) - super().__init__(runner=runner, config=config) - - @override - async def _prepare_session( - self, - context: RequestContext, - run_request: AgentRunRequest, - runner: Runner, - ): - logger.info(f"Loading session for message {context.message}") - - session = await super()._prepare_session(context, run_request, runner) - - if "base_url" not in session.state: - session.state["base_url"] = self._base_url - - use_ui = try_activate_a2ui_extension(context) - if use_ui: - capabilities = ( - context.message.metadata.get(A2UI_CLIENT_CAPABILITIES_KEY) - if context.message and context.message.metadata - else None - ) - a2ui_catalog = self.schema_manager.get_selected_catalog( - client_ui_capabilities=capabilities - ) - - examples = self.schema_manager.load_examples(a2ui_catalog, validate=True) - - await runner.session_service.append_event( - session, - Event( - invocation_id=new_invocation_context_id(), - author="system", - actions=EventActions( - state_delta={ - _A2UI_ENABLED_KEY: True, - _A2UI_CATALOG_KEY: a2ui_catalog, - _A2UI_EXAMPLES_KEY: examples, - } - ), - ), - ) - - return session diff --git a/samples/agent/adk/rizzcharts/examples/rizzcharts_catalog/chart.json b/samples/agent/adk/rizzcharts/examples/rizzcharts_catalog/chart.json deleted file mode 100644 index 9fec64ba3..000000000 --- a/samples/agent/adk/rizzcharts/examples/rizzcharts_catalog/chart.json +++ /dev/null @@ -1,202 +0,0 @@ -[ - { - "beginRendering": { - "surfaceId": "sales-dashboard", - "root": "root-canvas" - } - }, - { - "surfaceUpdate": { - "surfaceId": "sales-dashboard", - "components": [ - { - "id": "root-canvas", - "component": { - "Canvas": { - "children": { - "explicitList": [ - "chart-container" - ] - } - } - } - }, - { - "id": "chart-container", - "component": { - "Column": { - "children": { - "explicitList": [ - "sales-chart" - ] - }, - "alignment": "center" - } - } - }, - { - "id": "sales-chart", - "component": { - "Chart": { - "type": "doughnut", - "title": { - "path": "/chart.title" - }, - "chartData": { - "path": "/chart.items" - } - } - } - } - ] - } - }, - { - "dataModelUpdate": { - "surfaceId": "sales-dashboard", - "path": "/", - "contents": [ - { - "key": "chart.title", - "valueString": "Sales by Category" - }, - { - "key": "chart.items[0].label", - "valueString": "Apparel" - }, - { - "key": "chart.items[0].value", - "valueNumber": 41 - }, - { - "key": "chart.items[0].drillDown[0].label", - "valueString": "Tops" - }, - { - "key": "chart.items[0].drillDown[0].value", - "valueNumber": 31 - }, - { - "key": "chart.items[0].drillDown[1].label", - "valueString": "Bottoms" - }, - { - "key": "chart.items[0].drillDown[1].value", - "valueNumber": 38 - }, - { - "key": "chart.items[0].drillDown[2].label", - "valueString": "Outerwear" - }, - { - "key": "chart.items[0].drillDown[2].value", - "valueNumber": 20 - }, - { - "key": "chart.items[0].drillDown[3].label", - "valueString": "Footwear" - }, - { - "key": "chart.items[0].drillDown[3].value", - "valueNumber": 11 - }, - { - "key": "chart.items[1].label", - "valueString": "Home Goods" - }, - { - "key": "chart.items[1].value", - "valueNumber": 15 - }, - { - "key": "chart.items[1].drillDown[0].label", - "valueString": "Pillow" - }, - { - "key": "chart.items[1].drillDown[0].value", - "valueNumber": 8 - }, - { - "key": "chart.items[1].drillDown[1].label", - "valueString": "Coffee Maker" - }, - { - "key": "chart.items[1].drillDown[1].value", - "valueNumber": 16 - }, - { - "key": "chart.items[1].drillDown[2].label", - "valueString": "Area Rug" - }, - { - "key": "chart.items[1].drillDown[2].value", - "valueNumber": 3 - }, - { - "key": "chart.items[1].drillDown[3].label", - "valueString": "Bath Towels" - }, - { - "key": "chart.items[1].drillDown[3].value", - "valueNumber": 14 - }, - { - "key": "chart.items[2].label", - "valueString": "Electronics" - }, - { - "key": "chart.items[2].value", - "valueNumber": 28 - }, - { - "key": "chart.items[2].drillDown[0].label", - "valueString": "Phones" - }, - { - "key": "chart.items[2].drillDown[0].value", - "valueNumber": 25 - }, - { - "key": "chart.items[2].drillDown[1].label", - "valueString": "Laptops" - }, - { - "key": "chart.items[2].drillDown[1].value", - "valueNumber": 27 - }, - { - "key": "chart.items[2].drillDown[2].label", - "valueString": "TVs" - }, - { - "key": "chart.items[2].drillDown[2].value", - "valueNumber": 21 - }, - { - "key": "chart.items[2].drillDown[3].label", - "valueString": "Other" - }, - { - "key": "chart.items[2].drillDown[3].value", - "valueNumber": 27 - }, - { - "key": "chart.items[3].label", - "valueString": "Health & Beauty" - }, - { - "key": "chart.items[3].value", - "valueNumber": 10 - }, - { - "key": "chart.items[4].label", - "valueString": "Other" - }, - { - "key": "chart.items[4].value", - "valueNumber": 6 - } - ] - } - } -] \ No newline at end of file diff --git a/samples/agent/adk/rizzcharts/examples/rizzcharts_catalog/map.json b/samples/agent/adk/rizzcharts/examples/rizzcharts_catalog/map.json deleted file mode 100644 index 08530a89b..000000000 --- a/samples/agent/adk/rizzcharts/examples/rizzcharts_catalog/map.json +++ /dev/null @@ -1,176 +0,0 @@ -[ - { - "beginRendering": { - "surfaceId": "la-map-view", - "root": "root-canvas" - } - }, - { - "surfaceUpdate": { - "surfaceId": "la-map-view", - "components": [ - { - "id": "root-canvas", - "component": { - "Canvas": { - "children": { - "explicitList": [ - "map-layout-container" - ] - } - } - } - }, - { - "id": "map-layout-container", - "component": { - "Column": { - "children": { - "explicitList": [ - "map-header", - "location-map" - ] - }, - "alignment": "stretch" - } - } - }, - { - "id": "map-header", - "component": { - "Text": { - "text": { - "literalString": "Points of Interest in Los Angeles" - }, - "usageHint": "h2" - } - } - }, - { - "id": "location-map", - "component": { - "GoogleMap": { - "center": { - "path": "/mapConfig.center" - }, - "zoom": { - "path": "/mapConfig.zoom" - }, - "pins": { - "path": "/mapConfig.locations" - } - } - } - } - ] - } - }, - { - "dataModelUpdate": { - "surfaceId": "la-map-view", - "path": "/", - "contents": [ - { - "key": "mapConfig.center.lat", - "valueNumber": 34.0522 - }, - { - "key": "mapConfig.center.lng", - "valueNumber": -118.2437 - }, - { - "key": "mapConfig.zoom", - "valueNumber": 11 - }, - { - "key": "mapConfig.locations[0].lat", - "valueNumber": 34.0135 - }, - { - "key": "mapConfig.locations[0].lng", - "valueNumber": -118.4947 - }, - { - "key": "mapConfig.locations[0].name", - "valueString": "Google Store Santa Monica" - }, - { - "key": "mapConfig.locations[0].description", - "valueString": "Your local destination for Google hardware." - }, - { - "key": "mapConfig.locations[0].background", - "valueString": "#4285F4" - }, - { - "key": "mapConfig.locations[0].borderColor", - "valueString": "#FFFFFF" - }, - { - "key": "mapConfig.locations[0].glyphColor", - "valueString": "#FFFFFF" - }, - { - "key": "mapConfig.locations[1].lat", - "valueNumber": 34.1341 - }, - { - "key": "mapConfig.locations[1].lng", - "valueNumber": -118.3215 - }, - { - "key": "mapConfig.locations[1].name", - "valueString": "Griffith Observatory" - }, - { - "key": "mapConfig.locations[2].lat", - "valueNumber": 34.1340 - }, - { - "key": "mapConfig.locations[2].lng", - "valueNumber": -118.3397 - }, - { - "key": "mapConfig.locations[2].name", - "valueString": "Hollywood Sign Viewpoint" - }, - { - "key": "mapConfig.locations[3].lat", - "valueNumber": 34.0453 - }, - { - "key": "mapConfig.locations[3].lng", - "valueNumber": -118.2673 - }, - { - "key": "mapConfig.locations[3].name", - "valueString": "Crypto.com Arena" - }, - { - "key": "mapConfig.locations[4].lat", - "valueNumber": 34.0639 - }, - { - "key": "mapConfig.locations[4].lng", - "valueNumber": -118.3592 - }, - { - "key": "mapConfig.locations[4].name", - "valueString": "LACMA" - }, - { - "key": "mapConfig.locations[5].lat", - "valueNumber": 33.9850 - }, - { - "key": "mapConfig.locations[5].lng", - "valueNumber": -118.4729 - }, - { - "key": "mapConfig.locations[5].name", - "valueString": "Venice Beach Boardwalk" - } - ] - } - } -] \ No newline at end of file diff --git a/samples/agent/adk/rizzcharts/examples/standard_catalog/chart.json b/samples/agent/adk/rizzcharts/examples/standard_catalog/chart.json deleted file mode 100644 index 76169f133..000000000 --- a/samples/agent/adk/rizzcharts/examples/standard_catalog/chart.json +++ /dev/null @@ -1,151 +0,0 @@ -[ - { - "beginRendering": { - "surfaceId": "sales-dashboard", - "root": "root-column", - "styles": { - "primaryColor": "#00BFFF", - "font": "Arial" - } - } - }, - { - "surfaceUpdate": { - "surfaceId": "sales-dashboard", - "components": [ - { - "id": "root-column", - "component": { - "Column": { - "children": { - "explicitList": [ - "chart-title", - "category-list" - ] - } - } - } - }, - { - "id": "chart-title", - "component": { - "Text": { - "text": { - "path": "/chart.title" - }, - "usageHint": "h2" - } - } - }, - { - "id": "category-list", - "component": { - "List": { - "direction": "vertical", - "children": { - "template": { - "componentId": "category-item-template", - "dataBinding": "/chart.items" - } - } - } - } - }, - { - "id": "category-item-template", - "component": { - "Card": { - "child": "item-row" - } - } - }, - { - "id": "item-row", - "component": { - "Row": { - "distribution": "spaceBetween", - "children": { - "explicitList": [ - "item-label", - "item-value" - ] - } - } - } - }, - { - "id": "item-label", - "component": { - "Text": { - "text": { - "path": "/label" - } - } - } - }, - { - "id": "item-value", - "component": { - "Text": { - "text": { - "path": "/value" - } - } - } - } - ] - } - }, - { - "dataModelUpdate": { - "surfaceId": "sales-dashboard", - "path": "/", - "contents": [ - { - "key": "chart.title", - "valueString": "Sales by Category" - }, - { - "key": "chart.items[0].label", - "valueString": "Apparel" - }, - { - "key": "chart.items[0].value", - "valueNumber": 41 - }, - { - "key": "chart.items[1].label", - "valueString": "Home Goods" - }, - { - "key": "chart.items[1].value", - "valueNumber": 15 - }, - { - "key": "chart.items[2].label", - "valueString": "Electronics" - }, - { - "key": "chart.items[2].value", - "valueNumber": 28 - }, - { - "key": "chart.items[3].label", - "valueString": "Health & Beauty" - }, - { - "key": "chart.items[3].value", - "valueNumber": 10 - }, - { - "key": "chart.items[4].label", - "valueString": "Other" - }, - { - "key": "chart.items[4].value", - "valueNumber": 6 - } - ] - } - } -] \ No newline at end of file diff --git a/samples/agent/adk/rizzcharts/examples/standard_catalog/map.json b/samples/agent/adk/rizzcharts/examples/standard_catalog/map.json deleted file mode 100644 index f38396c00..000000000 --- a/samples/agent/adk/rizzcharts/examples/standard_catalog/map.json +++ /dev/null @@ -1,157 +0,0 @@ -[ - { - "beginRendering": { - "surfaceId": "la-map-view", - "root": "root-column", - "styles": { - "primaryColor": "#4285F4", - "font": "Roboto" - } - } - }, - { - "surfaceUpdate": { - "surfaceId": "la-map-view", - "components": [ - { - "id": "root-column", - "component": { - "Column": { - "children": { - "explicitList": [ - "map-header", - "map-image", - "location-list" - ] - }, - "alignment": "stretch" - } - } - }, - { - "id": "map-header", - "component": { - "Text": { - "text": { - "literalString": "Points of Interest in Los Angeles" - }, - "usageHint": "h2" - } - } - }, - { - "id": "location-list", - "component": { - "List": { - "direction": "vertical", - "children": { - "template": { - "componentId": "location-card-template", - "dataBinding": "/mapConfig.locations" - } - } - } - } - }, - { - "id": "location-card-template", - "component": { - "Card": { - "child": "location-details" - } - } - }, - { - "id": "location-details", - "component": { - "Column": { - "children": { - "explicitList": [ - "location-name", - "location-description" - ] - } - } - } - }, - { - "id": "location-name", - "component": { - "Text": { - "text": { - "path": "/name" - }, - "usageHint": "h4" - } - } - }, - { - "id": "location-description", - "component": { - "Text": { - "text": { - "path": "/description" - } - } - } - } - ] - } - }, - { - "dataModelUpdate": { - "surfaceId": "la-map-view", - "path": "/", - "contents": [ - { - "key": "mapConfig.locations[0].name", - "valueString": "Google Store Santa Monica" - }, - { - "key": "mapConfig.locations[0].description", - "valueString": "Your local destination for Google hardware." - }, - { - "key": "mapConfig.locations[1].name", - "valueString": "Griffith Observatory" - }, - { - "key": "mapConfig.locations[1].description", - "valueString": "A public observatory with views of the Hollywood Sign." - }, - { - "key": "mapConfig.locations[2].name", - "valueString": "Hollywood Sign Viewpoint" - }, - { - "key": "mapConfig.locations[2].description", - "valueString": "Iconic landmark in the Hollywood Hills." - }, - { - "key": "mapConfig.locations[3].name", - "valueString": "Crypto.com Arena" - }, - { - "key": "mapConfig.locations[3].description", - "valueString": "Multi-purpose sports and entertainment arena." - }, - { - "key": "mapConfig.locations[4].name", - "valueString": "LACMA" - }, - { - "key": "mapConfig.locations[4].description", - "valueString": "Los Angeles County Museum of Art." - }, - { - "key": "mapConfig.locations[5].name", - "valueString": "Venice Beach Boardwalk" - }, - { - "key": "mapConfig.locations[5].description", - "valueString": "Famous oceanfront promenade." - } - ] - } - } -] \ No newline at end of file diff --git a/samples/agent/adk/rizzcharts/pyproject.toml b/samples/agent/adk/rizzcharts/pyproject.toml deleted file mode 100644 index 8f60edf70..000000000 --- a/samples/agent/adk/rizzcharts/pyproject.toml +++ /dev/null @@ -1,33 +0,0 @@ -[project] -name = "rizzcharts" -version = "0.1.0" -description = "Sample Google ADK-based rizzcharts agent that uses a2ui extension with a custom catalog and is hosted as an A2A server agent." -readme = "README.md" -requires-python = ">=3.13" -dependencies = [ - "a2a-sdk>=0.3.0", - "click>=8.1.8", - "google-adk>=1.8.0", - "google-genai>=1.27.0", - "python-dotenv>=1.1.0", - "litellm", - "jsonschema>=4.0.0", - "a2ui-agent", -] - -[tool.hatch.build.targets.wheel] -packages = ["."] - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[tool.hatch.metadata] -allow-direct-references = true - -[[tool.uv.index]] -url = "https://pypi.org/simple" -default = true - -[tool.uv.sources] -a2ui-agent = { path = "../../../../agent_sdks/python", editable = true } diff --git a/samples/agent/adk/rizzcharts/rizzcharts_catalog_definition.json b/samples/agent/adk/rizzcharts/rizzcharts_catalog_definition.json deleted file mode 100644 index e7ccff637..000000000 --- a/samples/agent/adk/rizzcharts/rizzcharts_catalog_definition.json +++ /dev/null @@ -1,951 +0,0 @@ -{ - "catalogId": "https://github.com/google/A2UI/blob/main/samples/agent/adk/rizzcharts/rizzcharts_catalog_definition.json", - "components": { - "Text": { - "type": "object", - "additionalProperties": false, - "properties": { - "text": { - "type": "object", - "description": "The text content to display. This can be a literal string or a reference to a value in the data model ('path', e.g., '/doc/title'). While simple Markdown formatting is supported (i.e. without HTML, images, or links), utilizing dedicated UI components is generally preferred for a richer and more structured presentation.", - "additionalProperties": false, - "properties": { - "literalString": { - "type": "string" - }, - "path": { - "type": "string" - } - } - }, - "usageHint": { - "type": "string", - "description": "A hint for the base text style. One of:\n- `h1`: Largest heading.\n- `h2`: Second largest heading.\n- `h3`: Third largest heading.\n- `h4`: Fourth largest heading.\n- `h5`: Fifth largest heading.\n- `caption`: Small text for captions.\n- `body`: Standard body text.", - "enum": [ - "h1", - "h2", - "h3", - "h4", - "h5", - "caption", - "body" - ] - } - }, - "required": [ - "text" - ] - }, - "Image": { - "type": "object", - "additionalProperties": false, - "properties": { - "url": { - "type": "object", - "description": "The URL of the image to display. This can be a literal string ('literal') or a reference to a value in the data model ('path', e.g. '/thumbnail/url').", - "additionalProperties": false, - "properties": { - "literalString": { - "type": "string" - }, - "path": { - "type": "string" - } - } - }, - "fit": { - "type": "string", - "description": "Specifies how the image should be resized to fit its container. This corresponds to the CSS 'object-fit' property.", - "enum": [ - "contain", - "cover", - "fill", - "none", - "scale-down" - ] - }, - "usageHint": { - "type": "string", - "description": "A hint for the image size and style. One of:\n- `icon`: Small square icon.\n- `avatar`: Circular avatar image.\n- `smallFeature`: Small feature image.\n- `mediumFeature`: Medium feature image.\n- `largeFeature`: Large feature image.\n- `header`: Full-width, full bleed, header image.", - "enum": [ - "icon", - "avatar", - "smallFeature", - "mediumFeature", - "largeFeature", - "header" - ] - } - }, - "required": [ - "url" - ] - }, - "Icon": { - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "type": "object", - "description": "The name of the icon to display. This can be a literal string or a reference to a value in the data model ('path', e.g. '/form/submit').", - "additionalProperties": false, - "properties": { - "literalString": { - "type": "string", - "enum": [ - "accountCircle", - "add", - "arrowBack", - "arrowForward", - "attachFile", - "calendarToday", - "call", - "camera", - "check", - "close", - "delete", - "download", - "edit", - "event", - "error", - "favorite", - "favoriteOff", - "folder", - "help", - "home", - "info", - "locationOn", - "lock", - "lockOpen", - "mail", - "menu", - "moreVert", - "moreHoriz", - "notificationsOff", - "notifications", - "payment", - "person", - "phone", - "photo", - "print", - "refresh", - "search", - "send", - "settings", - "share", - "shoppingCart", - "star", - "starHalf", - "starOff", - "upload", - "visibility", - "visibilityOff", - "warning" - ] - }, - "path": { - "type": "string" - } - } - } - }, - "required": [ - "name" - ] - }, - "Video": { - "type": "object", - "additionalProperties": false, - "properties": { - "url": { - "type": "object", - "description": "The URL of the video to display. This can be a literal string or a reference to a value in the data model ('path', e.g. '/video/url').", - "additionalProperties": false, - "properties": { - "literalString": { - "type": "string" - }, - "path": { - "type": "string" - } - } - } - }, - "required": [ - "url" - ] - }, - "AudioPlayer": { - "type": "object", - "additionalProperties": false, - "properties": { - "url": { - "type": "object", - "description": "The URL of the audio to be played. This can be a literal string ('literal') or a reference to a value in the data model ('path', e.g. '/song/url').", - "additionalProperties": false, - "properties": { - "literalString": { - "type": "string" - }, - "path": { - "type": "string" - } - } - }, - "description": { - "type": "object", - "description": "A description of the audio, such as a title or summary. This can be a literal string or a reference to a value in the data model ('path', e.g. '/song/title').", - "additionalProperties": false, - "properties": { - "literalString": { - "type": "string" - }, - "path": { - "type": "string" - } - } - } - }, - "required": [ - "url" - ] - }, - "Row": { - "type": "object", - "additionalProperties": false, - "properties": { - "children": { - "type": "object", - "description": "Defines the children. Use 'explicitList' for a fixed set of children, or 'template' to generate children from a data list.", - "additionalProperties": false, - "properties": { - "explicitList": { - "type": "array", - "items": { - "type": "string" - } - }, - "template": { - "type": "object", - "description": "A template for generating a dynamic list of children from a data model list. `componentId` is the component to use as a template, and `dataBinding` is the path to the map of components in the data model. Values in the map will define the list of children.", - "additionalProperties": false, - "properties": { - "componentId": { - "type": "string" - }, - "dataBinding": { - "type": "string" - } - }, - "required": [ - "componentId", - "dataBinding" - ] - } - } - }, - "distribution": { - "type": "string", - "description": "Defines the arrangement of children along the main axis (horizontally). This corresponds to the CSS 'justify-content' property.", - "enum": [ - "center", - "end", - "spaceAround", - "spaceBetween", - "spaceEvenly", - "start" - ] - }, - "alignment": { - "type": "string", - "description": "Defines the alignment of children along the cross axis (vertically). This corresponds to the CSS 'align-items' property.", - "enum": [ - "start", - "center", - "end", - "stretch" - ] - } - }, - "required": [ - "children" - ] - }, - "Column": { - "type": "object", - "additionalProperties": false, - "properties": { - "children": { - "type": "object", - "description": "Defines the children. Use 'explicitList' for a fixed set of children, or 'template' to generate children from a data list.", - "additionalProperties": false, - "properties": { - "explicitList": { - "type": "array", - "items": { - "type": "string" - } - }, - "template": { - "type": "object", - "description": "A template for generating a dynamic list of children from a data model list. `componentId` is the component to use as a template, and `dataBinding` is the path to the map of components in the data model. Values in the map will define the list of children.", - "additionalProperties": false, - "properties": { - "componentId": { - "type": "string" - }, - "dataBinding": { - "type": "string" - } - }, - "required": [ - "componentId", - "dataBinding" - ] - } - } - }, - "distribution": { - "type": "string", - "description": "Defines the arrangement of children along the main axis (vertically). This corresponds to the CSS 'justify-content' property.", - "enum": [ - "start", - "center", - "end", - "spaceBetween", - "spaceAround", - "spaceEvenly" - ] - }, - "alignment": { - "type": "string", - "description": "Defines the alignment of children along the cross axis (horizontally). This corresponds to the CSS 'align-items' property.", - "enum": [ - "center", - "end", - "start", - "stretch" - ] - } - }, - "required": [ - "children" - ] - }, - "List": { - "type": "object", - "additionalProperties": false, - "properties": { - "children": { - "type": "object", - "description": "Defines the children. Use 'explicitList' for a fixed set of children, or 'template' to generate children from a data list.", - "additionalProperties": false, - "properties": { - "explicitList": { - "type": "array", - "items": { - "type": "string" - } - }, - "template": { - "type": "object", - "description": "A template for generating a dynamic list of children from a data model list. `componentId` is the component to use as a template, and `dataBinding` is the path to the map of components in the data model. Values in the map will define the list of children.", - "additionalProperties": false, - "properties": { - "componentId": { - "type": "string" - }, - "dataBinding": { - "type": "string" - } - }, - "required": [ - "componentId", - "dataBinding" - ] - } - } - }, - "direction": { - "type": "string", - "description": "The direction in which the list items are laid out.", - "enum": [ - "vertical", - "horizontal" - ] - }, - "alignment": { - "type": "string", - "description": "Defines the alignment of children along the cross axis.", - "enum": [ - "start", - "center", - "end", - "stretch" - ] - } - }, - "required": [ - "children" - ] - }, - "Card": { - "type": "object", - "additionalProperties": false, - "properties": { - "child": { - "type": "string", - "description": "The ID of the component to be rendered inside the card." - } - }, - "required": [ - "child" - ] - }, - "Tabs": { - "type": "object", - "additionalProperties": false, - "properties": { - "tabItems": { - "type": "array", - "description": "An array of objects, where each object defines a tab with a title and a child component.", - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "title": { - "type": "object", - "description": "The tab title. Defines the value as either a literal value or a path to data model value (e.g. '/options/title').", - "additionalProperties": false, - "properties": { - "literalString": { - "type": "string" - }, - "path": { - "type": "string" - } - } - }, - "child": { - "type": "string" - } - }, - "required": [ - "title", - "child" - ] - } - } - }, - "required": [ - "tabItems" - ] - }, - "Divider": { - "type": "object", - "additionalProperties": false, - "properties": { - "axis": { - "type": "string", - "description": "The orientation of the divider.", - "enum": [ - "horizontal", - "vertical" - ] - } - } - }, - "Modal": { - "type": "object", - "additionalProperties": false, - "properties": { - "entryPointChild": { - "type": "string", - "description": "The ID of the component that opens the modal when interacted with (e.g., a button)." - }, - "contentChild": { - "type": "string", - "description": "The ID of the component to be displayed inside the modal." - } - }, - "required": [ - "entryPointChild", - "contentChild" - ] - }, - "Button": { - "type": "object", - "additionalProperties": false, - "properties": { - "child": { - "type": "string", - "description": "The ID of the component to display in the button, typically a Text component." - }, - "primary": { - "type": "boolean", - "description": "Indicates if this button should be styled as the primary action." - }, - "action": { - "type": "object", - "description": "The client-side action to be dispatched when the button is clicked. It includes the action's name and an optional context payload.", - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "context": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "key": { - "type": "string" - }, - "value": { - "type": "object", - "description": "Defines the value to be included in the context as either a literal value or a path to a data model value (e.g. '/user/name').", - "additionalProperties": false, - "properties": { - "path": { - "type": "string" - }, - "literalString": { - "type": "string" - }, - "literalNumber": { - "type": "number" - }, - "literalBoolean": { - "type": "boolean" - } - } - } - }, - "required": [ - "key", - "value" - ] - } - } - }, - "required": [ - "name" - ] - } - }, - "required": [ - "child", - "action" - ] - }, - "CheckBox": { - "type": "object", - "additionalProperties": false, - "properties": { - "label": { - "type": "object", - "description": "The text to display next to the checkbox. Defines the value as either a literal value or a path to data model ('path', e.g. '/option/label').", - "additionalProperties": false, - "properties": { - "literalString": { - "type": "string" - }, - "path": { - "type": "string" - } - } - }, - "value": { - "type": "object", - "description": "The current state of the checkbox (true for checked, false for unchecked). This can be a literal boolean ('literalBoolean') or a reference to a value in the data model ('path', e.g. '/filter/open').", - "additionalProperties": false, - "properties": { - "literalBoolean": { - "type": "boolean" - }, - "path": { - "type": "string" - } - } - } - }, - "required": [ - "label", - "value" - ] - }, - "TextField": { - "type": "object", - "additionalProperties": false, - "properties": { - "label": { - "type": "object", - "description": "The text label for the input field. This can be a literal string or a reference to a value in the data model ('path, e.g. '/user/name').", - "additionalProperties": false, - "properties": { - "literalString": { - "type": "string" - }, - "path": { - "type": "string" - } - } - }, - "text": { - "type": "object", - "description": "The value of the text field. This can be a literal string or a reference to a value in the data model ('path', e.g. '/user/name').", - "additionalProperties": false, - "properties": { - "literalString": { - "type": "string" - }, - "path": { - "type": "string" - } - } - }, - "textFieldType": { - "type": "string", - "description": "The type of input field to display.", - "enum": [ - "date", - "longText", - "number", - "shortText", - "obscured" - ] - }, - "validationRegexp": { - "type": "string", - "description": "A regular expression used for client-side validation of the input." - } - }, - "required": [ - "label" - ] - }, - "DateTimeInput": { - "type": "object", - "additionalProperties": false, - "properties": { - "value": { - "type": "object", - "description": "The selected date and/or time value in ISO 8601 format. This can be a literal string ('literalString') or a reference to a value in the data model ('path', e.g. '/user/dob').", - "additionalProperties": false, - "properties": { - "literalString": { - "type": "string" - }, - "path": { - "type": "string" - } - } - }, - "enableDate": { - "type": "boolean", - "description": "If true, allows the user to select a date." - }, - "enableTime": { - "type": "boolean", - "description": "If true, allows the user to select a time." - } - }, - "required": [ - "value" - ] - }, - "MultipleChoice": { - "type": "object", - "additionalProperties": false, - "required": [ - "selections", - "options" - ], - "properties": { - "selections": { - "type": "object", - "description": "The currently selected values for the component. This can be a literal array of strings or a path to an array in the data model('path', e.g. '/hotel/options').", - "additionalProperties": false, - "properties": { - "literalArray": { - "type": "array", - "items": { - "type": "string" - } - }, - "path": { - "type": "string" - } - } - }, - "options": { - "type": "array", - "description": "An array of available options for the user to choose from.", - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "label": { - "type": "object", - "description": "The text to display for this option. This can be a literal string or a reference to a value in the data model (e.g. '/option/label').", - "additionalProperties": false, - "properties": { - "literalString": { - "type": "string" - }, - "path": { - "type": "string" - } - } - }, - "value": { - "type": "string", - "description": "The value to be associated with this option when selected." - } - }, - "required": [ - "label", - "value" - ] - } - }, - "maxAllowedSelections": { - "type": "integer", - "description": "The maximum number of options that the user is allowed to select." - }, - "variant": { - "type": "string", - "description": "The display style of the component.", - "enum": [ - "checkbox", - "chips" - ] - }, - "filterable": { - "type": "boolean", - "description": "If true, displays a search input to filter the options." - } - } - }, - "Slider": { - "type": "object", - "additionalProperties": false, - "properties": { - "value": { - "type": "object", - "description": "The current value of the slider. This can be a literal number ('literalNumber') or a reference to a value in the data model ('path', e.g. '/restaurant/cost').", - "additionalProperties": false, - "properties": { - "literalNumber": { - "type": "number" - }, - "path": { - "type": "string" - } - } - }, - "minValue": { - "type": "number", - "description": "The minimum value of the slider." - }, - "maxValue": { - "type": "number", - "description": "The maximum value of the slider." - } - }, - "required": [ - "value" - ] - }, - "Canvas": { - "type": "object", - "description": "Renders the UI element in a stateful panel next to the chat window.", - "properties": { - "children": { - "type": "object", - "description": "Defines the children. Use 'explicitList' for a fixed set of children.", - "properties": { - "explicitList": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - }, - "required": [ - "children" - ] - }, - "Chart": { - "type": "object", - "description": "An interactive chart that uses a hierarchical list of objects for its data.", - "properties": { - "type": { - "type": "string", - "description": "The type of chart to render.", - "enum": [ - "doughnut", - "pie" - ] - }, - "title": { - "type": "object", - "description": "The title of the chart. Can be a literal string or a data model path.", - "properties": { - "literalString": { - "type": "string" - }, - "path": { - "type": "string" - } - } - }, - "chartData": { - "type": "object", - "description": "The data for the chart, provided as a list of items. Can be a literal array or a data model path.", - "properties": { - "literalArray": { - "type": "array", - "items": { - "type": "object", - "properties": { - "label": { - "type": "string" - }, - "value": { - "type": "number" - }, - "drillDown": { - "type": "array", - "description": "An optional list of items for the next level of data.", - "items": { - "type": "object", - "properties": { - "label": { - "type": "string" - }, - "value": { - "type": "number" - } - }, - "required": [ - "label", - "value" - ] - } - } - }, - "required": [ - "label", - "value" - ] - } - }, - "path": { - "type": "string" - } - } - } - }, - "required": [ - "type", - "chartData" - ] - }, - "GoogleMap": { - "type": "object", - "description": "A component to display a Google Map with pins.", - "properties": { - "center": { - "type": "object", - "description": "The center point of the map, containing latitude and longitude. Can be a literal object or a data model path.", - "properties": { - "literalObject": { - "type": "object", - "properties": { - "lat": { - "type": "number" - }, - "lng": { - "type": "number" - } - }, - "required": [ - "lat", - "lng" - ] - }, - "path": { - "type": "string" - } - } - }, - "zoom": { - "type": "object", - "description": "The zoom level of the map. Can be a literal number or a data model path.", - "properties": { - "literalNumber": { - "type": "number" - }, - "path": { - "type": "string" - } - } - }, - "pins": { - "type": "object", - "description": "A list of pin objects to display on the map. Can be a literal array or a data model path.", - "properties": { - "literalArray": { - "type": "array", - "items": { - "type": "object", - "properties": { - "lat": { - "type": "number" - }, - "lng": { - "type": "number" - }, - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "background": { - "type": "string", - "description": "Hex color code for the pin background (e.g., '#FBBC04')." - }, - "borderColor": { - "type": "string", - "description": "Hex color code for the pin border (e.g., '#000000')." - }, - "glyphColor": { - "type": "string", - "description": "Hex color code for the pin's glyph/icon (e.g., '#000000')." - } - }, - "required": [ - "lat", - "lng", - "name" - ] - } - }, - "path": { - "type": "string" - } - } - } - }, - "required": [ - "center", - "zoom" - ] - } - } -} \ No newline at end of file diff --git a/samples/agent/adk/rizzcharts/tools.py b/samples/agent/adk/rizzcharts/tools.py deleted file mode 100644 index 9acca87a0..000000000 --- a/samples/agent/adk/rizzcharts/tools.py +++ /dev/null @@ -1,107 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import logging -from typing import Any - -logger = logging.getLogger(__name__) - - -def get_store_sales(region: str = "all", **kwargs: Any) -> dict[str, Any]: - """ - Gets individual store sales - - Args: - region: The region to get store sales for. - **kwargs: Additional arguments. - - Returns: - A dict containing the stores with locations and their sales, and with outlier stores highlighted - """ - logger.info("get_store_sales called with region=%s, kwargs=%s", region, kwargs) - - return { - "center": {"lat": 34, "lng": -118.2437}, - "zoom": 10, - "locations": [ - { - "lat": 34.0195, - "lng": -118.4912, - "name": "Santa Monica Branch", - "description": "High traffic coastal location.", - "outlier_reason": "Yes, 15% sales over baseline", - "background": "#4285F4", - "borderColor": "#FFFFFF", - "glyphColor": "#FFFFFF", - }, - {"lat": 34.0488, "lng": -118.2518, "name": "Downtown Flagship"}, - {"lat": 34.1016, "lng": -118.3287, "name": "Hollywood Boulevard Store"}, - {"lat": 34.1478, "lng": -118.1445, "name": "Pasadena Location"}, - {"lat": 33.7701, "lng": -118.1937, "name": "Long Beach Outlet"}, - {"lat": 34.0736, "lng": -118.4004, "name": "Beverly Hills Boutique"}, - ], - } - - -def get_sales_data(time_period: str = "year", **kwargs: Any) -> dict[str, Any]: - """ - Gets the sales data. - - Args: - time_period: The time period to get sales data for (e.g. 'Q1', 'year'). Defaults to 'year'. - **kwargs: Additional arguments. - - Returns: - A dict containing the sales breakdown by product category. - """ - logger.info( - "get_sales_data called with time_period=%s, kwargs=%s", time_period, kwargs - ) - - return { - "sales_data": [ - { - "label": "Apparel", - "value": 41, - "drillDown": [ - {"label": "Tops", "value": 31}, - {"label": "Bottoms", "value": 38}, - {"label": "Outerwear", "value": 20}, - {"label": "Footwear", "value": 11}, - ], - }, - { - "label": "Home Goods", - "value": 15, - "drillDown": [ - {"label": "Pillow", "value": 8}, - {"label": "Coffee Maker", "value": 16}, - {"label": "Area Rug", "value": 3}, - {"label": "Bath Towels", "value": 14}, - ], - }, - { - "label": "Electronics", - "value": 28, - "drillDown": [ - {"label": "Phones", "value": 25}, - {"label": "Laptops", "value": 27}, - {"label": "TVs", "value": 21}, - {"label": "Other", "value": 27}, - ], - }, - {"label": "Health & Beauty", "value": 10}, - {"label": "Other", "value": 6}, - ] - } diff --git a/samples/agent/adk/uv.lock b/samples/agent/adk/uv.lock index 706c32cd9..7b0327872 100644 --- a/samples/agent/adk/uv.lock +++ b/samples/agent/adk/uv.lock @@ -12,7 +12,6 @@ members = [ "a2ui-contact-multiple-surfaces", "a2ui-restaurant-finder", "orchestrator", - "rizzcharts", ] [manifest.dependency-groups] @@ -2498,33 +2497,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ef/45/615f5babd880b4bd7d405cc0dc348234c5ffb6ed1ea33e152ede08b2072d/rich-14.3.2-py3-none-any.whl", hash = "sha256:08e67c3e90884651da3239ea668222d19bea7b589149d8014a21c633420dbb69", size = 309963, upload-time = "2026-02-01T16:20:46.078Z" }, ] -[[package]] -name = "rizzcharts" -version = "0.1.0" -source = { editable = "rizzcharts" } -dependencies = [ - { name = "a2a-sdk" }, - { name = "a2ui-agent" }, - { name = "click" }, - { name = "google-adk" }, - { name = "google-genai" }, - { name = "jsonschema" }, - { name = "litellm" }, - { name = "python-dotenv" }, -] - -[package.metadata] -requires-dist = [ - { name = "a2a-sdk", specifier = ">=0.3.0" }, - { name = "a2ui-agent", editable = "../../../agent_sdks/python" }, - { name = "click", specifier = ">=8.1.8" }, - { name = "google-adk", specifier = ">=1.8.0" }, - { name = "google-genai", specifier = ">=1.27.0" }, - { name = "jsonschema", specifier = ">=4.0.0" }, - { name = "litellm" }, - { name = "python-dotenv", specifier = ">=1.1.0" }, -] - [[package]] name = "rpds-py" version = "0.30.0" diff --git a/samples/client/angular/README.md b/samples/client/angular/README.md index b68a240cd..4cd9d480d 100644 --- a/samples/client/angular/README.md +++ b/samples/client/angular/README.md @@ -7,8 +7,6 @@ These are sample implementations of A2UI in Angular. 1. [nodejs](https://nodejs.org/en) 2. [uv](https://docs.astral.sh/uv/getting-started/installation/) -NOTE: [For the rizzcharts app](../../agent/adk/rizzcharts/), you will need GoogleMap API ([How to get the API key](https://developers.google.com/maps/documentation/javascript/get-api-key)) to display Google Map custome components. Please refer to [Rizzcharts README](./projects/rizzcharts/README.md) - ## Running Here is the quickstart for the restaurant app: @@ -19,23 +17,21 @@ cp ../../agent/adk/restaurant_finder/.env.example ../../agent/adk/restaurant_fin # Edit the .env file with your actual API key (do not commit .env) # Start the restaurant app frontend -npm install -npm run demo:restaurant +npm install +npm run demo:restaurant ``` -Here are the instructions if you want to do each step manually. +Here are the instructions if you want to do each step manually. 1. Build the shared dependencies by running `npm install && npm run build` in the `renderers/lit` directory 2. Install the dependencies: `npm install` 3. Run the relevant A2A server: * [For the restaurant app](../../agent/adk/restaurant_finder/) * [For the contact app](../../agent/adk/contact_lookup/) - * [For the rizzcharts app](../../agent/adk/rizzcharts/) * [For the orchestrator app](../../agent/adk/orchestrator/) 4. Run the relevant app: * `npm start -- restaurant` * `npm start -- contact` - * `npm start -- rizzcharts` * `npm start -- orchestrator` * `npm start -- gallery` (Client-only, no server required) 5. Open http://localhost:4200/ diff --git a/samples/client/angular/angular.json b/samples/client/angular/angular.json index 87be99fb3..bacd7b1cc 100644 --- a/samples/client/angular/angular.json +++ b/samples/client/angular/angular.json @@ -42,7 +42,9 @@ "input": "projects/restaurant/public" } ], - "styles": ["projects/restaurant/src/styles.css"], + "styles": [ + "projects/restaurant/src/styles.css" + ], "server": "projects/restaurant/src/main.server.ts", "outputMode": "server", "ssr": { @@ -98,90 +100,8 @@ "input": "projects/restaurant/public" } ], - "styles": ["projects/restaurant/src/styles.css"] - } - } - } - }, - "rizzcharts": { - "projectType": "application", - "schematics": {}, - "root": "projects/rizzcharts", - "sourceRoot": "projects/rizzcharts/src", - "prefix": "app", - "architect": { - "build": { - "builder": "@angular/build:application", - "options": { - "browser": "projects/rizzcharts/src/main.ts", - "tsConfig": "projects/rizzcharts/tsconfig.app.json", - "preserveSymlinks": true, - "assets": [ - { - "glob": "**/*", - "input": "projects/rizzcharts/public" - } - ], - "styles": [ - "projects/rizzcharts/src/styles.scss" - ], - "server": "projects/rizzcharts/src/main.server.ts", - "outputMode": "server", - "ssr": { - "entry": "projects/rizzcharts/src/server.ts" - } - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "1MB", - "maximumError": "2MB" - }, - { - "type": "anyComponentStyle", - "maximumWarning": "4kB", - "maximumError": "8kB" - } - ], - "outputHashing": "all" - }, - "development": { - "optimization": false, - "extractLicenses": false, - "sourceMap": true - } - }, - "defaultConfiguration": "production" - }, - "serve": { - "builder": "@angular/build:dev-server", - "configurations": { - "production": { - "buildTarget": "rizzcharts:build:production" - }, - "development": { - "buildTarget": "rizzcharts:build:development" - } - }, - "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular/build:extract-i18n" - }, - "test": { - "builder": "@angular/build:karma", - "options": { - "tsConfig": "projects/rizzcharts/tsconfig.spec.json", - "assets": [ - { - "glob": "**/*", - "input": "projects/rizzcharts/public" - } - ], "styles": [ - "projects/rizzcharts/src/styles.css" + "projects/restaurant/src/styles.css" ] } } @@ -273,7 +193,7 @@ }, "gallery": { "projectType": "application", - "schematics": { }, + "schematics": {}, "root": "projects/gallery", "sourceRoot": "projects/gallery/src", "prefix": "app", @@ -371,7 +291,10 @@ "test": { "builder": "@angular/build:karma", "options": { - "polyfills": ["zone.js", "zone.js/testing"], + "polyfills": [ + "zone.js", + "zone.js/testing" + ], "tsConfig": "projects/a2a-chat-canvas/tsconfig.spec.json", "assets": [ { diff --git a/samples/client/angular/package.json b/samples/client/angular/package.json index 8caa9f3e1..6c0909bfc 100644 --- a/samples/client/angular/package.json +++ b/samples/client/angular/package.json @@ -9,7 +9,6 @@ "test": "ng test", "serve:ssr:angular": "node dist/angular/server/server.mjs", "serve:ssr:restaurant": "node dist/restaurant/server/server.mjs", - "serve:ssr:rizzcharts": "node dist/rizzcharts/server/server.mjs", "serve:ssr:contact": "node dist/contact/server/server.mjs", "build:renderer": "cd ../../../renderers && for dir in 'web_core' 'markdown/markdown-it'; do (cd \"$dir\" && npm install && npm run build); done", "serve:agent:restaurant": "cd ../../agent/adk/restaurant_finder && uv run .", diff --git a/samples/client/angular/projects/rizzcharts/README.md b/samples/client/angular/projects/rizzcharts/README.md deleted file mode 100644 index 10fbbd794..000000000 --- a/samples/client/angular/projects/rizzcharts/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# Angular A2UI - RIZZ Charts - -These are sample implementations of A2UI in Angular. - -## Prerequisites - -1. [nodejs](https://nodejs.org/en) -2. GoogleMap API ([How to get the API key](https://developers.google.com/maps/documentation/javascript/get-api-key)) - -## Running - -1. Update the `src/environments/environment.ts` file with your Google Maps API key. -2. Build the shared dependencies by running `npm i`, then `npm run build` in the `renderers/web_core` directory -3. Install the dependencies: `npm i` -4. Run the A2A server for the [rizzcharts agent](../../../../agent/adk/rizzcharts/) -5. Run the relevant app: - * `npm start -- rizzcharts` -6. Open http://localhost:4200/ \ No newline at end of file diff --git a/samples/client/angular/projects/rizzcharts/public/Gradient.png b/samples/client/angular/projects/rizzcharts/public/Gradient.png deleted file mode 100644 index fc0d5965e..000000000 Binary files a/samples/client/angular/projects/rizzcharts/public/Gradient.png and /dev/null differ diff --git a/samples/client/angular/projects/rizzcharts/public/avatar.jpg b/samples/client/angular/projects/rizzcharts/public/avatar.jpg deleted file mode 100644 index 05ff89197..000000000 Binary files a/samples/client/angular/projects/rizzcharts/public/avatar.jpg and /dev/null differ diff --git a/samples/client/angular/projects/rizzcharts/public/favicon.ico b/samples/client/angular/projects/rizzcharts/public/favicon.ico deleted file mode 100644 index 57614f9c9..000000000 Binary files a/samples/client/angular/projects/rizzcharts/public/favicon.ico and /dev/null differ diff --git a/samples/client/angular/projects/rizzcharts/public/logo-dark.png b/samples/client/angular/projects/rizzcharts/public/logo-dark.png deleted file mode 100644 index 77f58dfc0..000000000 Binary files a/samples/client/angular/projects/rizzcharts/public/logo-dark.png and /dev/null differ diff --git a/samples/client/angular/projects/rizzcharts/public/logo-light.png b/samples/client/angular/projects/rizzcharts/public/logo-light.png deleted file mode 100644 index c66f4f86d..000000000 Binary files a/samples/client/angular/projects/rizzcharts/public/logo-light.png and /dev/null differ diff --git a/samples/client/angular/projects/rizzcharts/public/rizz-agent.png b/samples/client/angular/projects/rizzcharts/public/rizz-agent.png deleted file mode 100644 index 7ce238c94..000000000 Binary files a/samples/client/angular/projects/rizzcharts/public/rizz-agent.png and /dev/null differ diff --git a/samples/client/angular/projects/rizzcharts/src/a2ui-catalog/canvas.ts b/samples/client/angular/projects/rizzcharts/src/a2ui-catalog/canvas.ts deleted file mode 100644 index 6c126fe02..000000000 --- a/samples/client/angular/projects/rizzcharts/src/a2ui-catalog/canvas.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* - Copyright 2025 Google LLC - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import { DynamicComponent } from '@a2ui/angular'; -import * as Types from '@a2ui/web_core/types/types'; -import { ChangeDetectionStrategy, Component, computed, inject, OnInit } from '@angular/core'; -import { CanvasService } from '@a2a_chat_canvas/services/canvas-service'; - -@Component({ - selector: 'a2ui-canvas', - changeDetection: ChangeDetectionStrategy.Eager, - styles: ` - :host { - display: block; - flex: var(--weight); - min-height: 0; - overflow: auto; - } - - section { - display: flex; - justify-content: space-between; - flex-direction: row; - } - `, - template: `
`, -}) -export class Canvas extends DynamicComponent implements OnInit { - private readonly canvasService = inject(CanvasService); - - readonly isCanvasOpened = computed(() => this.canvasService.surfaceId() === this.surfaceId()); - - ngOnInit(): void { - this.openCanvas(); - } - - protected closeCanvas() { - this.canvasService.surfaceId.set(null); - } - - protected openCanvas() { - this.canvasService.openSurfaceInCanvas( - this.surfaceId()!, - this.component().properties['children'] as Types.AnyComponentNode[], - ); - } -} diff --git a/samples/client/angular/projects/rizzcharts/src/a2ui-catalog/catalog.ts b/samples/client/angular/projects/rizzcharts/src/a2ui-catalog/catalog.ts deleted file mode 100644 index e37fda05b..000000000 --- a/samples/client/angular/projects/rizzcharts/src/a2ui-catalog/catalog.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - Copyright 2025 Google LLC - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import { Catalog, DEFAULT_CATALOG } from '@a2ui/angular'; -import { inputBinding } from '@angular/core'; - -export const RIZZ_CHARTS_CATALOG = { - ...DEFAULT_CATALOG, - Canvas: () => import('./canvas').then((r) => r.Canvas), - Chart: { - type: () => import('./chart').then((r) => r.Chart), - bindings: ({ properties }) => [ - inputBinding('type', () => ('type' in properties && properties['type']) || undefined), - inputBinding('title', () => ('title' in properties && properties['title']) || undefined), - inputBinding( - 'chartData', - () => ('chartData' in properties && properties['chartData']) || undefined, - ), - ], - }, - GoogleMap: { - type: () => import('./google-map').then((r) => r.GoogleMap), - bindings: ({ properties }) => [ - inputBinding('zoom', () => ('zoom' in properties && properties['zoom']) || 8), - inputBinding('center', () => ('center' in properties && properties['center']) || undefined), - inputBinding('pins', () => ('pins' in properties && properties['pins']) || undefined), - inputBinding('title', () => ('title' in properties && properties['title']) || undefined), - ], - }, -} as Catalog; diff --git a/samples/client/angular/projects/rizzcharts/src/a2ui-catalog/chart.ts b/samples/client/angular/projects/rizzcharts/src/a2ui-catalog/chart.ts deleted file mode 100644 index ecc6cc12e..000000000 --- a/samples/client/angular/projects/rizzcharts/src/a2ui-catalog/chart.ts +++ /dev/null @@ -1,282 +0,0 @@ -/* - Copyright 2025 Google LLC - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import { DynamicComponent } from '@a2ui/angular'; -import * as Primitives from '@a2ui/web_core/types/primitives'; -import * as Types from '@a2ui/web_core/types/types'; -import { - ChangeDetectionStrategy, - Component, - computed, - input, - Signal, - signal, - ViewChild, -} from '@angular/core'; -import { MatIconButton } from '@angular/material/button'; -import { MatIcon } from '@angular/material/icon'; -import { ChartData, ChartEvent, ChartOptions, ChartType, LegendItem } from 'chart.js'; -import { BaseChartDirective } from 'ng2-charts'; - -@Component({ - selector: 'a2ui-chart', - imports: [BaseChartDirective, MatIconButton, MatIcon], - changeDetection: ChangeDetectionStrategy.Eager, - styles: ` - :host { - display: block; - flex: var(--weight); - padding: 20px; - } - - .chart-box-container { - background-color: var(--mat-sys-surface-container); /* Dark background for the box */ - border-radius: 8px; - border: 1px solid #444; /* Subtle border for dark theme */ - padding: 20px; - margin: 20px auto; /* Center the box */ - max-width: 800px; /* Limit width for better appearance */ - } - - /* Combined header for title and icon */ - .chart-header { - display: flex; - justify-content: space-between; /* Pushes title to left, icon to right */ - align-items: start; /* Vertically centers title and icon */ - margin-bottom: 15px; /* Space below the header */ - } - - .chart-header h2 { - margin: 0; /* Remove default margin from h2 */ - font-size: 24px; - color: var(--mat-sys-on-surface-container); /* Light text for title */ - } - - .header-icon { - cursor: pointer; - line-height: 0; /* Helps with vertical alignment of SVG */ - } - - .chart-container { - width: 100%; - display: flex; - flex-direction: column; - font-family: Arial, sans-serif; - color: #ccc; /* Light text for dark theme */ - } - - .chart-container p { - margin-bottom: 10px; - color: #ccc; /* Light text for dark theme */ - } - `, - template: ` -
- -
-
-

{{ resolvedTitle() }}

- @if (isDrillDown()) { -

{{ selectedCategory() }}

- } -
- -
- - -
-
-
- @if (isDrillDown()) { - - } - - -
-
- `, -}) -export class Chart extends DynamicComponent { - readonly type = input.required(); - protected readonly chartType = computed(() => this.type() as ChartType); - - readonly title = input(); - protected readonly resolvedTitle: Signal = computed(() => - super.resolvePrimitive(this.title() ?? null), - ); - - readonly chartData = input.required(); - protected readonly resolvedPieChartData: Signal< - Map> | undefined - > = computed(() => { - const chartDataPathPrefix = this.chartData(); - const chartType = this.chartType(); - if (chartDataPathPrefix === null) { - return undefined; - } - if (chartType === 'pie' || chartType === 'doughnut') { - return this.resolvePieChartData(chartDataPathPrefix); - } - console.error('Unsupported chart type specified:', chartType); - return undefined; - }); - - protected readonly selectedCategory = signal('root'); - protected readonly isDrillDown = computed(() => this.selectedCategory() !== 'root'); - protected readonly currentData: Signal | undefined> = computed( - () => { - const selectedCategory: string = this.selectedCategory(); - const allData = this.resolvedPieChartData(); - if (!allData) { - return undefined; - } - return { ...allData.get(selectedCategory) } as ChartData<'pie', number[], string>; - }, - ); - - @ViewChild(BaseChartDirective) chart?: BaseChartDirective; - protected chartOptions: ChartOptions = { - responsive: true, - plugins: { - legend: { - display: true, - position: 'right', - labels: { - color: '#166a8f', - font: { - size: 14, - }, - }, - onClick: (e: ChartEvent, legendItem: LegendItem) => { - this.updateChartWithCategory(legendItem.text); - }, - }, - datalabels: { - formatter: (value: number, ctx: any) => { - const total = (ctx.chart.data.datasets[0].data as number[]).reduce((a, b) => a + b, 0); - const percentage = Math.round((value / total) * 100); - return `${percentage.toFixed(1)}%`; - }, - color: 'white', - font: { - size: 16, - }, - }, - }, - }; - - private resolvePieChartData( - pathPrefix: Primitives.StringValue, - ): Map> | undefined { - const dataMap = new Map>(); - const labels: string[] = []; - const data: number[] = []; - if (pathPrefix?.path) { - for (let index: number = 0; index < 500; index++) { - const itemPrefix = `${pathPrefix.path}[${index}]`; - const labelPath: Primitives.StringValue = { path: `${itemPrefix}.label` }; - const valuePath: Primitives.NumberValue = { path: `${itemPrefix}.value` }; - const label = super.resolvePrimitive(labelPath); - const value = super.resolvePrimitive(valuePath); - if (label === null || value === null) { - break; - } - labels.push(label); - data.push(value); - - const drilldownLabels: string[] = []; - const drilldownData: number[] = []; - const drilldownPathPrefix = `${itemPrefix}.drillDown`; - for (let jindex: number = 0; jindex < 500; jindex++) { - const drilldownItemPrefix = `${drilldownPathPrefix}[${jindex}]`; - const drilldownLabelPath: Primitives.StringValue = { - path: `${drilldownItemPrefix}.label`, - }; - const drilldownValuePath: Primitives.NumberValue = { - path: `${drilldownItemPrefix}.value`, - }; - const drilldownLabel = super.resolvePrimitive(drilldownLabelPath); - const drilldownValue = super.resolvePrimitive(drilldownValuePath); - if (drilldownLabel === null || drilldownValue === null) { - break; - } - drilldownLabels.push(drilldownLabel); - drilldownData.push(drilldownValue); - } - - const drilldownChartData: ChartData<'pie', number[], string> = { - labels: drilldownLabels, - datasets: [ - { - data: drilldownData, - }, - ], - }; - dataMap.set(label, drilldownChartData); - } - } - - const rootData: ChartData<'pie', number[], string> = { - labels, - datasets: [ - { - data, - }, - ], - }; - dataMap.set('root', rootData); - return dataMap; - } - - public restoreOriginalView() { - this.selectedCategory.set('root'); - } - - protected onClick(e: { event?: ChartEvent; active?: any[] | undefined }) { - const active = e.active; - if (!active || active.length === 0) return; - - // active[0] for pie chart contains the data index that was clicked - // @ts-ignore -- ActiveElement typing can vary between versions - const dataIndex: number | undefined = (active[0] as any).index; - const labels = [...(this.currentData()?.labels ?? [])]; - const label = labels && typeof dataIndex === 'number' ? labels[dataIndex] : undefined; - if (label) { - this.updateChartWithCategory(label); - } - } - - private updateChartWithCategory(label: string) { - const currentCategory = this.selectedCategory(); - if (currentCategory !== 'root') { - console.error('Cannot drilldown further'); - return; - } - this.selectedCategory.set(label); - } -} diff --git a/samples/client/angular/projects/rizzcharts/src/a2ui-catalog/google-map.ts b/samples/client/angular/projects/rizzcharts/src/a2ui-catalog/google-map.ts deleted file mode 100644 index 883af7683..000000000 --- a/samples/client/angular/projects/rizzcharts/src/a2ui-catalog/google-map.ts +++ /dev/null @@ -1,232 +0,0 @@ -/* - Copyright 2025 Google LLC - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import { DynamicComponent } from '@a2ui/angular'; -import * as Primitives from '@a2ui/web_core/types/primitives'; -import * as Types from '@a2ui/web_core/types/types'; -import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core'; -import { GoogleMapsModule } from '@angular/google-maps'; -import { MatIconButton } from '@angular/material/button'; -import { MatIcon } from '@angular/material/icon'; - -// --- Location Definitions --- -interface Pin { - lat: number; - lng: number; - name: string; - description: string | null; - pinElement: google.maps.marker.PinElement; -} - -export interface CustomProperties { - path: string; -} - -@Component({ - selector: 'a2ui-map', - imports: [GoogleMapsModule, MatIconButton, MatIcon], - changeDetection: ChangeDetectionStrategy.Eager, - styles: ` - :host { - display: block; - flex: var(--weight); - padding: 20px; - } - - .map-box-container { - background-color: var(--mat-sys-surface-container); /* Dark background for the box */ - border-radius: 8px; - border: 1px solid var(--mat-sys-surface-container-high); /* Subtle border for dark theme */ - padding: 20px; - margin: 20px auto; /* Center the box */ - max-width: 800px; /* Limit width for better appearance */ - } - - /* Combined header for title and icon */ - .map-header { - display: flex; - justify-content: space-between; /* Pushes title to left, icon to right */ - align-items: center; /* Vertically centers title and icon */ - margin-bottom: 15px; /* Space below the header */ - } - - .map-header h2 { - margin: 0; /* Remove default margin from h2 */ - font-size: 24px; - color: var(--mat-sys-on-surface-container); /* Light text for title */ - } - - .header-icon { - cursor: pointer; - line-height: 0; /* Helps with vertical alignment of SVG */ - } - - .map-container { - width: 100%; - display: flex; - flex-direction: column; - font-family: Arial, sans-serif; - color: #ccc; /* Light text for dark theme */ - } - - .map-container p { - margin-bottom: 10px; - color: #ccc; /* Light text for dark theme */ - } - - google-map { - border: 1px solid #555; /* Dark theme border around the map */ - border-radius: 4px; - overflow: hidden; /* Ensures border-radius applies to map content */ - } - `, - template: ` - @let resolvedZoom = this.resolvedZoom(); - - @if (resolvedZoom) { -
- -
-

{{ resolvedTitle() }}

-
- - -
-
- -
- - @for (pin of resolvedPins(); track pin) { - - - } - -
-
- } - `, -}) -export class GoogleMap extends DynamicComponent { - private readonly maxPinCount = 100; - private readonly defaultCenter: google.maps.LatLngLiteral = { - lat: 34.0626, - lng: -118.3759, - }; - - mapId = '4506f1f5f5e6e8e2'; - - readonly title = input(); - protected resolvedTitle = computed(() => super.resolvePrimitive(this.title() ?? null)); - - readonly zoom = input.required(); - protected resolvedZoom = computed(() => super.resolvePrimitive(this.zoom())); - - readonly center = input.required(); - protected resolvedCenter = computed(() => this.resolveLatLng(this.center())); - - readonly pins = input(); - protected readonly resolvedPins = computed(() => this.resolveLocations(this.pins())); - - constructor() { - super(); - } - - private resolveLocations(value: CustomProperties | undefined): Pin[] { - const locations: Pin[] = []; - if (value?.path) { - for (let index: number = 0; index < this.maxPinCount; index++) { - const locationPath = `${value.path}[${index}]`; - const pin = this.resolveLocation(locationPath); - // Stop iterating if no more locations can be found at the `locationPath`. - if (!pin) { - break; - } - locations.push(pin); - } - } - return locations; - } - - private resolveLocation(value: string | null): Pin | null { - if (!value) { - return null; - } - - const latValue: Primitives.NumberValue = { path: `${value}.lat` }; - const lngValue: Primitives.NumberValue = { path: `${value}.lng` }; - const nameValue: Primitives.StringValue = { path: `${value}.name` }; - const descriptionValue: Primitives.StringValue = { path: `${value}.description` }; - const backgroundValue: Primitives.StringValue = { path: `${value}.background` }; - const borderColorValue: Primitives.StringValue = { path: `${value}.borderColor` }; - const glyphColorValue: Primitives.StringValue = { path: `${value}.glyphColor` }; - - const lat = this.resolvePrimitive(latValue); - const lng = this.resolvePrimitive(lngValue); - const name = this.resolvePrimitive(nameValue); - const description = this.resolvePrimitive(descriptionValue); - const background = this.resolvePrimitive(backgroundValue); - const borderColor = this.resolvePrimitive(borderColorValue); - const glyphColor = this.resolvePrimitive(glyphColorValue); - - // TODO: This logic should be implemented in the `guard.ts` by making the data model typed upstream. - if (lat === null || lng === null || name === null) { - // The location is invalid. - return null; - } - - return { - lat, - lng, - name, - // TODO: Description is currently not used in the Maps. - description, - pinElement: new google.maps.marker.PinElement({ - background, - borderColor, - glyphColor, - }), - }; - } - - private resolveLatLng(value: CustomProperties | null): google.maps.LatLngLiteral { - if (value?.path) { - const latValue: Primitives.NumberValue = { path: `${value.path}.lat` }; - const lngValue: Primitives.NumberValue = { path: `${value.path}.lng` }; - const lat = this.resolvePrimitive(latValue)!; - const lng = this.resolvePrimitive(lngValue)!; - return { - lat, - lng, - }; - } - - return this.defaultCenter; - } -} diff --git a/samples/client/angular/projects/rizzcharts/src/app/app.config.server.ts b/samples/client/angular/projects/rizzcharts/src/app/app.config.server.ts deleted file mode 100644 index 40260ea38..000000000 --- a/samples/client/angular/projects/rizzcharts/src/app/app.config.server.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - Copyright 2025 Google LLC - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import { mergeApplicationConfig, ApplicationConfig } from '@angular/core'; -import { provideServerRendering, withRoutes } from '@angular/ssr'; -import { appConfig } from './app.config'; -import { serverRoutes } from './app.routes.server'; - -const serverConfig: ApplicationConfig = { - providers: [ - provideServerRendering(withRoutes(serverRoutes)) - ] -}; - -export const config = mergeApplicationConfig(appConfig, serverConfig); diff --git a/samples/client/angular/projects/rizzcharts/src/app/app.config.ts b/samples/client/angular/projects/rizzcharts/src/app/app.config.ts deleted file mode 100644 index 5e1c43918..000000000 --- a/samples/client/angular/projects/rizzcharts/src/app/app.config.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - Copyright 2025 Google LLC - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import { - configureChatCanvasFeatures, - usingA2aService, - usingA2uiRenderers, - usingMarkdownRenderer, -} from '@a2a_chat_canvas/config'; -import { - ApplicationConfig, - provideBrowserGlobalErrorListeners, - provideZonelessChangeDetection, -} from '@angular/core'; -import { provideRouter } from '@angular/router'; -import { RIZZ_CHARTS_CATALOG } from '@rizzcharts/a2ui-catalog/catalog'; -import { provideCharts, withDefaultRegisterables } from 'ng2-charts'; -import { A2aService } from '../services/a2a_service'; -import { RizzchartsMarkdownRendererService } from '../services/markdown-renderer.service'; -import { theme } from './theme'; - -import { provideClientHydration, withEventReplay } from '@angular/platform-browser'; -import { routes } from './app.routes'; - -export const appConfig: ApplicationConfig = { - providers: [ - provideBrowserGlobalErrorListeners(), - provideZonelessChangeDetection(), - provideRouter(routes), - provideClientHydration(withEventReplay()), - provideCharts(withDefaultRegisterables()), - configureChatCanvasFeatures( - usingA2aService(A2aService), - usingA2uiRenderers(RIZZ_CHARTS_CATALOG, theme), - usingMarkdownRenderer(RizzchartsMarkdownRendererService), - ), - ], -}; diff --git a/samples/client/angular/projects/rizzcharts/src/app/app.html b/samples/client/angular/projects/rizzcharts/src/app/app.html deleted file mode 100644 index 0a6c46e98..000000000 --- a/samples/client/angular/projects/rizzcharts/src/app/app.html +++ /dev/null @@ -1,55 +0,0 @@ - - -
- - - -
-
-
-
- Rizz agent icon -
-
-
- {{ agentName() }} -
-
-
- -

- I help you understand and visualize sales pipelines and analyze customer performance data. -

-
- - - -
-
-
-
- - diff --git a/samples/client/angular/projects/rizzcharts/src/app/app.routes.server.ts b/samples/client/angular/projects/rizzcharts/src/app/app.routes.server.ts deleted file mode 100644 index 2a69106a0..000000000 --- a/samples/client/angular/projects/rizzcharts/src/app/app.routes.server.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - Copyright 2025 Google LLC - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import { RenderMode, ServerRoute } from '@angular/ssr'; - -export const serverRoutes: ServerRoute[] = [ - { - path: '**', - renderMode: RenderMode.Prerender - } -]; diff --git a/samples/client/angular/projects/rizzcharts/src/app/app.routes.ts b/samples/client/angular/projects/rizzcharts/src/app/app.routes.ts deleted file mode 100644 index 028cd9e4f..000000000 --- a/samples/client/angular/projects/rizzcharts/src/app/app.routes.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - Copyright 2025 Google LLC - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import { Routes } from '@angular/router'; - -export const routes: Routes = []; diff --git a/samples/client/angular/projects/rizzcharts/src/app/app.scss b/samples/client/angular/projects/rizzcharts/src/app/app.scss deleted file mode 100644 index c4c12cf5c..000000000 --- a/samples/client/angular/projects/rizzcharts/src/app/app.scss +++ /dev/null @@ -1,109 +0,0 @@ -/* - Copyright 2025 Google LLC - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -main { - height: calc(100vh - 64px); -} - -.empty-history { - display: block; - padding-bottom: 8rem; - background-image: url("/Gradient.png"); - background-size: contain; - background-repeat: no-repeat; - background-position-y: bottom; - background-position-x: right; -} - -.empty-history-text { - margin: 0; - font: var(--mat-sys-display-small); - // These values are not from the spec, but UX wants them for the gradient. - background: linear-gradient( - 90deg, - #217bfe 28.03%, - #078efb 49.56%, - #ac87eb 71.1% - ); - background-clip: text; - color: transparent; -} - -.chat-history-items { - width: 100%; - height: 100%; - box-sizing: border-box; -} - -.chat-history-message-container { - width: 100%; - margin-block-end: 1rem; -} - -.turn-container { - width: 100%; - box-sizing: border-box; - - &:last-of-type { - // Using min-height ensures that the size is 100% plus the padding. - min-height: 100%; - padding-block-end: 56px; - } -} - -.large-icon { - width: 32px; - height: 32px; - font-size: 32px; - border-radius: var(--mat-sys-corner-extra-large); - vertical-align: top; -} - - -.agent-header { - display: flex; - align-items: center; - flex-flow: row wrap; -} -.agent-header-part { - margin-inline-end: 1.25rem; - margin-block: 8px; -} - -.suggestion-chips { - display: flex; - flex-wrap: wrap; - gap: 8px; - justify-content: center; - margin-top: 32px; -} - -.chip { - // Override Material button styles to look like a chip - border-radius: 100px !important; // Force pill shape - padding: 10px 16px !important; - display: inline-flex !important; - align-items: center !important; - height: auto !important; - line-height: 25px !important; // Match icon height - .material-icons-outlined { - font-size: 20px; - margin-right: 8px; - line-height: 1; // Prevent icon from affecting line height - position: relative; - top: 4px; // Move icon down slightly to match text - } -} diff --git a/samples/client/angular/projects/rizzcharts/src/app/app.spec.ts b/samples/client/angular/projects/rizzcharts/src/app/app.spec.ts deleted file mode 100644 index b5276721f..000000000 --- a/samples/client/angular/projects/rizzcharts/src/app/app.spec.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - Copyright 2025 Google LLC - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import { provideZonelessChangeDetection } from '@angular/core'; -import { TestBed } from '@angular/core/testing'; -import { App } from './app'; - -describe('App', () => { - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [App], - providers: [provideZonelessChangeDetection()], - }).compileComponents(); - }); - - it('should create the app', () => { - const fixture = TestBed.createComponent(App); - const app = fixture.componentInstance; - expect(app).toBeTruthy(); - }); - - it('should render title', () => { - const fixture = TestBed.createComponent(App); - fixture.detectChanges(); - const compiled = fixture.nativeElement as HTMLElement; - expect(compiled.querySelector('h1')?.textContent).toContain('Hello, chat_canvas'); - }); -}); diff --git a/samples/client/angular/projects/rizzcharts/src/app/app.ts b/samples/client/angular/projects/rizzcharts/src/app/app.ts deleted file mode 100644 index 4eece1741..000000000 --- a/samples/client/angular/projects/rizzcharts/src/app/app.ts +++ /dev/null @@ -1,66 +0,0 @@ -/* - Copyright 2025 Google LLC - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import { - ChangeDetectionStrategy, - Component, - Inject, - OnInit, - Renderer2, - inject, - signal, -} from '@angular/core'; -import { DOCUMENT } from '@angular/common'; -import { RouterOutlet } from '@angular/router'; -import { MatButtonModule } from '@angular/material/button'; -import { A2aChatCanvas } from '@a2a_chat_canvas/a2a-chat-canvas'; -import { ChatService } from '@a2a_chat_canvas/services/chat-service'; -import { Toolbar } from '@rizzcharts/components/toolbar/toolbar'; -import { environment } from '@rizzcharts/environments/environment'; -import { A2aService } from '@rizzcharts/services/a2a_service'; - -@Component({ - selector: 'app-root', - imports: [A2aChatCanvas, RouterOutlet, Toolbar, MatButtonModule], - templateUrl: './app.html', - styleUrl: './app.scss', - changeDetection: ChangeDetectionStrategy.Eager, -}) -export class App implements OnInit { - protected readonly agentName = signal(''); - readonly chatService = inject(ChatService); - private readonly a2aService = inject(A2aService); - - constructor( - private _renderer2: Renderer2, - @Inject(DOCUMENT) private _document: Document, - ) {} - - ngOnInit() { - const script = this._renderer2.createElement('script'); - script.src = `https://maps.googleapis.com/maps/api/js?key=${environment.googleMapsApiKey}&callback=initMap&libraries=marker`; - script.async = true; - script.defer = true; - this._renderer2.appendChild(this._document.body, script); - this.a2aService.getAgentCard().then((card) => { - this.agentName.set(card.name); - }); - } - - sendMessage(text: string) { - this.chatService.sendMessage(text); - } -} diff --git a/samples/client/angular/projects/rizzcharts/src/app/theme.ts b/samples/client/angular/projects/rizzcharts/src/app/theme.ts deleted file mode 100644 index 6f5239be1..000000000 --- a/samples/client/angular/projects/rizzcharts/src/app/theme.ts +++ /dev/null @@ -1,375 +0,0 @@ -/* - Copyright 2025 Google LLC - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import * as Styles from '@a2ui/web_core/styles/index'; -import * as Types from '@a2ui/web_core/types/types'; - -/** Elements */ - -const a = { - 'typography-f-sf': true, - 'typography-fs-n': true, - 'typography-w-500': true, - 'layout-as-n': true, - 'layout-dis-iflx': true, - 'layout-al-c': true, -}; - -const audio = { - 'layout-w-100': true, -}; - -const body = { - 'typography-f-s': true, - 'typography-fs-n': true, - 'typography-w-400': true, - 'layout-mt-0': true, - 'layout-mb-2': true, - 'typography-sz-bm': true, - 'color-c-n10': true, -}; - -const button = { - 'typography-f-sf': true, - 'typography-fs-n': true, - 'typography-w-500': true, - 'layout-pt-3': true, - 'layout-pb-3': true, - 'layout-pl-5': true, - 'layout-pr-5': true, - 'layout-mb-1': true, - 'border-br-16': true, - 'border-bw-0': true, - 'border-c-n70': true, - 'border-bs-s': true, - 'color-bgc-s30': true, - 'color-c-n100': true, - 'behavior-ho-80': true, -}; - -const heading = { - 'typography-f-sf': true, - 'typography-fs-n': true, - 'typography-w-500': true, - 'layout-mt-0': true, - 'layout-mb-2': true, - 'color-c-n10': true, -}; - -const h1 = { - ...heading, - 'typography-sz-tl': true, -}; - -const h2 = { - ...heading, - 'typography-sz-tm': true, -}; - -const h3 = { - ...heading, - 'typography-sz-ts': true, -}; - -const iframe = { - 'behavior-sw-n': true, -}; - -const input = { - 'typography-f-sf': true, - 'typography-fs-n': true, - 'typography-w-400': true, - 'layout-pl-4': true, - 'layout-pr-4': true, - 'layout-pt-2': true, - 'layout-pb-2': true, - 'border-br-6': true, - 'border-bw-1': true, - 'color-bc-s70': true, - 'border-bs-s': true, - 'layout-as-n': true, - 'color-c-n10': true, -}; - -const p = { - 'typography-f-s': true, - 'typography-fs-n': true, - 'typography-w-400': true, - 'layout-m-0': true, - 'typography-sz-bm': true, - 'layout-as-n': true, - 'color-c-n10': true, -}; - -const orderedList = { - 'typography-f-s': true, - 'typography-fs-n': true, - 'typography-w-400': true, - 'layout-m-0': true, - 'typography-sz-bm': true, - 'layout-as-n': true, -}; - -const unorderedList = { - 'typography-f-s': true, - 'typography-fs-n': true, - 'typography-w-400': true, - 'layout-m-0': true, - 'typography-sz-bm': true, - 'layout-as-n': true, -}; - -const listItem = { - 'typography-f-s': true, - 'typography-fs-n': true, - 'typography-w-400': true, - 'layout-m-0': true, - 'typography-sz-bm': true, - 'layout-as-n': true, -}; - -const pre = { - 'typography-f-c': true, - 'typography-fs-n': true, - 'typography-w-400': true, - 'typography-sz-bm': true, - 'typography-ws-p': true, - 'layout-as-n': true, -}; - -const textarea = { - ...input, - 'layout-r-none': true, - 'layout-fs-c': true, -}; - -const video = { - 'layout-el-cv': true, -}; - -const aLight = Styles.merge(a, { 'color-c-n5': true }); -const inputLight = Styles.merge(input, { 'color-c-n5': true }); -const textareaLight = Styles.merge(textarea, { 'color-c-n5': true }); -const buttonLight = Styles.merge(button, { 'color-c-n100': true }); -const h1Light = Styles.merge(h1, { 'color-c-n5': true }); -const h2Light = Styles.merge(h2, { 'color-c-n5': true }); -const h3Light = Styles.merge(h3, { 'color-c-n5': true }); -const bodyLight = Styles.merge(body, { 'color-c-n5': true }); -const pLight = Styles.merge(p, { 'color-c-n35': true }); -const preLight = Styles.merge(pre, { 'color-c-n35': true }); -const orderedListLight = Styles.merge(orderedList, { - 'color-c-n35': true, -}); -const unorderedListLight = Styles.merge(unorderedList, { - 'color-c-n35': true, -}); -const listItemLight = Styles.merge(listItem, { - 'color-c-n35': true, -}); - -export const theme: Types.Theme = { - additionalStyles: { - Button: { - '--n-35': 'var(--n-100)', - }, - }, - components: { - AudioPlayer: {}, - Button: { - 'layout-pt-2': true, - 'layout-pb-2': true, - 'layout-pl-3': true, - 'layout-pr-3': true, - 'border-br-12': true, - 'border-bw-0': true, - 'border-bs-s': true, - 'color-bgc-p30': true, - 'color-c-n100': true, - 'behavior-ho-70': true, - }, - Card: { 'border-br-9': true, 'color-bgc-p100': true, 'layout-p-4': true }, - CheckBox: { - element: { - 'layout-m-0': true, - 'layout-mr-2': true, - 'layout-p-2': true, - 'border-br-12': true, - 'border-bw-1': true, - 'border-bs-s': true, - 'color-bgc-p100': true, - 'color-bc-p60': true, - 'color-c-n30': true, - 'color-c-p30': true, - }, - label: { - 'color-c-p30': true, - 'typography-f-sf': true, - 'typography-v-r': true, - 'typography-w-400': true, - 'layout-flx-1': true, - 'typography-sz-ll': true, - }, - container: { - 'layout-dsp-iflex': true, - 'layout-al-c': true, - }, - }, - Column: { - 'layout-g-2': true, - }, - DateTimeInput: { - container: {}, - label: {}, - element: { - 'layout-pt-2': true, - 'layout-pb-2': true, - 'layout-pl-3': true, - 'layout-pr-3': true, - 'border-br-12': true, - 'border-bw-1': true, - 'border-bs-s': true, - 'color-bgc-p100': true, - 'color-bc-p60': true, - 'color-c-n30': true, - 'color-c-p30': true, - }, - }, - Divider: {}, - Image: { - all: { - 'border-br-5': true, - 'layout-el-cv': true, - 'layout-w-100': true, - 'layout-h-100': true, - }, - avatar: {}, - header: {}, - icon: {}, - largeFeature: {}, - mediumFeature: {}, - smallFeature: {}, - }, - Icon: {}, - List: { - 'layout-g-4': true, - 'layout-p-2': true, - }, - Modal: { - backdrop: { 'color-bbgc-p60_20': true }, - element: { - 'border-br-2': true, - 'color-bgc-p100': true, - 'layout-p-4': true, - 'border-bw-1': true, - 'border-bs-s': true, - 'color-bc-p80': true, - }, - }, - MultipleChoice: { - container: {}, - label: {}, - element: {}, - }, - Row: { - 'layout-g-4': true, - }, - Slider: { - container: {}, - label: {}, - element: {}, - }, - Tabs: { - container: {}, - controls: { all: {}, selected: {} }, - element: {}, - }, - Text: { - all: { - 'layout-w-100': true, - 'layout-g-2': true, - 'color-c-p30': true, - }, - h1: {}, - h2: {}, - h3: {}, - h4: {}, - h5: {}, - caption: {}, - body: {} - }, - TextField: { - container: { - 'typography-sz-bm': true, - 'layout-w-100': true, - 'layout-g-2': true, - 'layout-dsp-flexhor': true, - 'layout-al-c': true, - }, - label: { - 'layout-flx-0': true, - }, - element: { - 'typography-sz-bm': true, - 'layout-pt-2': true, - 'layout-pb-2': true, - 'layout-pl-3': true, - 'layout-pr-3': true, - 'border-br-12': true, - 'border-bw-1': true, - 'border-bs-s': true, - 'color-bgc-p100': true, - 'color-bc-p60': true, - 'color-c-n30': true, - 'color-c-p30': true, - }, - }, - Video: { - 'border-br-5': true, - 'layout-el-cv': true, - }, - }, - elements: { - a: aLight, - audio, - body: bodyLight, - button: buttonLight, - h1: h1Light, - h2: h2Light, - h3: h3Light, - h4: {}, - h5: {}, - iframe, - input: inputLight, - p: pLight, - pre: preLight, - textarea: textareaLight, - video, - }, - markdown: { - p: [...Object.keys(pLight)], - h1: [...Object.keys(h1Light)], - h2: [...Object.keys(h2Light)], - h3: [...Object.keys(h3Light)], - h4: [], - h5: [], - ul: [...Object.keys(unorderedListLight)], - ol: [...Object.keys(orderedListLight)], - li: [...Object.keys(listItemLight)], - a: [...Object.keys(aLight)], - strong: [], - em: [], - }, -}; diff --git a/samples/client/angular/projects/rizzcharts/src/components/toolbar/toolbar.html b/samples/client/angular/projects/rizzcharts/src/components/toolbar/toolbar.html deleted file mode 100644 index 3b06d0c68..000000000 --- a/samples/client/angular/projects/rizzcharts/src/components/toolbar/toolbar.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - A2UI Component Catalog - - @for (option of catalogs; track option) { - {{ option.viewValue }} - } - - - - - - \ No newline at end of file diff --git a/samples/client/angular/projects/rizzcharts/src/components/toolbar/toolbar.scss b/samples/client/angular/projects/rizzcharts/src/components/toolbar/toolbar.scss deleted file mode 100644 index f41813f36..000000000 --- a/samples/client/angular/projects/rizzcharts/src/components/toolbar/toolbar.scss +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - - -.example-spacer { - flex: 1 1 auto; -} - -.logo { - -} - -.profile-photo { - width: 40px; - height: 40px; - font-size: 40px; - border-radius: var(--mat-sys-corner-extra-large); - vertical-align: top; - margin-right: 0.5rem; -} - -.logo-dark { - display: block; - height: 50px; -} - -.logo-light { - display: none; - height: 24px; - margin-left: 20px; -} - -@media (prefers-color-scheme: dark) { - .logo-light { - display: block; - } - - .logo-dark { - display: none; - } -} - -mat-toolbar { - background-color: transparent; -} - -mat-form-field { - width: 350px; -} \ No newline at end of file diff --git a/samples/client/angular/projects/rizzcharts/src/components/toolbar/toolbar.spec.ts b/samples/client/angular/projects/rizzcharts/src/components/toolbar/toolbar.spec.ts deleted file mode 100644 index 05b3c33ad..000000000 --- a/samples/client/angular/projects/rizzcharts/src/components/toolbar/toolbar.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - Copyright 2025 Google LLC - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { Toolbar } from './toolbar'; - -describe('Toolbar', () => { - let component: Toolbar; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [Toolbar] - }) - .compileComponents(); - - fixture = TestBed.createComponent(Toolbar); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/samples/client/angular/projects/rizzcharts/src/components/toolbar/toolbar.ts b/samples/client/angular/projects/rizzcharts/src/components/toolbar/toolbar.ts deleted file mode 100644 index 343f1ebfe..000000000 --- a/samples/client/angular/projects/rizzcharts/src/components/toolbar/toolbar.ts +++ /dev/null @@ -1,67 +0,0 @@ -/* - Copyright 2025 Google LLC - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import { inject, Component, ChangeDetectionStrategy } from '@angular/core'; -import { CatalogService } from '../../services/catalog_service'; -import { MatButtonModule } from '@angular/material/button'; -import { MatIconModule } from '@angular/material/icon'; -import { MatToolbarModule } from '@angular/material/toolbar'; - -import { MatInputModule } from '@angular/material/input'; -import { MatSelectModule } from '@angular/material/select'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { FormsModule } from '@angular/forms'; - -@Component({ - selector: 'app-toolbar', - imports: [ - MatButtonModule, - MatIconModule, - MatToolbarModule, - MatSelectModule, - MatFormFieldModule, - MatInputModule, - FormsModule, - ], - templateUrl: './toolbar.html', - styleUrl: './toolbar.scss', - changeDetection: ChangeDetectionStrategy.Eager, -}) -export class Toolbar { - catalogService = inject(CatalogService); - selectedCatalogs: string[] = []; - - catalogs = [ - { - value: 'https://a2ui.org/specification/v0_8/standard_catalog_definition.json', - viewValue: 'Standard', - }, - { - value: - 'https://github.com/google/A2UI/blob/main/samples/agent/adk/rizzcharts/rizzcharts_catalog_definition.json', - viewValue: 'Rizzcharts Custom', - }, - ]; - - ngOnInit() { - this.selectedCatalogs = this.catalogs.map((c) => c.value); - this.updateCatalogService(); - } - - updateCatalogService() { - this.catalogService.catalogUris = this.selectedCatalogs; - } -} diff --git a/samples/client/angular/projects/rizzcharts/src/environments/environment.ts b/samples/client/angular/projects/rizzcharts/src/environments/environment.ts deleted file mode 100644 index 40acd8eae..000000000 --- a/samples/client/angular/projects/rizzcharts/src/environments/environment.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - Copyright 2025 Google LLC - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -export const environment = { - googleMapsApiKey: 'YOUR_API_KEY_HERE' -}; diff --git a/samples/client/angular/projects/rizzcharts/src/index.html b/samples/client/angular/projects/rizzcharts/src/index.html deleted file mode 100644 index 5ffb846f5..000000000 --- a/samples/client/angular/projects/rizzcharts/src/index.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - RIZZCharts - - - - - - - - - - - diff --git a/samples/client/angular/projects/rizzcharts/src/main.server.ts b/samples/client/angular/projects/rizzcharts/src/main.server.ts deleted file mode 100644 index 66e605130..000000000 --- a/samples/client/angular/projects/rizzcharts/src/main.server.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - Copyright 2025 Google LLC - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import { BootstrapContext, bootstrapApplication } from '@angular/platform-browser'; -import { App } from './app/app'; -import { config } from './app/app.config.server'; - -const bootstrap = (context: BootstrapContext) => - bootstrapApplication(App, config, context); - -export default bootstrap; diff --git a/samples/client/angular/projects/rizzcharts/src/main.ts b/samples/client/angular/projects/rizzcharts/src/main.ts deleted file mode 100644 index 996a47ce8..000000000 --- a/samples/client/angular/projects/rizzcharts/src/main.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - Copyright 2025 Google LLC - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import { bootstrapApplication } from '@angular/platform-browser'; -import { appConfig } from './app/app.config'; -import { App } from './app/app'; -import { Chart, registerables } from 'chart.js'; -import ChartDataLabels from 'chartjs-plugin-datalabels'; - -Chart.register(...registerables, ChartDataLabels); - -bootstrapApplication(App, appConfig) - .catch((err) => console.error(err)); diff --git a/samples/client/angular/projects/rizzcharts/src/pipes/markdown.pipe.spec.ts b/samples/client/angular/projects/rizzcharts/src/pipes/markdown.pipe.spec.ts deleted file mode 100644 index 55ef02e22..000000000 --- a/samples/client/angular/projects/rizzcharts/src/pipes/markdown.pipe.spec.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - Copyright 2025 Google LLC - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import { TestBed } from '@angular/core/testing'; -import { DomSanitizer } from '@angular/platform-browser'; -import { MarkdownPipe } from './markdown.pipe'; - -describe('MarkdownPipe', () => { - let pipe: MarkdownPipe; - let sanitizer: DomSanitizer; - - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [ - MarkdownPipe, - { - provide: DomSanitizer, - useValue: { - bypassSecurityTrustHtml: (val: string) => val, - }, - }, - ], - }); - pipe = TestBed.inject(MarkdownPipe); - sanitizer = TestBed.inject(DomSanitizer); - }); - - it('create an instance', () => { - expect(pipe).toBeTruthy(); - }); - - it('should render markdown to html', () => { - const markdown = '**bold**'; - const result = pipe.transform(markdown); - expect(result).toContain('bold'); - }); - - - it('should open links in new tab', () => { - const markdown = '[link](http://example.com)'; - const result = pipe.transform(markdown); - expect(result).toContain('target="_blank"'); - expect(result).toContain('rel="noopener noreferrer"'); - }); -}); diff --git a/samples/client/angular/projects/rizzcharts/src/pipes/markdown.pipe.ts b/samples/client/angular/projects/rizzcharts/src/pipes/markdown.pipe.ts deleted file mode 100644 index f7613e43e..000000000 --- a/samples/client/angular/projects/rizzcharts/src/pipes/markdown.pipe.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - Copyright 2025 Google LLC - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import { Pipe, PipeTransform, inject } from '@angular/core'; -import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; -import markdownit from 'markdown-it'; - -@Pipe({ name: 'markdown' }) -export class MarkdownPipe implements PipeTransform { - private readonly sanitizer = inject(DomSanitizer); - private readonly md = markdownit({ - html: false, - linkify: true, - typographer: true, - }); - - constructor() { - this.configureRenderer(); - } - - transform(value: string | null | undefined): SafeHtml { - if (!value) { - return ''; - } - const rendered = this.md.render(value); - return this.sanitizer.bypassSecurityTrustHtml(rendered); - } - - private configureRenderer() { - // Open links in new tab - const defaultLinkOpenRender = - this.md.renderer.rules['link_open'] || - ((tokens, idx, options, env, self) => self.renderToken(tokens, idx, options)); - - this.md.renderer.rules['link_open'] = (tokens, idx, options, env, self) => { - const token = tokens[idx]; - token.attrSet('target', '_blank'); - token.attrSet('rel', 'noopener noreferrer'); - return defaultLinkOpenRender(tokens, idx, options, env, self); - }; - } -} diff --git a/samples/client/angular/projects/rizzcharts/src/server.ts b/samples/client/angular/projects/rizzcharts/src/server.ts deleted file mode 100644 index a5a361ef0..000000000 --- a/samples/client/angular/projects/rizzcharts/src/server.ts +++ /dev/null @@ -1,143 +0,0 @@ -/* - Copyright 2025 Google LLC - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import { - AngularNodeAppEngine, - createNodeRequestHandler, - isMainModule, - writeResponseToNodeResponse, -} from '@angular/ssr/node'; -import express from 'express'; -import { join } from 'node:path'; -import { v4 as uuidv4 } from 'uuid'; -import { A2AClient } from '@a2a-js/sdk/client'; -import { Message, MessageSendParams, Part, SendMessageSuccessResponse, Task } from '@a2a-js/sdk'; - -const browserDistFolder = join(import.meta.dirname, '../browser'); -const app = express(); -const angularApp = new AngularNodeAppEngine(); -let client: A2AClient | null = null; - -app.use( - express.static(browserDistFolder, { - maxAge: '1y', - index: false, - redirect: false, - }), -); - -app.post('/a2a', (req, res) => { - let originalBody = ''; - - req.on('data', (chunk) => { - originalBody += chunk.toString(); - }); - - req.on('end', async () => { - const data = JSON.parse(originalBody); - - console.log('[a2a-middleware] Received data:', originalBody); - - const parts: Part[] = data['parts']; - const metadata: Record = data['metadata']; - const contextId: string | undefined = data['context_id']; - - const sendParams: MessageSendParams = { - message: { - messageId: uuidv4(), - contextId, - role: 'user', - parts, - kind: 'message', - metadata: metadata, - }, - }; - - const client = await createOrGetClient(); - const response = await client.sendMessage(sendParams); - - res.set('Cache-Control', 'no-store'); - - if ('error' in response) { - console.error('Error:', response.error.message); - res.status(500).json({ error: response.error.message }); - return; - } - - res.json(response); - }); -}); - -app.get('/a2a/agent-card', async (req, res) => { - try { - const response = await fetchWithCustomHeader('http://localhost:10002/.well-known/agent-card.json'); - if (!response.ok) { - res.status(response.status).json({ error: 'Failed to fetch agent card' }); - return; - } - const card = await response.json(); - res.json(card); - } catch (error) { - console.error('Error fetching agent card:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -app.use((req, res, next) => { - angularApp - .handle(req) - .then((response) => (response ? writeResponseToNodeResponse(response, res) : next())) - .catch(next); -}); - -if (isMainModule(import.meta.url) || process.env['pm_id']) { - const port = process.env['PORT'] || 4000; - app.listen(port, (error) => { - if (error) { - throw error; - } - - console.log(`Node Express server listening on http://localhost:${port}`); - }); -} - -async function fetchWithCustomHeader(url: string | URL | Request, init?: RequestInit) { - const headers = new Headers(init?.headers); - headers.set('X-A2A-Extensions', 'https://a2ui.org/a2a-extension/a2ui/v0.8'); - const newInit = { ...init, headers }; - return fetch(url, newInit); -} - -async function createOrGetClient() { - // Create a client pointing to the agent's Agent Card URL. - client ??= await A2AClient.fromCardUrl('http://localhost:10002/.well-known/agent-card.json', { - fetchImpl: fetchWithCustomHeader, - }); - - return client; -} - -function isJson(str: string): boolean { - try { - const parsed = JSON.parse(str); - return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed); - } catch (err) { - console.warn(err); - return false; - } -} - -export const reqHandler = createNodeRequestHandler(app); diff --git a/samples/client/angular/projects/rizzcharts/src/services/a2a_service.ts b/samples/client/angular/projects/rizzcharts/src/services/a2a_service.ts deleted file mode 100644 index 9433b901a..000000000 --- a/samples/client/angular/projects/rizzcharts/src/services/a2a_service.ts +++ /dev/null @@ -1,67 +0,0 @@ -/* - Copyright 2025 Google LLC - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import { AgentCard, Part, SendMessageSuccessResponse } from '@a2a-js/sdk'; -import { A2aService as A2aServiceInterface } from '@a2a_chat_canvas/interfaces/a2a-service'; -import { Injectable } from '@angular/core'; -import { CatalogService } from './catalog_service'; - -@Injectable({ providedIn: 'root' }) -export class A2aService implements A2aServiceInterface { - private contextId?: string; - - constructor(private catalogService: CatalogService) { } - - async sendMessage(parts: Part[], signal?: AbortSignal): Promise { - const currentCatalogUris = this.catalogService.catalogUris; - console.log("Attaching supported A2UI catalogs to message: ", currentCatalogUris); - const response = await fetch('/a2a', { - body: JSON.stringify({ - 'parts': parts, - 'metadata': { - "a2uiClientCapabilities": { - "supportedCatalogIds": currentCatalogUris - } - }, - 'context_id': this.contextId - }), - method: 'POST', - signal, - }); - - if (response.ok) { - const json = await response.json() as SendMessageSuccessResponse & { context_id?: string }; - if (json.context_id) { - this.contextId = json.context_id; - } - return json; - } - - const error = (await response.json()) as { error: string }; - throw new Error(error.error); - } - - async getAgentCard(): Promise { - const response = await fetch('/a2a/agent-card'); - if (!response.ok) { - throw new Error('Failed to fetch agent card'); - } - const card = await response.json() as AgentCard; - // Override iconUrl to use local asset - card.iconUrl = 'rizz-agent.png'; - return card; - } -} diff --git a/samples/client/angular/projects/rizzcharts/src/services/catalog_service.ts b/samples/client/angular/projects/rizzcharts/src/services/catalog_service.ts deleted file mode 100644 index 103a460fb..000000000 --- a/samples/client/angular/projects/rizzcharts/src/services/catalog_service.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - Copyright 2025 Google LLC - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import { Injectable } from '@angular/core'; - -@Injectable({ providedIn: 'root' }) -export class CatalogService { - public catalogUris: string[] = []; -} diff --git a/samples/client/angular/projects/rizzcharts/src/services/markdown-renderer.service.spec.ts b/samples/client/angular/projects/rizzcharts/src/services/markdown-renderer.service.spec.ts deleted file mode 100644 index 5c81b4327..000000000 --- a/samples/client/angular/projects/rizzcharts/src/services/markdown-renderer.service.spec.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - Copyright 2025 Google LLC - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import { TestBed } from '@angular/core/testing'; -import { DomSanitizer } from '@angular/platform-browser'; -import { RizzchartsMarkdownRendererService } from './markdown-renderer.service'; - -describe('RizzchartsMarkdownRendererService', () => { - let service: RizzchartsMarkdownRendererService; - - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [ - RizzchartsMarkdownRendererService, - { - provide: DomSanitizer, - useValue: { - bypassSecurityTrustHtml: (val: string) => val, - }, - }, - ], - }); - service = TestBed.inject(RizzchartsMarkdownRendererService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); - - it('should render markdown to html', async () => { - const markdown = '**bold**'; - const result = await service.render(markdown); - expect(result).toContain('bold'); - }); - - it('should open links in new tab', async () => { - const markdown = '[link](http://example.com)'; - const result = await service.render(markdown); - expect(result).toContain('target="_blank"'); - expect(result).toContain('rel="noopener noreferrer"'); - }); -}); diff --git a/samples/client/angular/projects/rizzcharts/src/services/markdown-renderer.service.ts b/samples/client/angular/projects/rizzcharts/src/services/markdown-renderer.service.ts deleted file mode 100644 index ba05e2d4a..000000000 --- a/samples/client/angular/projects/rizzcharts/src/services/markdown-renderer.service.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - Copyright 2025 Google LLC - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import { MarkdownRendererService } from '@a2a_chat_canvas/interfaces/markdown-renderer-service'; -import { inject, Injectable } from '@angular/core'; -import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; -import markdownit from 'markdown-it'; - -@Injectable({ - providedIn: 'root', -}) -export class RizzchartsMarkdownRendererService implements MarkdownRendererService { - private readonly sanitizer = inject(DomSanitizer); - private readonly md = markdownit({ - html: false, - linkify: true, - typographer: true, - }); - - constructor() { - this.configureRenderer(); - } - - render(markdown: string): Promise { - const rendered = this.md.render(markdown); - return Promise.resolve(this.sanitizer.bypassSecurityTrustHtml(rendered)); - } - - private configureRenderer() { - // Open links in new tab - const defaultLinkOpenRender = - this.md.renderer.rules['link_open'] || - ((tokens, idx, options, env, self) => self.renderToken(tokens, idx, options)); - - this.md.renderer.rules['link_open'] = (tokens, idx, options, env, self) => { - const token = tokens[idx]; - token.attrSet('target', '_blank'); - token.attrSet('rel', 'noopener noreferrer'); - return defaultLinkOpenRender(tokens, idx, options, env, self); - }; - } -} diff --git a/samples/client/angular/projects/rizzcharts/src/styles.scss b/samples/client/angular/projects/rizzcharts/src/styles.scss deleted file mode 100644 index 236d15a04..000000000 --- a/samples/client/angular/projects/rizzcharts/src/styles.scss +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - - -// You can add global styles to this file, and also import other style files -@use '@angular/material' as mat; - - -@mixin styled-scrollbar { - ::-webkit-scrollbar { - width: 0.5rem; - height: 0.5rem; - background: transparent; - } - - ::-webkit-scrollbar-track { - background: var(--mat-sys-container-low); - border-radius: 0.25rem; - } - - ::-webkit-scrollbar-thumb { - background: var(--mat-sys-outline-variant); - border-radius: 0.25rem; - } - - ::-webkit-scrollbar-thumb:hover { - background: var(--mat-sys-outline); - } -} - -html { - color-scheme: light dark; - @include mat.theme(( - color: mat.$blue-palette, - typography: Roboto, - density: 0 - )); - - @include styled-scrollbar; -} - -:root { - --n-100: #ffffff; - --n-99: #fcfcfc; - --n-98: #f9f9f9; - --n-95: #f1f1f1; - --n-90: #e2e2e2; - --n-80: #c6c6c6; - --n-70: #ababab; - --n-60: #919191; - --n-50: #777777; - --n-40: #5e5e5e; - --n-35: #525252; - --n-30: #474747; - --n-25: #3b3b3b; - --n-20: #303030; - --n-15: #262626; - --n-10: #1b1b1b; - --n-5: #111111; - --n-0: #000000; - - --p-100: #ffffff; - --p-99: #fffbff; - --p-98: #fcf8ff; - --p-95: #f2efff; - --p-90: #e1e0ff; - --p-80: #c0c1ff; - --p-70: #a0a3ff; - --p-60: #8487ea; - --p-50: #6a6dcd; - --p-40: #5154b3; - --p-35: #4447a6; - --p-30: #383b99; - --p-25: #2c2e8d; - --p-20: #202182; - --p-15: #131178; - --p-10: #06006c; - --p-5: #03004d; - --p-0: #000000; - - --s-100: #ffffff; - --s-99: #fffbff; - --s-98: #fcf8ff; - --s-95: #f2efff; - --s-90: #e2e0f9; - --s-80: #c6c4dd; - --s-70: #aaa9c1; - --s-60: #8f8fa5; - --s-50: #75758b; - --s-40: #5d5c72; - --s-35: #515165; - --s-30: #454559; - --s-25: #393a4d; - --s-20: #2e2f42; - --s-15: #242437; - --s-10: #191a2c; - --s-5: #0f0f21; - --s-0: #000000; - - --t-100: #ffffff; - --t-99: #fffbff; - --t-98: #fff8f9; - --t-95: #ffecf4; - --t-90: #ffd8ec; - --t-80: #e9b9d3; - --t-70: #cc9eb8; - --t-60: #af849d; - --t-50: #946b83; - --t-40: #79536a; - --t-35: #6c475d; - --t-30: #5f3c51; - --t-25: #523146; - --t-20: #46263a; - --t-15: #3a1b2f; - --t-10: #2e1125; - --t-5: #22071a; - --t-0: #000000; - - --nv-100: #ffffff; - --nv-99: #fffbff; - --nv-98: #fcf8ff; - --nv-95: #f2effa; - --nv-90: #e4e1ec; - --nv-80: #c8c5d0; - --nv-70: #acaab4; - --nv-60: #918f9a; - --nv-50: #777680; - --nv-40: #5e5d67; - --nv-35: #52515b; - --nv-30: #46464f; - --nv-25: #3b3b43; - --nv-20: #303038; - --nv-15: #25252d; - --nv-10: #1b1b23; - --nv-5: #101018; - --nv-0: #000000; - - --e-100: #ffffff; - --e-99: #fffbff; - --e-98: #fff8f7; - --e-95: #ffedea; - --e-90: #ffdad6; - --e-80: #ffb4ab; - --e-70: #ff897d; - --e-60: #ff5449; - --e-50: #de3730; - --e-40: #ba1a1a; - --e-35: #a80710; - --e-30: #93000a; - --e-25: #7e0007; - --e-20: #690005; - --e-15: #540003; - --e-10: #410002; - --e-5: #2d0001; - --e-0: #000000; - - --primary: #137fec; - --text-color: #fff; - --background-light: #f6f7f8; - --background-dark: #101922; - --border-color: oklch(from var(--background-light) l c h / calc(alpha * 0.15)); - --elevated-background-light: oklch(from var(--background-light) l c h / calc(alpha * 0.05)); - --bb-grid-size: 4px; - --bb-grid-size-2: calc(var(--bb-grid-size) * 2); - --bb-grid-size-3: calc(var(--bb-grid-size) * 3); - --bb-grid-size-4: calc(var(--bb-grid-size) * 4); - --bb-grid-size-5: calc(var(--bb-grid-size) * 5); - --bb-grid-size-6: calc(var(--bb-grid-size) * 6); - --bb-grid-size-7: calc(var(--bb-grid-size) * 7); - --bb-grid-size-8: calc(var(--bb-grid-size) * 8); - --bb-grid-size-9: calc(var(--bb-grid-size) * 9); - --bb-grid-size-10: calc(var(--bb-grid-size) * 10); - --bb-grid-size-11: calc(var(--bb-grid-size) * 11); - --bb-grid-size-12: calc(var(--bb-grid-size) * 12); - --bb-grid-size-13: calc(var(--bb-grid-size) * 13); - --bb-grid-size-14: calc(var(--bb-grid-size) * 14); - --bb-grid-size-15: calc(var(--bb-grid-size) * 15); - --bb-grid-size-16: calc(var(--bb-grid-size) * 16); -} - -* { - box-sizing: border-box; -} - -html, -body { - --font-family: 'Google Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; - --font-family-flex: 'Google Sans Flex', 'Helvetica Neue', Helvetica, Arial, sans-serif; - --font-family-mono: 'Google Sans Code', 'Helvetica Neue', Helvetica, Arial, sans-serif; - - font-family: var(--font-family); - margin: 0; - padding: 0; - width: 100svw; - height: 100svh; -} diff --git a/samples/client/angular/projects/rizzcharts/src/utils/utils.ts b/samples/client/angular/projects/rizzcharts/src/utils/utils.ts deleted file mode 100644 index d9aedc1d8..000000000 --- a/samples/client/angular/projects/rizzcharts/src/utils/utils.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - Copyright 2025 Google LLC - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import { Part } from '@a2a-js/sdk'; - -/** - * Returns true if the part is a thought. - * - * @param part The part to check. - * @return True if the part is a thought, false otherwise. - */ -export function isAgentThought(part: Part): boolean { - if (!part.metadata) { - return false; - } - - const metadata = part.metadata as { adk_thought: boolean }; - return metadata.adk_thought; -} diff --git a/samples/client/angular/projects/rizzcharts/tsconfig.app.json b/samples/client/angular/projects/rizzcharts/tsconfig.app.json deleted file mode 100644 index e04b233e0..000000000 --- a/samples/client/angular/projects/rizzcharts/tsconfig.app.json +++ /dev/null @@ -1,17 +0,0 @@ -/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ -/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "../../out-tsc/app", - "types": [ - "node" - ], - }, - "include": [ - "src/**/*.ts" - ], - "exclude": [ - "src/**/*.spec.ts" - ] -} diff --git a/samples/client/angular/projects/rizzcharts/tsconfig.spec.json b/samples/client/angular/projects/rizzcharts/tsconfig.spec.json deleted file mode 100644 index 0feea88ed..000000000 --- a/samples/client/angular/projects/rizzcharts/tsconfig.spec.json +++ /dev/null @@ -1,14 +0,0 @@ -/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ -/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "../../out-tsc/spec", - "types": [ - "jasmine" - ] - }, - "include": [ - "src/**/*.ts" - ] -} diff --git a/samples/client/angular/tsconfig.json b/samples/client/angular/tsconfig.json index eebf19c42..196a5decb 100644 --- a/samples/client/angular/tsconfig.json +++ b/samples/client/angular/tsconfig.json @@ -10,9 +10,6 @@ ], "@a2a_chat_canvas/*": [ "./projects/a2a-chat-canvas/src/lib/*" - ], - "@rizzcharts/*": [ - "./projects/rizzcharts/src/*" ] }, "noImplicitReturns": true, @@ -45,12 +42,6 @@ { "path": "./projects/restaurant/tsconfig.spec.json" }, - { - "path": "./projects/rizzcharts/tsconfig.app.json" - }, - { - "path": "./projects/rizzcharts/tsconfig.spec.json" - }, { "path": "./projects/contact/tsconfig.app.json" }, diff --git a/samples/client/lit/package.json b/samples/client/lit/package.json index 8389e9746..8bee2be86 100644 --- a/samples/client/lit/package.json +++ b/samples/client/lit/package.json @@ -10,13 +10,11 @@ "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:orchestrator": "cd ../../agent/adk/orchestrator && uv run .", "serve:shell": "cd shell && npm run dev", "build:renderer": "cd ../../../renderers && for dir in 'web_core' 'markdown/markdown-it' 'lit'; do (cd \"$dir\" && npm install && npm run build); done", "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:contact": "npm install && npm run build:renderer && concurrently -k -n \"SHELL,CONT1\" -c \"magenta,green\" \"npm run serve:shell\" \"npm run serve:agent:contact_lookup\"", - "demo:rizzcharts": "npm install && npm run build:renderer && concurrently -k -n \"SHELL,RIZZ\" -c \"magenta,yellow\" \"npm run serve:shell\" \"npm run serve:agent:rizzcharts\"", "demo:orchestrator": "npm install && npm run build:renderer && concurrently -k -n \"SHELL,ORCH\" -c \"magenta,cyan\" \"npm run serve:shell\" \"npm run serve:agent:orchestrator\"" }, "devDependencies": {