From 53bc03abffc1fc2384e1203e3eee323beaca970a Mon Sep 17 00:00:00 2001 From: Google Maps SDK Team Date: Tue, 25 Aug 2026 20:05:48 +0000 Subject: [PATCH] feat: Sync from internal staging. --- .github/dependabot.yml | 15 ++- .github/workflows/zizmor.yml | 2 +- agent/python/README.md | 41 ++++++ agent/python/__main__.py | 59 +++++++-- agent/python/agent_executor.py | 21 ++- agent/python/pyproject.toml | 6 +- agent/python/setup.sh | 20 ++- .../main/java/com/example/maui/AgentType.kt | 23 ++++ .../java/com/example/maui/MainActivity.kt | 20 ++- .../java/com/example/maui/ui/ChatViewModel.kt | 14 +- .../app/src/main/res/layout/activity_main.xml | 14 +- client/ios/A2UI-Example-Info.plist | 44 ++++++- .../A2UI-Example.xcodeproj/project.pbxproj | 124 +++++++++++++++--- client/ios/ChatService.swift | 22 +++- client/ios/ChatView.swift | 8 +- client/ios/ChatViewModel.swift | 7 +- client/ios/Info.plist | 49 ------- client/ios/Models.swift | 3 +- client/ios/README.md | 6 +- client/web/react/src/utils/platform.ts | 7 +- 20 files changed, 387 insertions(+), 118 deletions(-) create mode 100644 client/android/app/src/main/java/com/example/maui/AgentType.kt delete mode 100644 client/ios/Info.plist diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f266a80..ad53b5f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,3 +1,16 @@ +# Copyright 2026 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 +# +# http://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. version: 2 updates: - package-ecosystem: "npm" @@ -20,4 +33,4 @@ updates: interval: "weekly" open-pull-requests-limit: 0 cooldown: - default-days: 7 + default-days: 7 \ No newline at end of file diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 8c4f481..1d45775 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -23,4 +23,4 @@ jobs: persist-credentials: false - name: Run zizmor - uses: zizmorcore/zizmor-action@195d10ad90f31d8cd6ea1efd6ecc12969ddbe73f # v0.5.1 + uses: zizmorcore/zizmor-action@195d10ad90f31d8cd6ea1efd6ecc12969ddbe73f # v0.5.1 \ No newline at end of file diff --git a/agent/python/README.md b/agent/python/README.md index b34771f..e208edf 100644 --- a/agent/python/README.md +++ b/agent/python/README.md @@ -45,6 +45,47 @@ variables to be set: This will automatically resolve dependencies, install them in a local virtual environment, and start the A2A server on port 10002. +## Agent Modes & Configuration + +The sample server supports 3 agent backends configured via `--agent` or the +`A2UI_DEFAULT_AGENT` environment variable: + +Agent Mode | CLI Flag / Env Option | Description +:------------------------------- | :---------------------------------------------------- | :---------- +**Template Agent (Recommended)** | `--agent TEMPLATE`
`A2UI_DEFAULT_AGENT=TEMPLATE` | Low-latency template agent with fast intent classification (`LOCAL_SEARCH`, `DIRECTIONS`) and structured parameter merging. +**Base Agent** | `--agent BASE`
`A2UI_DEFAULT_AGENT=BASE` | Standard dynamic UI agent generating unconstrained A2UI component trees. +**Grounding Agent** | `--agent GROUNDING`
`A2UI_DEFAULT_AGENT=GROUNDING` | Vertex AI Maps Grounding agent. + +### Running with Template Agent + +```bash +A2UI_DEFAULT_AGENT=TEMPLATE \ +GEMINI_API_KEY="" \ +GOOGLE_MAPS_API_KEY="" \ +uv run python __main__.py --host 127.0.0.1 --port 10002 +``` + +### Multi-Agent Query Prefixes + +You can test specific agent implementations against a running server using +prompt prefixes: + +* `[TEMPLATE] ` ➔ Routes directly to `MAUIAgentWithTemplates` (e.g. + `[TEMPLATE] Coffee shops near Pike Place`). +* `[GROUNDING] ` ➔ Routes directly to `MAUIAgentWithGrounding` (e.g. + `[GROUNDING] Hotels in Bellevue`). +* `` (no prefix) ➔ Routes to the configured default agent. + +### Fallback Modes + +Set `A2UI_FALLBACK_MODE` to control behavior when a query cannot be fulfilled by +a static template: + +* `A2UI_FALLBACK_MODE=TEXT` (Default) ➔ Fast grounded plain text / markdown + response with Grounding Lite tool assistance. +* `A2UI_FALLBACK_MODE=DYNAMIC` ➔ Falls back to full dynamic multi-turn A2UI + component generation. + To run the frontend, follow the instructions in [../../client/web/react/README.md](../../client/web/react/README.md) diff --git a/agent/python/__main__.py b/agent/python/__main__.py index 9c49718..6653fa9 100644 --- a/agent/python/__main__.py +++ b/agent/python/__main__.py @@ -18,15 +18,17 @@ from a2a.server.apps import A2AStarletteApplication from a2a.server.request_handlers import DefaultRequestHandler from a2a.server.tasks import InMemoryTaskStore -from a2a.types import AgentCapabilities, AgentCard, AgentSkill import click import dotenv from starlette.middleware.cors import CORSMiddleware from starlette.responses import RedirectResponse from starlette.staticfiles import StaticFiles +import uvicorn -from python_agent.agent import MAUIAgent -from python_agent.agent_with_grounding import MAUIAgentWithGrounding +from agent import MAUIAgent +from agent_config import AgentConfig, FallbackMode +from agent_with_grounding import MAUIAgentWithGrounding +from agent_with_templates import MAUIAgentWithTemplates from agent_executor import MAUIAgentExecutor dotenv.load_dotenv() @@ -43,7 +45,18 @@ class MissingAPIKeyError(Exception): @click.option("--serverurl", default="") @click.option("--host", default="0.0.0.0") @click.option("--port", default=10002) -def main(serverurl, host, port): +@click.option( + "--agent", + default="MAUIAgent", + show_default=True, + envvar="A2UI_DEFAULT_AGENT", + help=( + "Agent to use as default. Accepts class name (e.g., 'MAUIAgent'," + " 'MAUIAgentWithTemplates', 'MAUIAgentWithGrounding') or shorthand" + " ('BASE', 'TEMPLATE', 'GROUNDING')." + ), +) +def main(serverurl, host, port, agent): try: # Check for API key only if Vertex AI is not configured if not os.getenv("GOOGLE_GENAI_USE_VERTEXAI") == "TRUE": @@ -58,11 +71,42 @@ def main(serverurl, host, port): if serverurl != "": base_url = serverurl + fallback_mode_env = os.getenv("A2UI_FALLBACK_MODE") + if fallback_mode_env: + config = AgentConfig(fallback_mode=FallbackMode(fallback_mode_env)) + else: + config = AgentConfig() + logger.info(f"Using fallback_mode: {config.fallback_mode}") + ui_agent = MAUIAgent(base_url=base_url) grounding_agent = MAUIAgentWithGrounding(base_url=base_url) + template_agent = MAUIAgentWithTemplates(base_url=base_url, config=config) + + agent_map = { + "MAUIAGENT": ui_agent, + "BASE": ui_agent, + "MAUIAGENTWITHGROUNDING": grounding_agent, + "GROUNDING": grounding_agent, + "MAUIAGENTWITHTEMPLATES": template_agent, + "TEMPLATE": template_agent, + } + + normalized_agent = agent.upper() + if normalized_agent not in agent_map: + raise ValueError( + f"Unknown agent: {agent}. Expected one of {list(agent_map.keys())}" + ) + + default_agent = agent_map[normalized_agent] + logger.info( + f"--- SERVER: Binding {default_agent.__class__.__name__} as default" + " agent ---" + ) agent_executor = MAUIAgentExecutor( - default_agent=ui_agent, grounding_agent=grounding_agent + default_agent=default_agent, + grounding_agent=grounding_agent, + template_agent=template_agent, ) request_handler = DefaultRequestHandler( @@ -70,15 +114,14 @@ def main(serverurl, host, port): task_store=InMemoryTaskStore(), ) server = A2AStarletteApplication( - agent_card=ui_agent.agent_card, http_handler=request_handler + agent_card=default_agent.agent_card, http_handler=request_handler ) - import uvicorn app = server.build() app.add_middleware( CORSMiddleware, - allow_origin_regex=r"http://localhost:\d+", + allow_origin_regex=r"http://(localhost|127\.0\.0\.1):\d+", allow_credentials=True, allow_methods=["*"], allow_headers=["*"], diff --git a/agent/python/agent_executor.py b/agent/python/agent_executor.py index 073c652..beead76 100644 --- a/agent/python/agent_executor.py +++ b/agent/python/agent_executor.py @@ -13,6 +13,7 @@ # limitations under the License. import logging +from typing import Optional from a2a.server.agent_execution import AgentExecutor, RequestContext from a2a.server.events import EventQueue @@ -33,8 +34,9 @@ from a2a.utils.errors import ServerError from a2ui.a2a.extension import try_activate_a2ui_extension -from python_agent.agent import MAUIAgent -from python_agent.agent_with_grounding import MAUIAgentWithGrounding +from agent import MAUIAgent +from agent_with_grounding import MAUIAgentWithGrounding +from agent_with_templates import MAUIAgentWithTemplates logger = logging.getLogger(__name__) @@ -43,10 +45,14 @@ class MAUIAgentExecutor(AgentExecutor): """MAUI AgentExecutor Example.""" def __init__( - self, default_agent: MAUIAgent, grounding_agent: MAUIAgentWithGrounding + self, + default_agent: MAUIAgent, + grounding_agent: MAUIAgentWithGrounding, + template_agent: Optional[MAUIAgentWithTemplates] = None, ): self._default_agent = default_agent self._grounding_agent = grounding_agent + self._template_agent = template_agent async def execute( self, @@ -95,6 +101,15 @@ async def execute( ) agent_to_use = self._grounding_agent query = query[len("[GROUNDING]") :].strip() + elif query.startswith("[TEMPLATE]"): + if not self._template_agent: + raise UnsupportedOperationError("Template Agent is not configured.") + logger.info( + "--- AGENT_EXECUTOR: Prefix [TEMPLATE] detected. Using Template" + " Agent. ---" + ) + agent_to_use = self._template_agent + query = query[len("[TEMPLATE]") :].strip() else: logger.info( "--- AGENT_EXECUTOR: No prefix detected. Using Default Agent. ---" diff --git a/agent/python/pyproject.toml b/agent/python/pyproject.toml index e781a71..5443901 100644 --- a/agent/python/pyproject.toml +++ b/agent/python/pyproject.toml @@ -5,9 +5,9 @@ description = "Sample agent consuming MAUI packages" requires-python = ">=3.13" dependencies = [ "maui-a2ui-python", - "google-adk[a2a,extensions]>=1.28.0,<2.0.0", - "a2a-sdk[http-server]>=0.3.0", - "google-genai>=1.64.0", + "google-adk[a2a,extensions,mcp]>=2.0.0", + "a2a-sdk[http-server]>=0.3.0,<1.0.0", + "google-genai>=2.0.0", "jsonschema>=4.0.0" ] diff --git a/agent/python/setup.sh b/agent/python/setup.sh index 44abb6a..232f54a 100755 --- a/agent/python/setup.sh +++ b/agent/python/setup.sh @@ -1,4 +1,18 @@ #!/bin/bash +# +# Copyright 2026 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 +# +# http://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. # Check if path argument is provided if [ -z "$1" ]; then @@ -18,6 +32,10 @@ fi # Replace path and uncomment line if needed # Using | as delimiter for sed to handle slashes in paths -sed -i '' "s|^ *#* *maui-a2ui-python = { path = [^,}]*|maui-a2ui-python = { path = \"$MAUI_PATH\"|" "$FILE" +if [[ "$OSTYPE" == "darwin"* ]]; then + sed -i '' "s|^ *#* *maui-a2ui-python = { path = [^,}]*|maui-a2ui-python = { path = \"$MAUI_PATH\"|" "$FILE" +else + sed -i "s|^ *#* *maui-a2ui-python = { path = [^,}]*|maui-a2ui-python = { path = \"$MAUI_PATH\"|" "$FILE" +fi echo "Updated $FILE with path: $MAUI_PATH" diff --git a/client/android/app/src/main/java/com/example/maui/AgentType.kt b/client/android/app/src/main/java/com/example/maui/AgentType.kt new file mode 100644 index 0000000..fff6fcc --- /dev/null +++ b/client/android/app/src/main/java/com/example/maui/AgentType.kt @@ -0,0 +1,23 @@ +// +// Copyright 2026 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 +// +// http://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. +// + +package com.example.maui + +enum class AgentType { + LITE, + VERTEX, + TEMPLATE, +} diff --git a/client/android/app/src/main/java/com/example/maui/MainActivity.kt b/client/android/app/src/main/java/com/example/maui/MainActivity.kt index be93eeb..1b44f26 100644 --- a/client/android/app/src/main/java/com/example/maui/MainActivity.kt +++ b/client/android/app/src/main/java/com/example/maui/MainActivity.kt @@ -128,13 +128,19 @@ class MainActivity : AppCompatActivity() { buttonSend.setOnClickListener { val messageText = editTextMessage.text.toString().trim() if (messageText.isNotEmpty()) { - val radioGroundingVertex = - findViewById(R.id.radioGroundingVertex) - viewModel.sendMessage( - messageText, - radioGroundingVertex.isChecked, - switchCannedServer.isChecked, - ) + val radioAgentVertex = findViewById(R.id.radioAgentVertex) + val radioAgentTemplate = findViewById(R.id.radioAgentTemplate) + + val agentType = + if (radioAgentVertex.isChecked) { + com.example.maui.AgentType.VERTEX + } else if (radioAgentTemplate.isChecked) { + com.example.maui.AgentType.TEMPLATE + } else { + com.example.maui.AgentType.LITE + } + + viewModel.sendMessage(messageText, agentType, switchCannedServer.isChecked) editTextMessage.text.clear() } } diff --git a/client/android/app/src/main/java/com/example/maui/ui/ChatViewModel.kt b/client/android/app/src/main/java/com/example/maui/ui/ChatViewModel.kt index d6f4396..5a1b746 100644 --- a/client/android/app/src/main/java/com/example/maui/ui/ChatViewModel.kt +++ b/client/android/app/src/main/java/com/example/maui/ui/ChatViewModel.kt @@ -46,11 +46,21 @@ class ChatViewModel( resourceLogger.startLogging(viewModelScope) } - fun sendMessage(text: String, isGrounding: Boolean = false, bypassCanned: Boolean = false) { + fun sendMessage( + text: String, + agentType: com.example.maui.AgentType = com.example.maui.AgentType.LITE, + bypassCanned: Boolean = false, + ) { currentRequestJob?.cancel() currentAgentTextIndex = null currentAgentA2UIIndex = null - val serverMessageText = if (isGrounding) "[GROUNDING] $text" else text + val serverMessageText = + when (agentType) { + com.example.maui.AgentType.VERTEX -> "[GROUNDING] $text" + com.example.maui.AgentType.TEMPLATE -> "[TEMPLATE] $text" + com.example.maui.AgentType.LITE -> text + } + addMessage(ChatMessage.Text(text, true)) val jsonObject = JSONObject().apply { diff --git a/client/android/app/src/main/res/layout/activity_main.xml b/client/android/app/src/main/res/layout/activity_main.xml index 60472d5..7891a55 100644 --- a/client/android/app/src/main/res/layout/activity_main.xml +++ b/client/android/app/src/main/res/layout/activity_main.xml @@ -34,7 +34,7 @@ android:id="@+id/promptsLayout" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_above="@+id/groundingRadioGroup" + android:layout_above="@+id/agentRadioGroup" android:orientation="horizontal" android:gravity="center_vertical" android:padding="8dp"> @@ -53,7 +53,7 @@ + + - NSAppTransportSecurity + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest - NSAllowsArbitraryLoads - + UIApplicationSupportsMultipleScenes + + UILaunchStoryboardName + LaunchScreen + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSAllowsLocalNetworking + + diff --git a/client/ios/A2UI-Example.xcodeproj/project.pbxproj b/client/ios/A2UI-Example.xcodeproj/project.pbxproj index ec2e66e..dbc3750 100644 --- a/client/ios/A2UI-Example.xcodeproj/project.pbxproj +++ b/client/ios/A2UI-Example.xcodeproj/project.pbxproj @@ -7,7 +7,34 @@ objects = { /* Begin PBXBuildFile section */ - 06250BF72FC7D25D009091F1 /* GoogleMapsA2UI in Frameworks */ = {isa = PBXBuildFile; productRef = 06250BF62FC7D25D009091F1 /* GoogleMapsA2UI */; }; + 06D7D379303CF72A005E2E6E /* GoogleMapsA2UI in Frameworks */ = {isa = PBXBuildFile; productRef = 06D7D378303CF72A005E2E6E /* GoogleMapsA2UI */; }; + 06D7D39E303CFB03005E2E6E /* LatencyLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D7D380303CFB03005E2E6E /* LatencyLogger.swift */; }; + 06D7D39F303CFB03005E2E6E /* ResourceLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D7D383303CFB03005E2E6E /* ResourceLogger.swift */; }; + 06D7D3A0303CFB03005E2E6E /* MockScenarioRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D7D381303CFB03005E2E6E /* MockScenarioRegistry.swift */; }; + 06D7D3A1303CFB03005E2E6E /* ChatService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D7D37E303CFB03005E2E6E /* ChatService.swift */; }; + 06D7D3A2303CFB03005E2E6E /* TestCasesMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D7D384303CFB03005E2E6E /* TestCasesMenuView.swift */; }; + 06D7D3A3303D019F005E2E6E /* SLUSaladsVegan.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D38F303CFB03005E2E6E /* SLUSaladsVegan.json */; }; + 06D7D3A4303D019F005E2E6E /* SLUSaladsDirections.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D38E303CFB03005E2E6E /* SLUSaladsDirections.json */; }; + 06D7D3A5303D019F005E2E6E /* NYCAttractions.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D38B303CFB03005E2E6E /* NYCAttractions.json */; }; + 06D7D3A6303D019F005E2E6E /* GasWorksPark.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D386303CFB03005E2E6E /* GasWorksPark.json */; }; + 06D7D3A7303D019F005E2E6E /* SLUSaladsClick.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D38D303CFB03005E2E6E /* SLUSaladsClick.json */; }; + 06D7D3A8303D019F005E2E6E /* LondonItinerary.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D389303CFB03005E2E6E /* LondonItinerary.json */; }; + 06D7D3A9303D019F005E2E6E /* MVGoogleGyms.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D38A303CFB03005E2E6E /* MVGoogleGyms.json */; }; + 06D7D3AA303D019F005E2E6E /* KirklandCommute.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D387303CFB03005E2E6E /* KirklandCommute.json */; }; + 06D7D3AB303D019F005E2E6E /* EdgewaterHotel.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D385303CFB03005E2E6E /* EdgewaterHotel.json */; }; + 06D7D3AC303D019F005E2E6E /* SeattleCoffeeShops.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D38C303CFB03005E2E6E /* SeattleCoffeeShops.json */; }; + 06D7D3AD303D019F005E2E6E /* LePetiteAcademy.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D388303CFB03005E2E6E /* LePetiteAcademy.json */; }; + 06D7D3AE303D29EA005E2E6E /* LePetiteAcademy.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D388303CFB03005E2E6E /* LePetiteAcademy.json */; }; + 06D7D3AF303D29EA005E2E6E /* SLUSaladsVegan.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D38F303CFB03005E2E6E /* SLUSaladsVegan.json */; }; + 06D7D3B0303D29EA005E2E6E /* NYCAttractions.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D38B303CFB03005E2E6E /* NYCAttractions.json */; }; + 06D7D3B1303D29EA005E2E6E /* GasWorksPark.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D386303CFB03005E2E6E /* GasWorksPark.json */; }; + 06D7D3B2303D29EA005E2E6E /* EdgewaterHotel.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D385303CFB03005E2E6E /* EdgewaterHotel.json */; }; + 06D7D3B3303D29EA005E2E6E /* KirklandCommute.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D387303CFB03005E2E6E /* KirklandCommute.json */; }; + 06D7D3B4303D29EA005E2E6E /* SeattleCoffeeShops.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D38C303CFB03005E2E6E /* SeattleCoffeeShops.json */; }; + 06D7D3B5303D29EA005E2E6E /* SLUSaladsDirections.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D38E303CFB03005E2E6E /* SLUSaladsDirections.json */; }; + 06D7D3B6303D29EA005E2E6E /* LondonItinerary.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D389303CFB03005E2E6E /* LondonItinerary.json */; }; + 06D7D3B7303D29EA005E2E6E /* MVGoogleGyms.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D38A303CFB03005E2E6E /* MVGoogleGyms.json */; }; + 06D7D3B8303D29EA005E2E6E /* SLUSaladsClick.json in Resources */ = {isa = PBXBuildFile; fileRef = 06D7D38D303CFB03005E2E6E /* SLUSaladsClick.json */; }; FDC9C4E32F5B9D7000ABA773 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC9C4E22F5B9D7000ABA773 /* Models.swift */; }; FDC9C4E42F5B9D7000ABA773 /* ChatApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC9C4DF2F5B9D7000ABA773 /* ChatApp.swift */; }; FDC9C4E52F5B9D7000ABA773 /* ChatViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC9C4E12F5B9D7000ABA773 /* ChatViewModel.swift */; }; @@ -25,6 +52,22 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 06D7D37E303CFB03005E2E6E /* ChatService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatService.swift; sourceTree = ""; }; + 06D7D380303CFB03005E2E6E /* LatencyLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LatencyLogger.swift; sourceTree = ""; }; + 06D7D381303CFB03005E2E6E /* MockScenarioRegistry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockScenarioRegistry.swift; sourceTree = ""; }; + 06D7D383303CFB03005E2E6E /* ResourceLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourceLogger.swift; sourceTree = ""; }; + 06D7D384303CFB03005E2E6E /* TestCasesMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestCasesMenuView.swift; sourceTree = ""; }; + 06D7D385303CFB03005E2E6E /* EdgewaterHotel.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = EdgewaterHotel.json; sourceTree = ""; }; + 06D7D386303CFB03005E2E6E /* GasWorksPark.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = GasWorksPark.json; sourceTree = ""; }; + 06D7D387303CFB03005E2E6E /* KirklandCommute.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = KirklandCommute.json; sourceTree = ""; }; + 06D7D388303CFB03005E2E6E /* LePetiteAcademy.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = LePetiteAcademy.json; sourceTree = ""; }; + 06D7D389303CFB03005E2E6E /* LondonItinerary.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = LondonItinerary.json; sourceTree = ""; }; + 06D7D38A303CFB03005E2E6E /* MVGoogleGyms.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = MVGoogleGyms.json; sourceTree = ""; }; + 06D7D38B303CFB03005E2E6E /* NYCAttractions.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = NYCAttractions.json; sourceTree = ""; }; + 06D7D38C303CFB03005E2E6E /* SeattleCoffeeShops.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = SeattleCoffeeShops.json; sourceTree = ""; }; + 06D7D38D303CFB03005E2E6E /* SLUSaladsClick.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = SLUSaladsClick.json; sourceTree = ""; }; + 06D7D38E303CFB03005E2E6E /* SLUSaladsDirections.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = SLUSaladsDirections.json; sourceTree = ""; }; + 06D7D38F303CFB03005E2E6E /* SLUSaladsVegan.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = SLUSaladsVegan.json; sourceTree = ""; }; FD3276DB2F7B9B3B007E25E3 /* A2UI-ExampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "A2UI-ExampleUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; FD8193352F749D4A001C0D09 /* A2UI-Example-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "A2UI-Example-Info.plist"; sourceTree = ""; }; FDC9C4D02F5B9CB800ABA773 /* A2UI-Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "A2UI-Example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -54,23 +97,54 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 06250BF72FC7D25D009091F1 /* GoogleMapsA2UI in Frameworks */, + 06D7D379303CF72A005E2E6E /* GoogleMapsA2UI in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 06D7D377303CF72A005E2E6E /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; + 06D7D390303CFB03005E2E6E /* testdata */ = { + isa = PBXGroup; + children = ( + 06D7D385303CFB03005E2E6E /* EdgewaterHotel.json */, + 06D7D386303CFB03005E2E6E /* GasWorksPark.json */, + 06D7D387303CFB03005E2E6E /* KirklandCommute.json */, + 06D7D388303CFB03005E2E6E /* LePetiteAcademy.json */, + 06D7D389303CFB03005E2E6E /* LondonItinerary.json */, + 06D7D38A303CFB03005E2E6E /* MVGoogleGyms.json */, + 06D7D38B303CFB03005E2E6E /* NYCAttractions.json */, + 06D7D38C303CFB03005E2E6E /* SeattleCoffeeShops.json */, + 06D7D38D303CFB03005E2E6E /* SLUSaladsClick.json */, + 06D7D38E303CFB03005E2E6E /* SLUSaladsDirections.json */, + 06D7D38F303CFB03005E2E6E /* SLUSaladsVegan.json */, + ); + path = testdata; + sourceTree = ""; + }; FDC9C4C72F5B9CB800ABA773 = { isa = PBXGroup; children = ( + 06D7D37E303CFB03005E2E6E /* ChatService.swift */, + 06D7D380303CFB03005E2E6E /* LatencyLogger.swift */, + 06D7D381303CFB03005E2E6E /* MockScenarioRegistry.swift */, + 06D7D383303CFB03005E2E6E /* ResourceLogger.swift */, + 06D7D384303CFB03005E2E6E /* TestCasesMenuView.swift */, + 06D7D390303CFB03005E2E6E /* testdata */, FD8193352F749D4A001C0D09 /* A2UI-Example-Info.plist */, - FDC9C4E82F5B9DE300ABA773 /* Resources */, FDC9C4DF2F5B9D7000ABA773 /* ChatApp.swift */, FDC9C4E02F5B9D7000ABA773 /* ChatView.swift */, FDC9C4E12F5B9D7000ABA773 /* ChatViewModel.swift */, FDC9C4E22F5B9D7000ABA773 /* Models.swift */, FD3276DC2F7B9B3B007E25E3 /* A2UI-ExampleUITests */, + 06D7D377303CF72A005E2E6E /* Frameworks */, FDC9C4D12F5B9CB800ABA773 /* Products */, ); sourceTree = ""; @@ -84,13 +158,6 @@ name = Products; sourceTree = ""; }; - FDC9C4E82F5B9DE300ABA773 /* Resources */ = { - isa = PBXGroup; - children = ( - ); - path = Resources; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -131,7 +198,7 @@ ); name = "A2UI-Example"; packageProductDependencies = ( - 06250BF62FC7D25D009091F1 /* GoogleMapsA2UI */, + 06D7D378303CF72A005E2E6E /* GoogleMapsA2UI */, ); productName = "A2UI-Example"; productReference = FDC9C4D02F5B9CB800ABA773 /* A2UI-Example.app */; @@ -167,7 +234,7 @@ mainGroup = FDC9C4C72F5B9CB800ABA773; minimizedProjectReferenceProxies = 1; packageReferences = ( - 06250BF52FC7D25D009091F1 /* XCLocalSwiftPackageReference "../../../a2ui/client/ios/GoogleMapsA2UI" */, + 06D7D376303CF70D005E2E6E /* XCLocalSwiftPackageReference "../../../a2ui/client/ios/GoogleMapsA2UI" */, ); preferredProjectObjectVersion = 77; productRefGroup = FDC9C4D12F5B9CB800ABA773 /* Products */; @@ -185,6 +252,17 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 06D7D3A3303D019F005E2E6E /* SLUSaladsVegan.json in Resources */, + 06D7D3A4303D019F005E2E6E /* SLUSaladsDirections.json in Resources */, + 06D7D3A5303D019F005E2E6E /* NYCAttractions.json in Resources */, + 06D7D3A6303D019F005E2E6E /* GasWorksPark.json in Resources */, + 06D7D3A7303D019F005E2E6E /* SLUSaladsClick.json in Resources */, + 06D7D3A8303D019F005E2E6E /* LondonItinerary.json in Resources */, + 06D7D3A9303D019F005E2E6E /* MVGoogleGyms.json in Resources */, + 06D7D3AA303D019F005E2E6E /* KirklandCommute.json in Resources */, + 06D7D3AB303D019F005E2E6E /* EdgewaterHotel.json in Resources */, + 06D7D3AC303D019F005E2E6E /* SeattleCoffeeShops.json in Resources */, + 06D7D3AD303D019F005E2E6E /* LePetiteAcademy.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -192,6 +270,17 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 06D7D3AE303D29EA005E2E6E /* LePetiteAcademy.json in Resources */, + 06D7D3AF303D29EA005E2E6E /* SLUSaladsVegan.json in Resources */, + 06D7D3B0303D29EA005E2E6E /* NYCAttractions.json in Resources */, + 06D7D3B1303D29EA005E2E6E /* GasWorksPark.json in Resources */, + 06D7D3B2303D29EA005E2E6E /* EdgewaterHotel.json in Resources */, + 06D7D3B3303D29EA005E2E6E /* KirklandCommute.json in Resources */, + 06D7D3B4303D29EA005E2E6E /* SeattleCoffeeShops.json in Resources */, + 06D7D3B5303D29EA005E2E6E /* SLUSaladsDirections.json in Resources */, + 06D7D3B6303D29EA005E2E6E /* LondonItinerary.json in Resources */, + 06D7D3B7303D29EA005E2E6E /* MVGoogleGyms.json in Resources */, + 06D7D3B8303D29EA005E2E6E /* SLUSaladsClick.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -211,6 +300,11 @@ files = ( FDC9C4E32F5B9D7000ABA773 /* Models.swift in Sources */, FDC9C4E42F5B9D7000ABA773 /* ChatApp.swift in Sources */, + 06D7D39E303CFB03005E2E6E /* LatencyLogger.swift in Sources */, + 06D7D39F303CFB03005E2E6E /* ResourceLogger.swift in Sources */, + 06D7D3A0303CFB03005E2E6E /* MockScenarioRegistry.swift in Sources */, + 06D7D3A1303CFB03005E2E6E /* ChatService.swift in Sources */, + 06D7D3A2303CFB03005E2E6E /* TestCasesMenuView.swift in Sources */, FDC9C4E52F5B9D7000ABA773 /* ChatViewModel.swift in Sources */, FDC9C4E72F5B9D7000ABA773 /* ChatView.swift in Sources */, ); @@ -484,16 +578,16 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - 06250BF52FC7D25D009091F1 /* XCLocalSwiftPackageReference "../../../a2ui/client/ios/GoogleMapsA2UI" */ = { + 06D7D376303CF70D005E2E6E /* XCLocalSwiftPackageReference "../../../a2ui/client/ios/GoogleMapsA2UI" */ = { isa = XCLocalSwiftPackageReference; relativePath = ../../../a2ui/client/ios/GoogleMapsA2UI; }; /* End XCLocalSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 06250BF62FC7D25D009091F1 /* GoogleMapsA2UI */ = { + 06D7D378303CF72A005E2E6E /* GoogleMapsA2UI */ = { isa = XCSwiftPackageProductDependency; - package = 06250BF52FC7D25D009091F1 /* XCLocalSwiftPackageReference "../../../a2ui/client/ios/GoogleMapsA2UI" */; + package = 06D7D376303CF70D005E2E6E /* XCLocalSwiftPackageReference "../../../a2ui/client/ios/GoogleMapsA2UI" */; productName = GoogleMapsA2UI; }; /* End XCSwiftPackageProductDependency section */ diff --git a/client/ios/ChatService.swift b/client/ios/ChatService.swift index 74212db..b03af20 100644 --- a/client/ios/ChatService.swift +++ b/client/ios/ChatService.swift @@ -18,9 +18,10 @@ import Foundation import GoogleMapsA2UI protocol ChatServiceProtocol { - func sendMessage(text: String, isVertex: Bool) async throws -> AsyncThrowingStream< - ParsedA2AEvent, Swift.Error - > + func sendMessage(text: String, agentType: AgentType) async throws + -> AsyncThrowingStream< + ParsedA2AEvent, Swift.Error + > func sendAction(jsonString: String) async throws -> AsyncThrowingStream< ParsedA2AEvent, Swift.Error > @@ -67,12 +68,19 @@ actor ChatService: ChatServiceProtocol { private var useSSEProtocol = false private let contextID = UUID().uuidString - func sendMessage(text: String, isVertex: Bool) async throws -> AsyncThrowingStream< - ParsedA2AEvent, Swift.Error - > { + func sendMessage(text: String, agentType: AgentType) async throws + -> AsyncThrowingStream< + ParsedA2AEvent, Swift.Error + > + { var serverText = text - if isVertex { + switch agentType { + case .vertex: serverText = "[GROUNDING] \(text)" + case .template: + serverText = "[TEMPLATE] \(text)" + case .lite: + break } let payload: [String: Any] = ["text": serverText] return try await callPythonServer(userMessage: payload) diff --git a/client/ios/ChatView.swift b/client/ios/ChatView.swift index 598b223..aca9758 100644 --- a/client/ios/ChatView.swift +++ b/client/ios/ChatView.swift @@ -85,7 +85,7 @@ struct ChatView: View { } } - GroundingSelector(selection: $viewModel.selectedGroundingType) + AgentSelector(selection: $viewModel.selectedAgentType) Divider() @@ -212,12 +212,12 @@ struct LoadingBubble: View { } } -struct GroundingSelector: View { - @Binding var selection: GroundingType +struct AgentSelector: View { + @Binding var selection: AgentType var body: some View { VStack(alignment: .leading, spacing: 12) { - ForEach(GroundingType.allCases) { type in + ForEach(AgentType.allCases) { type in Button(action: { withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) { selection = type diff --git a/client/ios/ChatViewModel.swift b/client/ios/ChatViewModel.swift index c38709b..e6bf452 100644 --- a/client/ios/ChatViewModel.swift +++ b/client/ios/ChatViewModel.swift @@ -24,7 +24,7 @@ class ChatViewModel: ObservableObject { @Published private(set) var messages: [ChatMessage] = [] @Published private(set) var isLoading: Bool = false @Published var webViewToScrollID: UUID? - @Published var selectedGroundingType: GroundingType = .lite + @Published var selectedAgentType: AgentType = .lite private let chatService: ChatServiceProtocol private let googleMapsApiKey = "$GOOGLE_MAPS_API_KEY" @@ -39,13 +39,14 @@ class ChatViewModel: ObservableObject { /// Sends a text message to the server. func sendMessage(text: String) { - let isVertex = (selectedGroundingType == .vertex) + let agentType = selectedAgentType addMessage(.text(content: text, isUser: true)) Task { isLoading = true do { - let stream = try await chatService.sendMessage(text: text, isVertex: isVertex) + let stream = try await chatService.sendMessage( + text: text, agentType: agentType) for try await part in stream { handleParsedEvent(part) } diff --git a/client/ios/Info.plist b/client/ios/Info.plist deleted file mode 100644 index 68eb763..0000000 --- a/client/ios/Info.plist +++ /dev/null @@ -1,49 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1 - LSRequiresIPhoneOS - - UIApplicationSceneManifest - - UIApplicationSupportsMultipleScenes - - - UILaunchStoryboardName - LaunchScreen - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - NSAllowsLocalNetworking - - - - diff --git a/client/ios/Models.swift b/client/ios/Models.swift index 0361325..41ca1a7 100644 --- a/client/ios/Models.swift +++ b/client/ios/Models.swift @@ -17,9 +17,10 @@ import Foundation import SwiftUI -enum GroundingType: String, CaseIterable, Identifiable { +enum AgentType: String, CaseIterable, Identifiable { case lite = "Grounding Lite (MCP)" case vertex = "Grounding with Google Maps (Vertex)" + case template = "Template Agent" var id: Self { self } } diff --git a/client/ios/README.md b/client/ios/README.md index d10c48b..9f59c34 100644 --- a/client/ios/README.md +++ b/client/ios/README.md @@ -13,9 +13,9 @@ This application relies on the core `GoogleMapsA2UI` module. To set up this depe ## Project Structure * `ChatApp.swift`: The main application entry point. -* `ChatView.swift`: The main chat UI, displaying message history, the input bar, and toggles to switch between data grounding modes (e.g., Vertex AI Maps Grounding vs. MCP Lite). +* `ChatView.swift`: The main chat UI, displaying message history, the input bar, and toggles to switch between different agent modes (e.g., Vertex AI Maps Grounding vs. MCP Lite vs. Template). * `ChatViewModel.swift`: Handles all networking with the backend protocols, maintains state, and routes A2A responses to the `GoogleMapsA2UI` library parser. -* `Models.swift`: Basic data structures for chat messages and grounding mode configurations. +* `Models.swift`: Basic data structures for chat messages and agent mode configurations. * `GoogleMapsA2UI`: A Swift package dependency pulled in from the `a2ui` module. It provides the `A2UIView` SwiftUI component to render the dynamic maps components and parses the A2A payload into a list of `ParsedA2AEvent` objects. *(See the [Library Dependency](#library-dependency) section above for integration details).* ## Quickstart Guide @@ -59,7 +59,7 @@ If your `activeServer` is set to `.demo` (This means the server is running on yo ### 4. Using the Demo Once the app is running: -* **Select Grounding Mode:** Use the radio buttons above the chat bar to toggle between **Grounding Lite (MCP)** and **Grounding with Google Maps (Vertex)**. +* **Select agent Mode:** Use the radio buttons above the chat bar to toggle between **Grounding Lite (MCP)**, **Grounding with Google Maps (Vertex)** and **Template**. * **Use Canned Prompts:** Tap the **Flask** or **List** icons next to the text input for a menu of pre-written test scenarios. * **Send Custom Prompts:** Type a query into the text box (e.g., *"Show me 3 Chinese restaurants in Seattle"*) and hit send. * **Interact with Maps:** Wait for the A2UI components to load. You can interact with the rendered maps and place cards (like tapping `Get Directions`) to trigger native Swift callbacks. diff --git a/client/web/react/src/utils/platform.ts b/client/web/react/src/utils/platform.ts index 6df1ce0..0122a54 100644 --- a/client/web/react/src/utils/platform.ts +++ b/client/web/react/src/utils/platform.ts @@ -19,7 +19,8 @@ export const isIOS = typeof window !== 'undefined' && typeof (window as any).web export const isMobileWebView = isAndroid || isIOS; export const getAttributionId = () => { - if (isAndroid) return "gmp_web_maui_v0.1.7_exp,gmp_android_maui_v0.1.7_exp"; - if (isIOS) return "gmp_web_maui_v0.1.7_exp,gmp_ios_maui_v0.1.7_exp"; - return "gmp_web_maui_v0.1.7_exp"; + if (isAndroid) + return 'gmp_web_maui_v0.1.8_atoui,gmp_android_maui_v0.1.8_atoui'; + if (isIOS) return 'gmp_web_maui_v0.1.8_atoui,gmp_ios_maui_v0.1.8_atoui'; + return 'gmp_web_maui_v0.1.8_atoui'; };